##// END OF EJS Templates
Fixes Redmine.pm broken by r2726 (#3330)....
Jean-Philippe Lang -
r2633:bae7579a7222
parent child
Show More
@@ -1,343 +1,345
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 ## 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 143 errmsg => 'RedmineCacheCredsMax must be decimal number',
144 144 },
145 145 );
146 146
147 147 sub RedmineDSN {
148 148 my ($self, $parms, $arg) = @_;
149 149 $self->{RedmineDSN} = $arg;
150 150 my $query = "SELECT
151 151 hashed_password, auth_source_id, permissions
152 FROM members, projects, users, roles
152 FROM members, projects, users, roles, member_roles
153 153 WHERE
154 projects.id=members.project_id
154 projects.id=members.project_id
155 AND member_roles.member_id=members.id
155 156 AND users.id=members.user_id
156 AND roles.id=members.role_id
157 AND roles.id=member_roles.role_id
157 158 AND users.status=1
158 159 AND login=?
159 160 AND identifier=? ";
160 161 $self->{RedmineQuery} = trim($query);
161 162 }
163
162 164 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
163 165 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
164 166 sub RedmineDbWhereClause {
165 167 my ($self, $parms, $arg) = @_;
166 168 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
167 169 }
168 170
169 171 sub RedmineCacheCredsMax {
170 172 my ($self, $parms, $arg) = @_;
171 173 if ($arg) {
172 174 $self->{RedmineCachePool} = APR::Pool->new;
173 175 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
174 176 $self->{RedmineCacheCredsCount} = 0;
175 177 $self->{RedmineCacheCredsMax} = $arg;
176 178 }
177 179 }
178 180
179 181 sub trim {
180 182 my $string = shift;
181 183 $string =~ s/\s{2,}/ /g;
182 184 return $string;
183 185 }
184 186
185 187 sub set_val {
186 188 my ($key, $self, $parms, $arg) = @_;
187 189 $self->{$key} = $arg;
188 190 }
189 191
190 192 Apache2::Module::add(__PACKAGE__, \@directives);
191 193
192 194
193 195 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
194 196
195 197 sub access_handler {
196 198 my $r = shift;
197 199
198 200 unless ($r->some_auth_required) {
199 201 $r->log_reason("No authentication has been configured");
200 202 return FORBIDDEN;
201 203 }
202 204
203 205 my $method = $r->method;
204 206 return OK unless defined $read_only_methods{$method};
205 207
206 208 my $project_id = get_project_identifier($r);
207 209
208 210 $r->set_handlers(PerlAuthenHandler => [\&OK])
209 211 if is_public_project($project_id, $r);
210 212
211 213 return OK
212 214 }
213 215
214 216 sub authen_handler {
215 217 my $r = shift;
216 218
217 219 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
218 220 return $res unless $res == OK;
219 221
220 222 if (is_member($r->user, $redmine_pass, $r)) {
221 223 return OK;
222 224 } else {
223 225 $r->note_auth_failure();
224 226 return AUTH_REQUIRED;
225 227 }
226 228 }
227 229
228 230 sub is_public_project {
229 231 my $project_id = shift;
230 232 my $r = shift;
231 233
232 234 my $dbh = connect_database($r);
233 235 my $sth = $dbh->prepare(
234 236 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
235 237 );
236 238
237 239 $sth->execute($project_id);
238 240 my $ret = $sth->fetchrow_array ? 1 : 0;
239 241 $sth->finish();
240 242 $dbh->disconnect();
241 243
242 244 $ret;
243 245 }
244 246
245 247 # perhaps we should use repository right (other read right) to check public access.
246 248 # it could be faster BUT it doesn't work for the moment.
247 249 # sub is_public_project_by_file {
248 250 # my $project_id = shift;
249 251 # my $r = shift;
250 252
251 253 # my $tree = Apache2::Directive::conftree();
252 254 # my $node = $tree->lookup('Location', $r->location);
253 255 # my $hash = $node->as_hash;
254 256
255 257 # my $svnparentpath = $hash->{SVNParentPath};
256 258 # my $repos_path = $svnparentpath . "/" . $project_id;
257 259 # return 1 if (stat($repos_path))[2] & 00007;
258 260 # }
259 261
260 262 sub is_member {
261 263 my $redmine_user = shift;
262 264 my $redmine_pass = shift;
263 265 my $r = shift;
264 266
265 267 my $dbh = connect_database($r);
266 268 my $project_id = get_project_identifier($r);
267 269
268 270 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
269 271
270 272 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
271 273 my $usrprojpass;
272 274 if ($cfg->{RedmineCacheCredsMax}) {
273 275 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
274 276 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
275 277 }
276 278 my $query = $cfg->{RedmineQuery};
277 279 my $sth = $dbh->prepare($query);
278 280 $sth->execute($redmine_user, $project_id);
279 281
280 282 my $ret;
281 283 while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
282 284
283 285 unless ($auth_source_id) {
284 286 my $method = $r->method;
285 287 if ($hashed_password eq $pass_digest && (defined $read_only_methods{$method} || $permissions =~ /:commit_access/) ) {
286 288 $ret = 1;
287 289 last;
288 290 }
289 291 } elsif ($CanUseLDAPAuth) {
290 292 my $sthldap = $dbh->prepare(
291 293 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
292 294 );
293 295 $sthldap->execute($auth_source_id);
294 296 while (my @rowldap = $sthldap->fetchrow_array) {
295 297 my $ldap = Authen::Simple::LDAP->new(
296 298 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
297 299 port => $rowldap[1],
298 300 basedn => $rowldap[5],
299 301 binddn => $rowldap[3] ? $rowldap[3] : "",
300 302 bindpw => $rowldap[4] ? $rowldap[4] : "",
301 303 filter => "(".$rowldap[6]."=%s)"
302 304 );
303 305 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
304 306 }
305 307 $sthldap->finish();
306 308 }
307 309 }
308 310 $sth->finish();
309 311 $dbh->disconnect();
310 312
311 313 if ($cfg->{RedmineCacheCredsMax} and $ret) {
312 314 if (defined $usrprojpass) {
313 315 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
314 316 } else {
315 317 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
316 318 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
317 319 $cfg->{RedmineCacheCredsCount}++;
318 320 } else {
319 321 $cfg->{RedmineCacheCreds}->clear();
320 322 $cfg->{RedmineCacheCredsCount} = 0;
321 323 }
322 324 }
323 325 }
324 326
325 327 $ret;
326 328 }
327 329
328 330 sub get_project_identifier {
329 331 my $r = shift;
330 332
331 333 my $location = $r->location;
332 334 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
333 335 $identifier;
334 336 }
335 337
336 338 sub connect_database {
337 339 my $r = shift;
338 340
339 341 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
340 342 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
341 343 }
342 344
343 345 1;
General Comments 0
You need to be logged in to leave comments. Login now