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