##// END OF EJS Templates
- Disable debug, ...
Liwiusz Ociepa -
r1632:81baddad00af
parent child
Show More
@@ -1,368 +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 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 'debug' => 1,
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 if ($CanUseMemcached) {
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 241 if (is_member($r->user, $redmine_pass, $r)) {
242 242 return OK;
243 243 } else {
244 244 $r->note_auth_failure();
245 245 return AUTH_REQUIRED;
246 246 }
247 247 }
248 248
249 249 sub is_public_project {
250 250 my $project_id = shift;
251 251 my $r = shift;
252 252
253 253 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
254 254 if ($cfg->{RedmineMemcache}) {
255 255 return 1 if ($cfg->{RedmineMemcached}->get($project_id));
256 256 }
257 257 my $dbh = connect_database($r);
258 258 my $sth = $dbh->prepare(
259 259 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
260 260 );
261 261
262 262 $sth->execute($project_id);
263 263 my $ret = $sth->fetchrow_array ? 1 : 0;
264 264 $sth->finish();
265 265 $dbh->disconnect();
266 266 if ($cfg->{RedmineMemcache}) {
267 267 if ($cfg->{RedmineMemcacheExpirySec}) {
268 268 $cfg->{RedmineMemcached}->set($project_id, $ret, $cfg->{RedmineMemcacheExpirySec});
269 269 } else {
270 270 $cfg->{RedmineMemcached}->set($project_id, $ret);
271 271 }
272 272 }
273 273
274 274
275 275 $ret;
276 276 }
277 277
278 278 # perhaps we should use repository right (other read right) to check public access.
279 279 # it could be faster BUT it doesn't work for the moment.
280 280 # sub is_public_project_by_file {
281 281 # my $project_id = shift;
282 282 # my $r = shift;
283 283
284 284 # my $tree = Apache2::Directive::conftree();
285 285 # my $node = $tree->lookup('Location', $r->location);
286 286 # my $hash = $node->as_hash;
287 287
288 288 # my $svnparentpath = $hash->{SVNParentPath};
289 289 # my $repos_path = $svnparentpath . "/" . $project_id;
290 290 # return 1 if (stat($repos_path))[2] & 00007;
291 291 # }
292 292
293 293 sub is_member {
294 294 my $redmine_user = shift;
295 295 my $redmine_pass = shift;
296 296 my $r = shift;
297 297
298 298 my $dbh = connect_database($r);
299 299 my $project_id = get_project_identifier($r);
300 300
301 301 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
302 302
303 303 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
304 304 my $usrprojpass;
305 305 if ($cfg->{RedmineMemcache}) {
306 306 $usrprojpass = $cfg->{RedmineMemcached}->get($redmine_user.":".$project_id);
307 307 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
308 308 }
309 309 my $query = $cfg->{RedmineQuery};
310 310 my $sth = $dbh->prepare($query);
311 311 $sth->execute($redmine_user, $project_id);
312 312
313 313 my $ret;
314 314 while (my @row = $sth->fetchrow_array) {
315 315 unless ($row[1]) {
316 316 if ($row[0] eq $pass_digest) {
317 317 $ret = 1;
318 318 last;
319 319 }
320 320 } elsif ($CanUseLDAPAuth) {
321 321 my $sthldap = $dbh->prepare(
322 322 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
323 323 );
324 324 $sthldap->execute($row[1]);
325 325 while (my @rowldap = $sthldap->fetchrow_array) {
326 326 my $ldap = Authen::Simple::LDAP->new(
327 327 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
328 328 port => $rowldap[1],
329 329 basedn => $rowldap[5],
330 330 binddn => $rowldap[3] ? $rowldap[3] : "",
331 331 bindpw => $rowldap[4] ? $rowldap[4] : "",
332 332 filter => "(".$rowldap[6]."=%s)"
333 333 );
334 334 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
335 335 }
336 336 $sthldap->finish();
337 337 }
338 338 }
339 339 $sth->finish();
340 340 $dbh->disconnect();
341 341
342 342 if ($cfg->{RedmineMemcache} and $ret) {
343 343 if ($cfg->{RedmineMemcacheExpirySec}) {
344 344 $cfg->{RedmineMemcached}->set($redmine_user.":".$project_id, $pass_digest, $cfg->{RedmineMemcacheExpirySec});
345 345 } else {
346 346 $cfg->{RedmineMemcached}->set($redmine_user.":".$project_id, $pass_digest);
347 347 }
348 348 }
349 349
350 350 $ret;
351 351 }
352 352
353 353 sub get_project_identifier {
354 354 my $r = shift;
355 355
356 356 my $location = $r->location;
357 357 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
358 358 $identifier;
359 359 }
360 360
361 361 sub connect_database {
362 362 my $r = shift;
363 363
364 364 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
365 365 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
366 366 }
367 367
368 368 1;
General Comments 0
You need to be logged in to leave comments. Login now