@@ -0,0 +1,97 | |||
|
1 | # Redmine - project management software | |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
|
3 | # | |
|
4 | # This program is free software; you can redistribute it and/or | |
|
5 | # modify it under the terms of the GNU General Public License | |
|
6 | # as published by the Free Software Foundation; either version 2 | |
|
7 | # of the License, or (at your option) any later version. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU General Public License | |
|
15 | # along with this program; if not, write to the Free Software | |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
|
17 | ||
|
18 | require File.expand_path('../test_case', __FILE__) | |
|
19 | require 'tmpdir' | |
|
20 | ||
|
21 | class RedminePmTest::RepositoryGitTest < RedminePmTest::TestCase | |
|
22 | fixtures :projects, :users, :members, :roles, :member_roles | |
|
23 | ||
|
24 | GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" | |
|
25 | ||
|
26 | def test_anonymous_read_on_public_repo_with_permission_should_succeed | |
|
27 | assert_success "ls-remote", git_url | |
|
28 | end | |
|
29 | ||
|
30 | def test_anonymous_read_on_public_repo_without_permission_should_fail | |
|
31 | Role.anonymous.remove_permission! :browse_repository | |
|
32 | assert_failure "ls-remote", git_url | |
|
33 | end | |
|
34 | ||
|
35 | def test_invalid_credentials_should_fail | |
|
36 | Project.find(1).update_attribute :is_public, false | |
|
37 | with_credentials "dlopper", "foo" do | |
|
38 | assert_success "ls-remote", git_url | |
|
39 | end | |
|
40 | with_credentials "dlopper", "wrong" do | |
|
41 | assert_failure "ls-remote", git_url | |
|
42 | end | |
|
43 | end | |
|
44 | ||
|
45 | def test_clone | |
|
46 | Dir.mktmpdir do |dir| | |
|
47 | Dir.chdir(dir) do | |
|
48 | assert_success "clone", git_url | |
|
49 | end | |
|
50 | end | |
|
51 | end | |
|
52 | ||
|
53 | def test_write_commands | |
|
54 | Role.find(2).add_permission! :commit_access | |
|
55 | filename = random_filename | |
|
56 | ||
|
57 | Dir.mktmpdir do |dir| | |
|
58 | assert_success "clone", git_url, dir | |
|
59 | Dir.chdir(dir) do | |
|
60 | f = File.new(File.join(dir, filename), "w") | |
|
61 | f.write "test file content" | |
|
62 | f.close | |
|
63 | ||
|
64 | with_credentials "dlopper", "foo" do | |
|
65 | assert_success "add", filename | |
|
66 | assert_success "commit -a --message Committing_a_file" | |
|
67 | assert_success "push", git_url, "--all" | |
|
68 | end | |
|
69 | end | |
|
70 | end | |
|
71 | ||
|
72 | Dir.mktmpdir do |dir| | |
|
73 | assert_success "clone", git_url, dir | |
|
74 | Dir.chdir(dir) do | |
|
75 | assert File.exists?(File.join(dir, "#{filename}")) | |
|
76 | end | |
|
77 | end | |
|
78 | end | |
|
79 | ||
|
80 | protected | |
|
81 | ||
|
82 | def execute(*args) | |
|
83 | a = [GIT_BIN] | |
|
84 | super a, *args | |
|
85 | end | |
|
86 | ||
|
87 | def git_url(path=nil) | |
|
88 | host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1' | |
|
89 | credentials = nil | |
|
90 | if username && password | |
|
91 | credentials = "#{username}:#{password}" | |
|
92 | end | |
|
93 | url = "http://#{credentials}@#{host}/git/ecookbook" | |
|
94 | url << "/#{path}" if path | |
|
95 | url | |
|
96 | end | |
|
97 | end |
@@ -102,6 +102,83 S<them :> | |||
|
102 | 102 | |
|
103 | 103 | And you need to upgrade at least reposman.rb (after r860). |
|
104 | 104 | |
|
105 | =head1 GIT SMART HTTP SUPPORT | |
|
106 | ||
|
107 | Git's smart HTTP protocol (available since Git 1.7.0) will not work with the | |
|
108 | above settings. Redmine.pm normally does access control depending on the HTTP | |
|
109 | method used: read-only methods are OK for everyone in public projects and | |
|
110 | members with read rights in private projects. The rest require membership with | |
|
111 | commit rights in the project. | |
|
112 | ||
|
113 | However, this scheme doesn't work for Git's smart HTTP protocol, as it will use | |
|
114 | POST even for a simple clone. Instead, read-only requests must be detected using | |
|
115 | the full URL (including the query string): anything that doesn't belong to the | |
|
116 | git-receive-pack service is read-only. | |
|
117 | ||
|
118 | To activate this mode of operation, add this line inside your <Location /git> | |
|
119 | block: | |
|
120 | ||
|
121 | RedmineGitSmartHttp yes | |
|
122 | ||
|
123 | Here's a sample Apache configuration which integrates git-http-backend with | |
|
124 | a MySQL database and this new option: | |
|
125 | ||
|
126 | SetEnv GIT_PROJECT_ROOT /var/www/git/ | |
|
127 | SetEnv GIT_HTTP_EXPORT_ALL | |
|
128 | ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ | |
|
129 | <Location /git> | |
|
130 | Order allow,deny | |
|
131 | Allow from all | |
|
132 | ||
|
133 | AuthType Basic | |
|
134 | AuthName Git | |
|
135 | Require valid-user | |
|
136 | ||
|
137 | PerlAccessHandler Apache::Authn::Redmine::access_handler | |
|
138 | PerlAuthenHandler Apache::Authn::Redmine::authen_handler | |
|
139 | # for mysql | |
|
140 | RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1" | |
|
141 | RedmineDbUser "redmine" | |
|
142 | RedmineDbPass "xxx" | |
|
143 | RedmineGitSmartHttp yes | |
|
144 | </Location> | |
|
145 | ||
|
146 | Make sure that all the names of the repositories under /var/www/git/ have a | |
|
147 | matching identifier for some project: /var/www/git/myproject and | |
|
148 | /var/www/git/myproject.git will work. You can put both bare and non-bare | |
|
149 | repositories in /var/www/git, though bare repositories are strongly | |
|
150 | recommended. You should create them with the rights of the user running Redmine, | |
|
151 | like this: | |
|
152 | ||
|
153 | cd /var/www/git | |
|
154 | sudo -u user-running-redmine mkdir myproject | |
|
155 | cd myproject | |
|
156 | sudo -u user-running-redmine git init --bare | |
|
157 | ||
|
158 | Once you have activated this option, you have three options when cloning a | |
|
159 | repository: | |
|
160 | ||
|
161 | - Cloning using "http://user@host/git/repo(.git)" works, but will ask for the password | |
|
162 | all the time. | |
|
163 | ||
|
164 | - Cloning with "http://user:pass@host/git/repo(.git)" does not have this problem, but | |
|
165 | this could reveal accidentally your password to the console in some versions | |
|
166 | of Git, and you would have to ensure that .git/config is not readable except | |
|
167 | by the owner for each of your projects. | |
|
168 | ||
|
169 | - Use "http://host/git/repo(.git)", and store your credentials in the ~/.netrc | |
|
170 | file. This is the recommended solution, as you only have one file to protect | |
|
171 | and passwords will not be leaked accidentally to the console. | |
|
172 | ||
|
173 | IMPORTANT NOTE: It is *very important* that the file cannot be read by other | |
|
174 | users, as it will contain your password in cleartext. To create the file, you | |
|
175 | can use the following commands, replacing yourhost, youruser and yourpassword | |
|
176 | with the right values: | |
|
177 | ||
|
178 | touch ~/.netrc | |
|
179 | chmod 600 ~/.netrc | |
|
180 | echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc | |
|
181 | ||
|
105 | 182 | =cut |
|
106 | 183 | |
|
107 | 184 | use strict; |
@@ -151,6 +228,11 my @directives = ( | |||
|
151 | 228 | args_how => TAKE1, |
|
152 | 229 | errmsg => 'RedmineCacheCredsMax must be decimal number', |
|
153 | 230 | }, |
|
231 | { | |
|
232 | name => 'RedmineGitSmartHttp', | |
|
233 | req_override => OR_AUTHCFG, | |
|
234 | args_how => TAKE1, | |
|
235 | }, | |
|
154 | 236 | ); |
|
155 | 237 | |
|
156 | 238 | sub RedmineDSN { |
@@ -188,6 +270,17 sub RedmineCacheCredsMax { | |||
|
188 | 270 | } |
|
189 | 271 | } |
|
190 | 272 | |
|
273 | sub RedmineGitSmartHttp { | |
|
274 | my ($self, $parms, $arg) = @_; | |
|
275 | $arg = lc $arg; | |
|
276 | ||
|
277 | if ($arg eq "yes" || $arg eq "true") { | |
|
278 | $self->{RedmineGitSmartHttp} = 1; | |
|
279 | } else { | |
|
280 | $self->{RedmineGitSmartHttp} = 0; | |
|
281 | } | |
|
282 | } | |
|
283 | ||
|
191 | 284 | sub trim { |
|
192 | 285 | my $string = shift; |
|
193 | 286 | $string =~ s/\s{2,}/ /g; |
@@ -204,6 +297,23 Apache2::Module::add(__PACKAGE__, \@directives); | |||
|
204 | 297 | |
|
205 | 298 | my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; |
|
206 | 299 | |
|
300 | sub request_is_read_only { | |
|
301 | my ($r) = @_; | |
|
302 | my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); | |
|
303 | ||
|
304 | # Do we use Git's smart HTTP protocol, or not? | |
|
305 | if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) { | |
|
306 | my $uri = $r->unparsed_uri; | |
|
307 | my $location = $r->location; | |
|
308 | my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o; | |
|
309 | return $is_read_only; | |
|
310 | } else { | |
|
311 | # Standard behaviour: check the HTTP method | |
|
312 | my $method = $r->method; | |
|
313 | return defined $read_only_methods{$method}; | |
|
314 | } | |
|
315 | } | |
|
316 | ||
|
207 | 317 | sub access_handler { |
|
208 | 318 | my $r = shift; |
|
209 | 319 | |
@@ -212,8 +322,7 sub access_handler { | |||
|
212 | 322 | return FORBIDDEN; |
|
213 | 323 | } |
|
214 | 324 | |
|
215 | my $method = $r->method; | |
|
216 | return OK unless defined $read_only_methods{$method}; | |
|
325 | return OK unless request_is_read_only($r); | |
|
217 | 326 | |
|
218 | 327 | my $project_id = get_project_identifier($r); |
|
219 | 328 | |
@@ -338,7 +447,7 sub is_member { | |||
|
338 | 447 | |
|
339 | 448 | my $pass_digest = Digest::SHA::sha1_hex($redmine_pass); |
|
340 | 449 | |
|
341 |
my $access_mode = |
|
|
450 | my $access_mode = request_is_read_only($r) ? "R" : "W"; | |
|
342 | 451 | |
|
343 | 452 | my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); |
|
344 | 453 | my $usrprojpass; |
@@ -414,7 +523,9 sub is_member { | |||
|
414 | 523 | sub get_project_identifier { |
|
415 | 524 | my $r = shift; |
|
416 | 525 | |
|
526 | my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); | |
|
417 | 527 | my $location = $r->location; |
|
528 | $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}); | |
|
418 | 529 | my ($identifier) = $r->uri =~ m{$location/*([^/.]+)}; |
|
419 | 530 | $identifier; |
|
420 | 531 | } |
General Comments 0
You need to be logged in to leave comments.
Login now