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