Mercurial > hg > soundsoftware-site
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; |