comparison extra/svn/Redmine.pm @ 1115:433d4f72a19b redmine-2.2

Update to Redmine SVN revision 11137 on 2.2-stable branch
author Chris Cannam
date Mon, 07 Jan 2013 12:01:42 +0000
parents 5f33065ddc4b
children 622f24f53b42
comparison
equal deleted inserted replaced
929:5f33065ddc4b 1115:433d4f72a19b
80 80
81 and you will have to use this reposman.rb command line to create repository : 81 and you will have to use this reposman.rb command line to create repository :
82 82
83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/ 83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
84 84
85 =head1 REPOSITORIES NAMING
86
87 A projet repository must be named with the projet identifier. In case
88 of multiple repositories for the same project, use the project identifier
89 and the repository identifier separated with a dot:
90
91 /var/svn/foo
92 /var/svn/foo.otherrepo
93
85 =head1 MIGRATION FROM OLDER RELEASES 94 =head1 MIGRATION FROM OLDER RELEASES
86 95
87 If you use an older reposman.rb (r860 or before), you need to change 96 If you use an older reposman.rb (r860 or before), you need to change
88 rights on repositories to allow the apache user to read and write 97 rights on repositories to allow the apache user to read and write
89 S<them :> 98 S<them :>
90 99
91 sudo chown -R www-data /var/svn/* 100 sudo chown -R www-data /var/svn/*
92 sudo chmod -R u+w /var/svn/* 101 sudo chmod -R u+w /var/svn/*
93 102
94 And you need to upgrade at least reposman.rb (after r860). 103 And you need to upgrade at least reposman.rb (after r860).
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
95 181
96 =cut 182 =cut
97 183
98 use strict; 184 use strict;
99 use warnings FATAL => 'all', NONFATAL => 'redefine'; 185 use warnings FATAL => 'all', NONFATAL => 'redefine';
140 name => 'RedmineCacheCredsMax', 226 name => 'RedmineCacheCredsMax',
141 req_override => OR_AUTHCFG, 227 req_override => OR_AUTHCFG,
142 args_how => TAKE1, 228 args_how => TAKE1,
143 errmsg => 'RedmineCacheCredsMax must be decimal number', 229 errmsg => 'RedmineCacheCredsMax must be decimal number',
144 }, 230 },
231 {
232 name => 'RedmineGitSmartHttp',
233 req_override => OR_AUTHCFG,
234 args_how => TAKE1,
235 },
145 ); 236 );
146 237
147 sub RedmineDSN { 238 sub RedmineDSN {
148 my ($self, $parms, $arg) = @_; 239 my ($self, $parms, $arg) = @_;
149 $self->{RedmineDSN} = $arg; 240 $self->{RedmineDSN} = $arg;
150 my $query = "SELECT 241 my $query = "SELECT
151 hashed_password, salt, auth_source_id, permissions 242 users.hashed_password, users.salt, users.auth_source_id, roles.permissions, projects.status
152 FROM projects, users, roles 243 FROM projects, users, roles
153 WHERE 244 WHERE
154 users.login=? 245 users.login=?
155 AND projects.identifier=? 246 AND projects.identifier=?
156 AND users.status=1 247 AND users.status=1
157 AND ( 248 AND (
158 roles.id IN (SELECT member_roles.role_id FROM members, member_roles WHERE members.user_id = users.id AND members.project_id = projects.id AND members.id = member_roles.member_id) 249 roles.id IN (SELECT member_roles.role_id FROM members, member_roles WHERE members.user_id = users.id AND members.project_id = projects.id AND members.id = member_roles.member_id)
159 OR 250 OR
160 (roles.builtin=1 AND cast(projects.is_public as CHAR) IN ('t', '1')) 251 (roles.builtin=1 AND cast(projects.is_public as CHAR) IN ('t', '1'))
161 ) "; 252 )
253 AND roles.permissions IS NOT NULL";
162 $self->{RedmineQuery} = trim($query); 254 $self->{RedmineQuery} = trim($query);
163 } 255 }
164 256
165 sub RedmineDbUser { set_val('RedmineDbUser', @_); } 257 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
166 sub RedmineDbPass { set_val('RedmineDbPass', @_); } 258 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
177 $self->{RedmineCacheCredsCount} = 0; 269 $self->{RedmineCacheCredsCount} = 0;
178 $self->{RedmineCacheCredsMax} = $arg; 270 $self->{RedmineCacheCredsMax} = $arg;
179 } 271 }
180 } 272 }
181 273
274 sub RedmineGitSmartHttp {
275 my ($self, $parms, $arg) = @_;
276 $arg = lc $arg;
277
278 if ($arg eq "yes" || $arg eq "true") {
279 $self->{RedmineGitSmartHttp} = 1;
280 } else {
281 $self->{RedmineGitSmartHttp} = 0;
282 }
283 }
284
182 sub trim { 285 sub trim {
183 my $string = shift; 286 my $string = shift;
184 $string =~ s/\s{2,}/ /g; 287 $string =~ s/\s{2,}/ /g;
185 return $string; 288 return $string;
186 } 289 }
191 } 294 }
192 295
193 Apache2::Module::add(__PACKAGE__, \@directives); 296 Apache2::Module::add(__PACKAGE__, \@directives);
194 297
195 298
196 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; 299 my %read_only_methods = map { $_ => 1 } qw/GET HEAD PROPFIND REPORT OPTIONS/;
300
301 sub request_is_read_only {
302 my ($r) = @_;
303 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
304
305 # Do we use Git's smart HTTP protocol, or not?
306 if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
307 my $uri = $r->unparsed_uri;
308 my $location = $r->location;
309 my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
310 return $is_read_only;
311 } else {
312 # Standard behaviour: check the HTTP method
313 my $method = $r->method;
314 return defined $read_only_methods{$method};
315 }
316 }
197 317
198 sub access_handler { 318 sub access_handler {
199 my $r = shift; 319 my $r = shift;
200 320
201 unless ($r->some_auth_required) { 321 unless ($r->some_auth_required) {
202 $r->log_reason("No authentication has been configured"); 322 $r->log_reason("No authentication has been configured");
203 return FORBIDDEN; 323 return FORBIDDEN;
204 } 324 }
205 325
206 my $method = $r->method; 326 return OK unless request_is_read_only($r);
207 return OK unless defined $read_only_methods{$method};
208 327
209 my $project_id = get_project_identifier($r); 328 my $project_id = get_project_identifier($r);
210 329
211 $r->set_handlers(PerlAuthenHandler => [\&OK]) 330 $r->set_handlers(PerlAuthenHandler => [\&OK])
212 if is_public_project($project_id, $r) && anonymous_role_allows_browse_repository($r); 331 if is_public_project($project_id, $r) && anonymous_role_allows_browse_repository($r);
222 341
223 if (is_member($r->user, $redmine_pass, $r)) { 342 if (is_member($r->user, $redmine_pass, $r)) {
224 return OK; 343 return OK;
225 } else { 344 } else {
226 $r->note_auth_failure(); 345 $r->note_auth_failure();
227 return AUTH_REQUIRED; 346 return DECLINED;
228 } 347 }
229 } 348 }
230 349
231 # check if authentication is forced 350 # check if authentication is forced
232 sub is_authentication_forced { 351 sub is_authentication_forced {
261 return 0; 380 return 0;
262 } 381 }
263 382
264 my $dbh = connect_database($r); 383 my $dbh = connect_database($r);
265 my $sth = $dbh->prepare( 384 my $sth = $dbh->prepare(
266 "SELECT is_public FROM projects WHERE projects.identifier = ?;" 385 "SELECT is_public FROM projects WHERE projects.identifier = ? AND projects.status <> 9;"
267 ); 386 );
268 387
269 $sth->execute($project_id); 388 $sth->execute($project_id);
270 my $ret = 0; 389 my $ret = 0;
271 if (my @row = $sth->fetchrow_array) { 390 if (my @row = $sth->fetchrow_array) {
327 my $dbh = connect_database($r); 446 my $dbh = connect_database($r);
328 my $project_id = get_project_identifier($r); 447 my $project_id = get_project_identifier($r);
329 448
330 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass); 449 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
331 450
332 my $access_mode = defined $read_only_methods{$r->method} ? "R" : "W"; 451 my $access_mode = request_is_read_only($r) ? "R" : "W";
333 452
334 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); 453 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
335 my $usrprojpass; 454 my $usrprojpass;
336 if ($cfg->{RedmineCacheCredsMax}) { 455 if ($cfg->{RedmineCacheCredsMax}) {
337 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode); 456 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
340 my $query = $cfg->{RedmineQuery}; 459 my $query = $cfg->{RedmineQuery};
341 my $sth = $dbh->prepare($query); 460 my $sth = $dbh->prepare($query);
342 $sth->execute($redmine_user, $project_id); 461 $sth->execute($redmine_user, $project_id);
343 462
344 my $ret; 463 my $ret;
345 while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) { 464 while (my ($hashed_password, $salt, $auth_source_id, $permissions, $project_status) = $sth->fetchrow_array) {
465 if ($project_status eq "9" || ($project_status ne "1" && $access_mode eq "W")) {
466 last;
467 }
346 468
347 unless ($auth_source_id) { 469 unless ($auth_source_id) {
348 my $method = $r->method; 470 my $method = $r->method;
349 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest); 471 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
350 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { 472 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
355 my $sthldap = $dbh->prepare( 477 my $sthldap = $dbh->prepare(
356 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;" 478 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
357 ); 479 );
358 $sthldap->execute($auth_source_id); 480 $sthldap->execute($auth_source_id);
359 while (my @rowldap = $sthldap->fetchrow_array) { 481 while (my @rowldap = $sthldap->fetchrow_array) {
482 my $bind_as = $rowldap[3] ? $rowldap[3] : "";
483 my $bind_pw = $rowldap[4] ? $rowldap[4] : "";
484 if ($bind_as =~ m/\$login/) {
485 # replace $login with $redmine_user and use $redmine_pass
486 $bind_as =~ s/\$login/$redmine_user/g;
487 $bind_pw = $redmine_pass
488 }
360 my $ldap = Authen::Simple::LDAP->new( 489 my $ldap = Authen::Simple::LDAP->new(
361 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0], 490 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
362 port => $rowldap[1], 491 port => $rowldap[1],
363 basedn => $rowldap[5], 492 basedn => $rowldap[5],
364 binddn => $rowldap[3] ? $rowldap[3] : "", 493 binddn => $bind_as,
365 bindpw => $rowldap[4] ? $rowldap[4] : "", 494 bindpw => $bind_pw,
366 filter => "(".$rowldap[6]."=%s)" 495 filter => "(".$rowldap[6]."=%s)"
367 ); 496 );
368 my $method = $r->method; 497 my $method = $r->method;
369 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/)); 498 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
370 499
396 } 525 }
397 526
398 sub get_project_identifier { 527 sub get_project_identifier {
399 my $r = shift; 528 my $r = shift;
400 529
530 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
401 my $location = $r->location; 531 my $location = $r->location;
402 my ($identifier) = $r->uri =~ m{$location/*([^/]+)}; 532 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
533 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
403 $identifier; 534 $identifier;
404 } 535 }
405 536
406 sub connect_database { 537 sub connect_database {
407 my $r = shift; 538 my $r = shift;