##// END OF EJS Templates
Remove default Memcached namespace....
Liwiusz Ociepa -
r1631:f6b5632fec94
parent child
Show More
@@ -1,374 +1,368
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 # RedmineMemcacheExpirySec "12"
66 # # Defaults to "RedminePM:"
65 # RedmineMemcacheExpirySec "60"
67 66 # RedmineMemcacheNamespace "RedmineCreds:"
68 67 </Location>
69 68
70 69 To be able to browse repository inside redmine, you must add something
71 70 like that :
72 71
73 72 <Location /svn-private>
74 73 DAV svn
75 74 SVNParentPath "/var/svn"
76 75 Order deny,allow
77 76 Deny from all
78 77 # only allow reading orders
79 78 <Limit GET PROPFIND OPTIONS REPORT>
80 79 Allow from redmine.server.ip
81 80 </Limit>
82 81 </Location>
83 82
84 83 and you will have to use this reposman.rb command line to create repository :
85 84
86 85 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
87 86
88 87 =head1 MIGRATION FROM OLDER RELEASES
89 88
90 89 If you use an older reposman.rb (r860 or before), you need to change
91 90 rights on repositories to allow the apache user to read and write
92 91 S<them :>
93 92
94 93 sudo chown -R www-data /var/svn/*
95 94 sudo chmod -R u+w /var/svn/*
96 95
97 96 And you need to upgrade at least reposman.rb (after r860).
98 97
99 98 =cut
100 99
101 100 use strict;
102 101 use warnings FATAL => 'all', NONFATAL => 'redefine';
103 102
104 103 use DBI;
105 104 use Digest::SHA1;
106 105 # optional module for LDAP authentication
107 106 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
108 107 my $CanUseMemcached = eval("use Cache::Memcached; 1");
109 108
110 109 use Apache2::Module;
111 110 use Apache2::Access;
112 111 use Apache2::ServerRec qw();
113 112 use Apache2::RequestRec qw();
114 113 use Apache2::RequestUtil qw();
115 114 use Apache2::Const qw(:common :override :cmd_how);
116 115
117 116 # use Apache2::Directive qw();
118 117
119 118 my @directives = (
120 119 {
121 120 name => 'RedmineDSN',
122 121 req_override => OR_AUTHCFG,
123 122 args_how => TAKE1,
124 123 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
125 124 },
126 125 {
127 126 name => 'RedmineDbUser',
128 127 req_override => OR_AUTHCFG,
129 128 args_how => TAKE1,
130 129 },
131 130 {
132 131 name => 'RedmineDbPass',
133 132 req_override => OR_AUTHCFG,
134 133 args_how => TAKE1,
135 134 },
136 135 {
137 136 name => 'RedmineDbWhereClause',
138 137 req_override => OR_AUTHCFG,
139 138 args_how => TAKE1,
140 139 },
141 140 {
142 141 name => 'RedmineMemcacheServers',
143 142 req_override => OR_AUTHCFG,
144 143 args_how => TAKE1,
145 144 },
146 145 {
147 146 name => 'RedmineMemcacheExpirySec',
148 147 req_override => OR_AUTHCFG,
149 148 args_how => TAKE1,
150 149 },
151 150 {
152 151 name => 'RedmineMemcacheNamespace',
153 152 req_override => OR_AUTHCFG,
154 153 args_how => TAKE1,
155 154 },
156 155 );
157 156
158 157 sub RedmineDSN {
159 158 my ($self, $parms, $arg) = @_;
160 159 $self->{RedmineDSN} = $arg;
161 160 my $query = "SELECT
162 161 hashed_password, auth_source_id
163 162 FROM members, projects, users
164 163 WHERE
165 164 projects.id=members.project_id
166 165 AND users.id=members.user_id
167 166 AND users.status=1
168 167 AND login=?
169 168 AND identifier=? ";
170 169 $self->{RedmineQuery} = trim($query);
171 170 }
172 171 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
173 172 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
174 173 sub RedmineDbWhereClause {
175 174 my ($self, $parms, $arg) = @_;
176 175 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
177 176 }
178 177
179 178 sub RedmineMemcacheServers {
180 179 my ($self, $parms, $arg) = @_;
181 180 if ($arg && $CanUseMemcached) {
182 181 $self->{RedmineMemcached} = new Cache::Memcached {
183 182 'servers' => [ $arg, ],
184 183 'debug' => 1,
185 184 };
186 185 $self->{RedmineMemcache} = 1;
187 # Undocumented feature of Cache::Memcached, please don't kill me
188 if (0 == length $self->{RedmineMemcached}->{namespace}) {
189 $self->{RedmineMemcached}->{namespace} = "RedminePM:";
190 $self->{RedmineMemcached}->{namespace_len} = length $self->{RedmineMemcached}->{namespace};
191 }
192 186 }
193 187 }
194 188
195 189 sub RedmineMemcacheExpirySec { set_val('RedmineMemcacheExpirySec', @_); }
196 190
197 191 sub RedmineMemcacheNamespace {
198 192 my ($self, $parms, $arg) = @_;
199 193 if ($CanUseMemcached) {
200 194 # Undocumented feature of Cache::Memcached, please don't kill me
201 195 $self->{RedmineMemcached}->{namespace} = $arg;
202 196 $self->{RedmineMemcached}->{namespace_len} = length $self->{RedmineMemcached}->{namespace};
203 197 }
204 198 }
205 199
206 200 sub trim {
207 201 my $string = shift;
208 202 $string =~ s/\s{2,}/ /g;
209 203 return $string;
210 204 }
211 205
212 206 sub set_val {
213 207 my ($key, $self, $parms, $arg) = @_;
214 208 $self->{$key} = $arg;
215 209 }
216 210
217 211 Apache2::Module::add(__PACKAGE__, \@directives);
218 212
219 213
220 214 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
221 215
222 216 sub access_handler {
223 217 my $r = shift;
224 218
225 219 unless ($r->some_auth_required) {
226 220 $r->log_reason("No authentication has been configured");
227 221 return FORBIDDEN;
228 222 }
229 223
230 224 my $method = $r->method;
231 225 return OK if defined $read_only_methods{$method};
232 226
233 227 my $project_id = get_project_identifier($r);
234 228
235 229 $r->set_handlers(PerlAuthenHandler => [\&OK])
236 230 if is_public_project($project_id, $r);
237 231
238 232 return OK
239 233 }
240 234
241 235 sub authen_handler {
242 236 my $r = shift;
243 237
244 238 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
245 239 return $res unless $res == OK;
246 240
247 241 if (is_member($r->user, $redmine_pass, $r)) {
248 242 return OK;
249 243 } else {
250 244 $r->note_auth_failure();
251 245 return AUTH_REQUIRED;
252 246 }
253 247 }
254 248
255 249 sub is_public_project {
256 250 my $project_id = shift;
257 251 my $r = shift;
258 252
259 253 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
260 254 if ($cfg->{RedmineMemcache}) {
261 255 return 1 if ($cfg->{RedmineMemcached}->get($project_id));
262 256 }
263 257 my $dbh = connect_database($r);
264 258 my $sth = $dbh->prepare(
265 259 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
266 260 );
267 261
268 262 $sth->execute($project_id);
269 263 my $ret = $sth->fetchrow_array ? 1 : 0;
270 264 $sth->finish();
271 265 $dbh->disconnect();
272 266 if ($cfg->{RedmineMemcache}) {
273 267 if ($cfg->{RedmineMemcacheExpirySec}) {
274 268 $cfg->{RedmineMemcached}->set($project_id, $ret, $cfg->{RedmineMemcacheExpirySec});
275 269 } else {
276 270 $cfg->{RedmineMemcached}->set($project_id, $ret);
277 271 }
278 272 }
279 273
280 274
281 275 $ret;
282 276 }
283 277
284 278 # perhaps we should use repository right (other read right) to check public access.
285 279 # it could be faster BUT it doesn't work for the moment.
286 280 # sub is_public_project_by_file {
287 281 # my $project_id = shift;
288 282 # my $r = shift;
289 283
290 284 # my $tree = Apache2::Directive::conftree();
291 285 # my $node = $tree->lookup('Location', $r->location);
292 286 # my $hash = $node->as_hash;
293 287
294 288 # my $svnparentpath = $hash->{SVNParentPath};
295 289 # my $repos_path = $svnparentpath . "/" . $project_id;
296 290 # return 1 if (stat($repos_path))[2] & 00007;
297 291 # }
298 292
299 293 sub is_member {
300 294 my $redmine_user = shift;
301 295 my $redmine_pass = shift;
302 296 my $r = shift;
303 297
304 298 my $dbh = connect_database($r);
305 299 my $project_id = get_project_identifier($r);
306 300
307 301 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
308 302
309 303 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
310 304 my $usrprojpass;
311 305 if ($cfg->{RedmineMemcache}) {
312 306 $usrprojpass = $cfg->{RedmineMemcached}->get($redmine_user.":".$project_id);
313 307 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
314 308 }
315 309 my $query = $cfg->{RedmineQuery};
316 310 my $sth = $dbh->prepare($query);
317 311 $sth->execute($redmine_user, $project_id);
318 312
319 313 my $ret;
320 314 while (my @row = $sth->fetchrow_array) {
321 315 unless ($row[1]) {
322 316 if ($row[0] eq $pass_digest) {
323 317 $ret = 1;
324 318 last;
325 319 }
326 320 } elsif ($CanUseLDAPAuth) {
327 321 my $sthldap = $dbh->prepare(
328 322 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
329 323 );
330 324 $sthldap->execute($row[1]);
331 325 while (my @rowldap = $sthldap->fetchrow_array) {
332 326 my $ldap = Authen::Simple::LDAP->new(
333 327 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
334 328 port => $rowldap[1],
335 329 basedn => $rowldap[5],
336 330 binddn => $rowldap[3] ? $rowldap[3] : "",
337 331 bindpw => $rowldap[4] ? $rowldap[4] : "",
338 332 filter => "(".$rowldap[6]."=%s)"
339 333 );
340 334 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
341 335 }
342 336 $sthldap->finish();
343 337 }
344 338 }
345 339 $sth->finish();
346 340 $dbh->disconnect();
347 341
348 342 if ($cfg->{RedmineMemcache} and $ret) {
349 343 if ($cfg->{RedmineMemcacheExpirySec}) {
350 344 $cfg->{RedmineMemcached}->set($redmine_user.":".$project_id, $pass_digest, $cfg->{RedmineMemcacheExpirySec});
351 345 } else {
352 346 $cfg->{RedmineMemcached}->set($redmine_user.":".$project_id, $pass_digest);
353 347 }
354 348 }
355 349
356 350 $ret;
357 351 }
358 352
359 353 sub get_project_identifier {
360 354 my $r = shift;
361 355
362 356 my $location = $r->location;
363 357 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
364 358 $identifier;
365 359 }
366 360
367 361 sub connect_database {
368 362 my $r = shift;
369 363
370 364 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
371 365 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
372 366 }
373 367
374 368 1;
General Comments 0
You need to be logged in to leave comments. Login now