##// END OF EJS Templates
Fix access to Repository Parent Path (FORBIDEN instead of AUTH_REQUIRED)....
Liwiusz Ociepa -
r2056:131b15fc7a0d swistak
parent child
Show More
@@ -1,368 +1,372
1 1 package Apache::Authn::Redmine;
2 2
3 3 =head1 Apache::Authn::Redmine
4 4
5 5 Redmine - a mod_perl module to authenticate webdav subversion users
6 6 against redmine database
7 7
8 8 =head1 SYNOPSIS
9 9
10 10 This module allow anonymous users to browse public project and
11 11 registred users to browse and commit their project. Authentication is
12 12 done against the redmine database or the LDAP configured in redmine.
13 13
14 14 This method is far simpler than the one with pam_* and works with all
15 15 database without an hassle but you need to have apache/mod_perl on the
16 16 svn server.
17 17
18 18 =head1 INSTALLATION
19 19
20 20 For this to automagically work, you need to have a recent reposman.rb
21 21 (after r860) and if you already use reposman, read the last section to
22 22 migrate.
23 23
24 24 Sorry ruby users but you need some perl modules, at least mod_perl2,
25 25 DBI and DBD::mysql (or the DBD driver for you database as it should
26 26 work on allmost all databases).
27 27
28 28 On debian/ubuntu you must do :
29 29
30 30 aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
31 31
32 32 If your Redmine users use LDAP authentication, you will also need
33 33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
34 34
35 35 aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl
36 36
37 37 =head1 CONFIGURATION
38 38
39 39 ## This module has to be in your perl path
40 40 ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm
41 41 PerlLoadModule Apache::Authn::Redmine
42 42 <Location /svn>
43 43 DAV svn
44 44 SVNParentPath "/var/svn"
45 45
46 46 AuthType Basic
47 47 AuthName redmine
48 48 Require valid-user
49 49
50 50 PerlAccessHandler Apache::Authn::Redmine::access_handler
51 51 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
52 52
53 53 ## for mysql
54 54 RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
55 55 ## for postgres
56 56 # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server"
57 57
58 58 RedmineDbUser "redmine"
59 59 RedmineDbPass "password"
60 60 ## Optional where clause (fulltext search would be slow and
61 61 ## database dependant).
62 62 # RedmineDbWhereClause "and members.role_id IN (1,2)"
63 63 ## Configuration for memcached
64 64 # RedmineMemcacheServers "127.0.0.1:112211"
65 65 # RedmineMemcacheExpirySec "60"
66 66 # RedmineMemcacheNamespace "RedmineCreds:"
67 67 </Location>
68 68
69 69 To be able to browse repository inside redmine, you must add something
70 70 like that :
71 71
72 72 <Location /svn-private>
73 73 DAV svn
74 74 SVNParentPath "/var/svn"
75 75 Order deny,allow
76 76 Deny from all
77 77 # only allow reading orders
78 78 <Limit GET PROPFIND OPTIONS REPORT>
79 79 Allow from redmine.server.ip
80 80 </Limit>
81 81 </Location>
82 82
83 83 and you will have to use this reposman.rb command line to create repository :
84 84
85 85 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
86 86
87 87 =head1 MIGRATION FROM OLDER RELEASES
88 88
89 89 If you use an older reposman.rb (r860 or before), you need to change
90 90 rights on repositories to allow the apache user to read and write
91 91 S<them :>
92 92
93 93 sudo chown -R www-data /var/svn/*
94 94 sudo chmod -R u+w /var/svn/*
95 95
96 96 And you need to upgrade at least reposman.rb (after r860).
97 97
98 98 =cut
99 99
100 100 use strict;
101 101 use warnings FATAL => 'all', NONFATAL => 'redefine';
102 102
103 103 use DBI;
104 104 use Digest::SHA1;
105 105 # optional module for LDAP authentication
106 106 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
107 107 my $CanUseMemcached = eval("use Cache::Memcached; 1");
108 108
109 109 use Apache2::Module;
110 110 use Apache2::Access;
111 111 use Apache2::ServerRec qw();
112 112 use Apache2::RequestRec qw();
113 113 use Apache2::RequestUtil qw();
114 114 use Apache2::Const qw(:common :override :cmd_how);
115 115
116 116 # use Apache2::Directive qw();
117 117
118 118 my @directives = (
119 119 {
120 120 name => 'RedmineDSN',
121 121 req_override => OR_AUTHCFG,
122 122 args_how => TAKE1,
123 123 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
124 124 },
125 125 {
126 126 name => 'RedmineDbUser',
127 127 req_override => OR_AUTHCFG,
128 128 args_how => TAKE1,
129 129 },
130 130 {
131 131 name => 'RedmineDbPass',
132 132 req_override => OR_AUTHCFG,
133 133 args_how => TAKE1,
134 134 },
135 135 {
136 136 name => 'RedmineDbWhereClause',
137 137 req_override => OR_AUTHCFG,
138 138 args_how => TAKE1,
139 139 },
140 140 {
141 141 name => 'RedmineMemcacheServers',
142 142 req_override => OR_AUTHCFG,
143 143 args_how => TAKE1,
144 144 },
145 145 {
146 146 name => 'RedmineMemcacheExpirySec',
147 147 req_override => OR_AUTHCFG,
148 148 args_how => TAKE1,
149 149 },
150 150 {
151 151 name => 'RedmineMemcacheNamespace',
152 152 req_override => OR_AUTHCFG,
153 153 args_how => TAKE1,
154 154 },
155 155 );
156 156
157 157 sub RedmineDSN {
158 158 my ($self, $parms, $arg) = @_;
159 159 $self->{RedmineDSN} = $arg;
160 160 my $query = "SELECT
161 161 hashed_password, auth_source_id
162 162 FROM members, projects, users
163 163 WHERE
164 164 projects.id=members.project_id
165 165 AND users.id=members.user_id
166 166 AND users.status=1
167 167 AND login=?
168 168 AND identifier=? ";
169 169 $self->{RedmineQuery} = trim($query);
170 170 }
171 171 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
172 172 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
173 173 sub RedmineDbWhereClause {
174 174 my ($self, $parms, $arg) = @_;
175 175 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
176 176 }
177 177
178 178 sub RedmineMemcacheServers {
179 179 my ($self, $parms, $arg) = @_;
180 180 if ($arg && $CanUseMemcached) {
181 181 $self->{RedmineMemcached} = new Cache::Memcached {
182 182 'servers' => [ $arg, ],
183 183 'debug' => 0,
184 184 };
185 185 $self->{RedmineMemcache} = 1;
186 186 }
187 187 }
188 188
189 189 sub RedmineMemcacheExpirySec { set_val('RedmineMemcacheExpirySec', @_); }
190 190
191 191 sub RedmineMemcacheNamespace {
192 192 my ($self, $parms, $arg) = @_;
193 193 if ($self->{RedmineMemcache}) {
194 194 # Undocumented feature of Cache::Memcached, please don't kill me
195 195 $self->{RedmineMemcached}->{namespace} = $arg;
196 196 $self->{RedmineMemcached}->{namespace_len} = length $self->{RedmineMemcached}->{namespace};
197 197 }
198 198 }
199 199
200 200 sub trim {
201 201 my $string = shift;
202 202 $string =~ s/\s{2,}/ /g;
203 203 return $string;
204 204 }
205 205
206 206 sub set_val {
207 207 my ($key, $self, $parms, $arg) = @_;
208 208 $self->{$key} = $arg;
209 209 }
210 210
211 211 Apache2::Module::add(__PACKAGE__, \@directives);
212 212
213 213
214 214 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
215 215
216 216 sub access_handler {
217 217 my $r = shift;
218 218
219 219 unless ($r->some_auth_required) {
220 220 $r->log_reason("No authentication has been configured");
221 221 return FORBIDDEN;
222 222 }
223 223
224 224 my $method = $r->method;
225 225 return OK if defined $read_only_methods{$method};
226 226
227 227 my $project_id = get_project_identifier($r);
228 228
229 229 $r->set_handlers(PerlAuthenHandler => [\&OK])
230 230 if is_public_project($project_id, $r);
231 231
232 232 return OK
233 233 }
234 234
235 235 sub authen_handler {
236 236 my $r = shift;
237 237
238 238 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
239 239 return $res unless $res == OK;
240 240
241 if (is_member($r->user, $redmine_pass, $r)) {
241 my $project_id = get_project_identifier($r);
242 if (!$project_id) {
243 return FORBIDDEN;
244 }
245 if (is_member($r->user, $redmine_pass, $r, $project_id)) {
242 246 return OK;
243 247 } else {
244 248 $r->note_auth_failure();
245 249 return AUTH_REQUIRED;
246 250 }
247 251 }
248 252
249 253 sub is_public_project {
250 254 my $project_id = shift;
251 255 my $r = shift;
252 256
253 257 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
254 258 if ($cfg->{RedmineMemcache}) {
255 259 return 1 if ($cfg->{RedmineMemcached}->get($project_id));
256 260 }
257 261 my $dbh = connect_database($r);
258 262 my $sth = $dbh->prepare(
259 263 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
260 264 );
261 265
262 266 $sth->execute($project_id);
263 267 my $ret = $sth->fetchrow_array ? 1 : 0;
264 268 $sth->finish();
265 269 $dbh->disconnect();
266 270 if ($cfg->{RedmineMemcache}) {
267 271 if ($cfg->{RedmineMemcacheExpirySec}) {
268 272 $cfg->{RedmineMemcached}->set($project_id, $ret, $cfg->{RedmineMemcacheExpirySec});
269 273 } else {
270 274 $cfg->{RedmineMemcached}->set($project_id, $ret);
271 275 }
272 276 }
273 277
274 278
275 279 $ret;
276 280 }
277 281
278 282 # perhaps we should use repository right (other read right) to check public access.
279 283 # it could be faster BUT it doesn't work for the moment.
280 284 # sub is_public_project_by_file {
281 285 # my $project_id = shift;
282 286 # my $r = shift;
283 287
284 288 # my $tree = Apache2::Directive::conftree();
285 289 # my $node = $tree->lookup('Location', $r->location);
286 290 # my $hash = $node->as_hash;
287 291
288 292 # my $svnparentpath = $hash->{SVNParentPath};
289 293 # my $repos_path = $svnparentpath . "/" . $project_id;
290 294 # return 1 if (stat($repos_path))[2] & 00007;
291 295 # }
292 296
293 297 sub is_member {
294 298 my $redmine_user = shift;
295 299 my $redmine_pass = shift;
296 300 my $r = shift;
301 my $project_id = shift;
297 302
298 303 my $dbh = connect_database($r);
299 my $project_id = get_project_identifier($r);
300 304
301 305 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
302 306
303 307 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
304 308 my $usrprojpass;
305 309 if ($cfg->{RedmineMemcache}) {
306 310 $usrprojpass = $cfg->{RedmineMemcached}->get($redmine_user.":".$project_id);
307 311 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
308 312 }
309 313 my $query = $cfg->{RedmineQuery};
310 314 my $sth = $dbh->prepare($query);
311 315 $sth->execute($redmine_user, $project_id);
312 316
313 317 my $ret;
314 318 while (my @row = $sth->fetchrow_array) {
315 319 unless ($row[1]) {
316 320 if ($row[0] eq $pass_digest) {
317 321 $ret = 1;
318 322 last;
319 323 }
320 324 } elsif ($CanUseLDAPAuth) {
321 325 my $sthldap = $dbh->prepare(
322 326 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
323 327 );
324 328 $sthldap->execute($row[1]);
325 329 while (my @rowldap = $sthldap->fetchrow_array) {
326 330 my $ldap = Authen::Simple::LDAP->new(
327 331 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
328 332 port => $rowldap[1],
329 333 basedn => $rowldap[5],
330 334 binddn => $rowldap[3] ? $rowldap[3] : "",
331 335 bindpw => $rowldap[4] ? $rowldap[4] : "",
332 336 filter => "(".$rowldap[6]."=%s)"
333 337 );
334 338 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
335 339 }
336 340 $sthldap->finish();
337 341 }
338 342 }
339 343 $sth->finish();
340 344 $dbh->disconnect();
341 345
342 346 if ($cfg->{RedmineMemcache} and $ret) {
343 347 if ($cfg->{RedmineMemcacheExpirySec}) {
344 348 $cfg->{RedmineMemcached}->set($redmine_user.":".$project_id, $pass_digest, $cfg->{RedmineMemcacheExpirySec});
345 349 } else {
346 350 $cfg->{RedmineMemcached}->set($redmine_user.":".$project_id, $pass_digest);
347 351 }
348 352 }
349 353
350 354 $ret;
351 355 }
352 356
353 357 sub get_project_identifier {
354 358 my $r = shift;
355 359
356 360 my $location = $r->location;
357 361 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
358 $identifier ? $identifier : " ";
362 $identifier;
359 363 }
360 364
361 365 sub connect_database {
362 366 my $r = shift;
363 367
364 368 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
365 369 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
366 370 }
367 371
368 372 1;
General Comments 0
You need to be logged in to leave comments. Login now