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