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