| 93 | 93 |  | 
  | 94 | 94 | And you need to upgrade at least reposman.rb (after r860). | 
  | 95 | 95 |  | 
  |  | 96 | =head1 GIT SMART HTTP SUPPORT | 
  |  | 97 |  | 
  |  | 98 | Git's smart HTTP protocol (available since Git 1.7.0) will not work with the | 
  |  | 99 | above settings. Redmine.pm normally does access control depending on the HTTP | 
  |  | 100 | method used: read-only methods are OK for everyone in public projects and | 
  |  | 101 | members with read rights in private projects. The rest require membership with | 
  |  | 102 | commit rights in the project. | 
  |  | 103 |  | 
  |  | 104 | However, this scheme doesn't work for Git's smart HTTP protocol, as it will use | 
  |  | 105 | POST even for a simple clone. Instead, read-only requests must be detected using | 
  |  | 106 | the full URL (including the query string): anything that doesn't belong to the | 
  |  | 107 | git-receive-pack service is read-only. | 
  |  | 108 |  | 
  |  | 109 | To activate this mode of operation, add this line inside your <Location /git> | 
  |  | 110 | block: | 
  |  | 111 |  | 
  |  | 112 |   RedmineGitSmartHttp yes | 
  |  | 113 |  | 
  |  | 114 | Here's a sample Apache configuration which integrates git-http-backend with | 
  |  | 115 | a MySQL database and this new option: | 
  |  | 116 |  | 
  |  | 117 |    SetEnv GIT_PROJECT_ROOT /var/www/git/ | 
  |  | 118 |    SetEnv GIT_HTTP_EXPORT_ALL | 
  |  | 119 |    ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ | 
  |  | 120 |    <Location /git> | 
  |  | 121 |        Order allow,deny | 
  |  | 122 |        Allow from all | 
  |  | 123 |  | 
  |  | 124 |        AuthType Basic | 
  |  | 125 |        AuthName Git | 
  |  | 126 |        Require valid-user | 
  |  | 127 |  | 
  |  | 128 |        PerlAccessHandler Apache::Authn::Redmine::access_handler | 
  |  | 129 |        PerlAuthenHandler Apache::Authn::Redmine::authen_handler | 
  |  | 130 |        # for mysql | 
  |  | 131 |        RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1" | 
  |  | 132 |        RedmineDbUser "redmine" | 
  |  | 133 |        RedmineDbPass "xxx" | 
  |  | 134 |        RedmineGitSmartHttp yes | 
  |  | 135 |     </Location> | 
  |  | 136 |  | 
  |  | 137 | Make sure that all the names of the repositories under /var/www/git/ match | 
  |  | 138 | exactly the identifier for some project: /var/www/git/myproject.git won't work, | 
  |  | 139 | due to the way this module extracts the identifier from the URL. | 
  |  | 140 | /var/www/git/myproject will work, though. You can put both bare and non-bare | 
  |  | 141 | repositories in /var/www/git, though bare repositories are strongly | 
  |  | 142 | recommended. You should create them with the rights of the user running Redmine, | 
  |  | 143 | like this: | 
  |  | 144 |  | 
  |  | 145 |   cd /var/www/git | 
  |  | 146 |   sudo -u user-running-redmine mkdir myproject | 
  |  | 147 |   cd myproject | 
  |  | 148 |   sudo -u user-running-redmine git init --bare | 
  |  | 149 |  | 
  |  | 150 | Once you have activated this option, you have three options when cloning a | 
  |  | 151 | repository: | 
  |  | 152 |  | 
  |  | 153 | - Cloning using "http://user@host/git/repo" works, but will ask for the password | 
  |  | 154 |   all the time. | 
  |  | 155 |  | 
  |  | 156 | - Cloning with "http://user:pass@host/git/repo" does not have this problem, but | 
  |  | 157 |   this could reveal accidentally your password to the console in some versions | 
  |  | 158 |   of Git, and you would have to ensure that .git/config is not readable except | 
  |  | 159 |   by the owner for each of your projects. | 
  |  | 160 |  | 
  |  | 161 | - Use "http://host/git/repo", and store your credentials in the ~/.netrc | 
  |  | 162 |   file. This is the recommended solution, as you only have one file to protect | 
  |  | 163 |   and passwords will not be leaked accidentally to the console. | 
  |  | 164 |  | 
  |  | 165 |   IMPORTANT NOTE: It is *very important* that the file cannot be read by other | 
  |  | 166 |   users, as it will contain your password in cleartext. To create the file, you | 
  |  | 167 |   can use the following commands, replacing yourhost, youruser and yourpassword | 
  |  | 168 |   with the right values: | 
  |  | 169 |  | 
  |  | 170 |     touch ~/.netrc | 
  |  | 171 |     chmod 600 ~/.netrc | 
  |  | 172 |     echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc | 
  |  | 173 |  | 
  | 96 | 174 | =cut | 
  | 97 | 175 |  | 
  | 98 | 176 | use strict; | 
  | ... | ... |  | 
  | 142 | 220 |     args_how => TAKE1, | 
  | 143 | 221 |     errmsg => 'RedmineCacheCredsMax must be decimal number', | 
  | 144 | 222 |   }, | 
  |  | 223 |   { | 
  |  | 224 |     name => 'RedmineGitSmartHttp', | 
  |  | 225 |     req_override => OR_AUTHCFG, | 
  |  | 226 |     args_how => TAKE1, | 
  |  | 227 |   }, | 
  | 145 | 228 | ); | 
  | 146 | 229 |  | 
  | 147 | 230 | sub RedmineDSN {  | 
  | ... | ... |  | 
  | 178 | 261 |   } | 
  | 179 | 262 | } | 
  | 180 | 263 |  | 
  |  | 264 | sub RedmineGitSmartHttp { | 
  |  | 265 |   my ($self, $parms, $arg) = @_; | 
  |  | 266 |   $arg = lc $arg; | 
  |  | 267 |  | 
  |  | 268 |   if ($arg eq "yes" || $arg eq "true") { | 
  |  | 269 |     $self->{RedmineGitSmartHttp} = 1; | 
  |  | 270 |   } else { | 
  |  | 271 |     $self->{RedmineGitSmartHttp} = 0; | 
  |  | 272 |   } | 
  |  | 273 | } | 
  |  | 274 |  | 
  | 181 | 275 | sub trim { | 
  | 182 | 276 |   my $string = shift; | 
  | 183 | 277 |   $string =~ s/\s{2,}/ /g; | 
  | ... | ... |  | 
  | 194 | 288 |  | 
  | 195 | 289 | my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; | 
  | 196 | 290 |  | 
  |  | 291 | sub request_is_read_only { | 
  |  | 292 |   my ($r) = @_; | 
  |  | 293 |   my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); | 
  |  | 294 |  | 
  |  | 295 |   # Do we use Git's smart HTTP protocol, or not? | 
  |  | 296 |   if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) { | 
  |  | 297 |     my $uri = $r->unparsed_uri; | 
  |  | 298 |     my $location = $r->location; | 
  |  | 299 |     my $is_read_only = $uri !~ m{^$location/*[^/]+/(info/refs\?service=)?git\-receive\-pack$}o; | 
  |  | 300 |     return $is_read_only; | 
  |  | 301 |   } else { | 
  |  | 302 |     # Old behaviour: check the HTTP method | 
  |  | 303 |     my $method = $r->method; | 
  |  | 304 |     return defined $read_only_methods{$method}; | 
  |  | 305 |   } | 
  |  | 306 | } | 
  |  | 307 |  | 
  | 197 | 308 | sub access_handler { | 
  | 198 | 309 |   my $r = shift; | 
  | 199 | 310 |  | 
  | ... | ... |  | 
  | 202 | 313 |       return FORBIDDEN; | 
  | 203 | 314 |   } | 
  | 204 | 315 |  | 
  | 205 |  |   my $method = $r->method; | 
  | 206 |  |   return OK unless defined $read_only_methods{$method}; | 
  |  | 316 |   return OK unless request_is_read_only($r); | 
  | 207 | 317 |  | 
  | 208 | 318 |   my $project_id = get_project_identifier($r); | 
  | 209 | 319 |  | 
  | ... | ... |  | 
  | 320 | 430 |  | 
  | 321 | 431 |       unless ($auth_source_id) { | 
  | 322 | 432 | 	  my $method = $r->method; | 
  | 323 |  |           if ($hashed_password eq $pass_digest && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { | 
  |  | 433 |           if ($hashed_password eq $pass_digest && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { | 
  | 324 | 434 |               $ret = 1; | 
  | 325 | 435 |               last; | 
  | 326 | 436 |           } | 
  | ... | ... |  | 
  | 339 | 449 |                 filter  =>      "(".$rowldap[6]."=%s)" | 
  | 340 | 450 |             ); | 
  | 341 | 451 |             my $method = $r->method; | 
  | 342 |  |             $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/)); | 
  |  | 452 |             $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) | 
  |  | 453 | 			 && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/)); | 
  | 343 | 454 |  | 
  | 344 | 455 |           } | 
  | 345 | 456 |           $sthldap->finish(); |