##// END OF EJS Templates
Adds support for Git's smart HTTP protocol to Redmine.pm (#4905)....
Jean-Philippe Lang -
r9646:e199c4b823c5
parent child
Show More
@@ -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 And you need to upgrade at least reposman.rb (after r860).
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 =cut
182 =cut
106
183
107 use strict;
184 use strict;
@@ -151,6 +228,11 my @directives = (
151 args_how => TAKE1,
228 args_how => TAKE1,
152 errmsg => 'RedmineCacheCredsMax must be decimal number',
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 sub RedmineDSN {
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 sub trim {
284 sub trim {
192 my $string = shift;
285 my $string = shift;
193 $string =~ s/\s{2,}/ /g;
286 $string =~ s/\s{2,}/ /g;
@@ -204,6 +297,23 Apache2::Module::add(__PACKAGE__, \@directives);
204
297
205 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
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 sub access_handler {
317 sub access_handler {
208 my $r = shift;
318 my $r = shift;
209
319
@@ -212,8 +322,7 sub access_handler {
212 return FORBIDDEN;
322 return FORBIDDEN;
213 }
323 }
214
324
215 my $method = $r->method;
325 return OK unless request_is_read_only($r);
216 return OK unless defined $read_only_methods{$method};
217
326
218 my $project_id = get_project_identifier($r);
327 my $project_id = get_project_identifier($r);
219
328
@@ -338,7 +447,7 sub is_member {
338
447
339 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
448 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
340
449
341 my $access_mode = defined $read_only_methods{$r->method} ? "R" : "W";
450 my $access_mode = request_is_read_only($r) ? "R" : "W";
342
451
343 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
452 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
344 my $usrprojpass;
453 my $usrprojpass;
@@ -414,7 +523,9 sub is_member {
414 sub get_project_identifier {
523 sub get_project_identifier {
415 my $r = shift;
524 my $r = shift;
416
525
526 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
417 my $location = $r->location;
527 my $location = $r->location;
528 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
418 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
529 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
419 $identifier;
530 $identifier;
420 }
531 }
General Comments 0
You need to be logged in to leave comments. Login now