##// END OF EJS Templates
Redmine.pm for webdav authentication:...
Jean-Philippe Lang -
r1321:2bcb7820875b
parent child
Show More
@@ -1,231 +1,235
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 on the redmine database.
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
33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used).
34
32 =head1 CONFIGURATION
35 =head1 CONFIGURATION
33
36
34 ## if the module isn't in your perl path
37 ## if the module isn't in your perl path
35 PerlRequire /usr/local/apache/Redmine.pm
38 PerlRequire /usr/local/apache/Redmine.pm
36 ## else
39 ## else
37 # PerlModule Apache::Authn::Redmine
40 # PerlModule Apache::Authn::Redmine
38 <Location /svn>
41 <Location /svn>
39 DAV svn
42 DAV svn
40 SVNParentPath "/var/svn"
43 SVNParentPath "/var/svn"
41
44
42 AuthType Basic
45 AuthType Basic
43 AuthName redmine
46 AuthName redmine
44 Require valid-user
47 Require valid-user
45
48
46 PerlAccessHandler Apache::Authn::Redmine::access_handler
49 PerlAccessHandler Apache::Authn::Redmine::access_handler
47 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
50 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
48
51
49 ## for mysql
52 ## for mysql
50 PerlSetVar dsn DBI:mysql:database=databasename;host=my.db.server
53 PerlSetVar dsn DBI:mysql:database=databasename;host=my.db.server
51 ## for postgres
54 ## for postgres
52 # PerlSetVar dsn DBI:Pg:dbname=databasename;host=my.db.server
55 # PerlSetVar dsn DBI:Pg:dbname=databasename;host=my.db.server
53
56
54 PerlSetVar db_user redmine
57 PerlSetVar db_user redmine
55 PerlSetVar db_pass password
58 PerlSetVar db_pass password
56 </Location>
59 </Location>
57
60
58 To be able to browse repository inside redmine, you must add something
61 To be able to browse repository inside redmine, you must add something
59 like that :
62 like that :
60
63
61 <Location /svn-private>
64 <Location /svn-private>
62 DAV svn
65 DAV svn
63 SVNParentPath "/var/svn"
66 SVNParentPath "/var/svn"
64 Order deny,allow
67 Order deny,allow
65 Deny from all
68 Deny from all
66 # only allow reading orders
69 # only allow reading orders
67 <Limit GET PROPFIND OPTIONS REPORT>
70 <Limit GET PROPFIND OPTIONS REPORT>
68 Allow from redmine.server.ip
71 Allow from redmine.server.ip
69 </Limit>
72 </Limit>
70 </Location>
73 </Location>
71
74
72 and you will have to use this reposman.rb command line to create repository :
75 and you will have to use this reposman.rb command line to create repository :
73
76
74 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
77 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
75
78
76 =head1 MIGRATION FROM OLDER RELEASES
79 =head1 MIGRATION FROM OLDER RELEASES
77
80
78 If you use an older reposman.rb (r860 or before), you need to change
81 If you use an older reposman.rb (r860 or before), you need to change
79 rights on repositories to allow the apache user to read and write
82 rights on repositories to allow the apache user to read and write
80 S<them :>
83 S<them :>
81
84
82 sudo chown -R www-data /var/svn/*
85 sudo chown -R www-data /var/svn/*
83 sudo chmod -R u+w /var/svn/*
86 sudo chmod -R u+w /var/svn/*
84
87
85 And you need to upgrade at least reposman.rb (after r860).
88 And you need to upgrade at least reposman.rb (after r860).
86
89
87 =cut
90 =cut
88
91
89 use strict;
92 use strict;
90
93
91 use DBI;
94 use DBI;
92 use Digest::SHA1;
95 use Digest::SHA1;
93 use Authen::Simple::LDAP;
96 # optional module for LDAP authentication
97 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
94
98
95 use Apache2::Module;
99 use Apache2::Module;
96 use Apache2::Access;
100 use Apache2::Access;
97 use Apache2::ServerRec qw();
101 use Apache2::ServerRec qw();
98 use Apache2::RequestRec qw();
102 use Apache2::RequestRec qw();
99 use Apache2::RequestUtil qw();
103 use Apache2::RequestUtil qw();
100 use Apache2::Const qw(:common);
104 use Apache2::Const qw(:common);
101 # use Apache2::Directive qw();
105 # use Apache2::Directive qw();
102
106
103 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
107 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
104
108
105 sub access_handler {
109 sub access_handler {
106 my $r = shift;
110 my $r = shift;
107
111
108 unless ($r->some_auth_required) {
112 unless ($r->some_auth_required) {
109 $r->log_reason("No authentication has been configured");
113 $r->log_reason("No authentication has been configured");
110 return FORBIDDEN;
114 return FORBIDDEN;
111 }
115 }
112
116
113 my $method = $r->method;
117 my $method = $r->method;
114 return OK unless 1 == $read_only_methods{$method};
118 return OK unless 1 == $read_only_methods{$method};
115
119
116 my $project_id = get_project_identifier($r);
120 my $project_id = get_project_identifier($r);
117
121
118 $r->set_handlers(PerlAuthenHandler => [\&OK])
122 $r->set_handlers(PerlAuthenHandler => [\&OK])
119 if is_public_project($project_id, $r);
123 if is_public_project($project_id, $r);
120
124
121 return OK
125 return OK
122 }
126 }
123
127
124 sub authen_handler {
128 sub authen_handler {
125 my $r = shift;
129 my $r = shift;
126
130
127 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
131 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
128 return $res unless $res == OK;
132 return $res unless $res == OK;
129
133
130 if (is_member($r->user, $redmine_pass, $r)) {
134 if (is_member($r->user, $redmine_pass, $r)) {
131 return OK;
135 return OK;
132 } else {
136 } else {
133 $r->note_auth_failure();
137 $r->note_auth_failure();
134 return AUTH_REQUIRED;
138 return AUTH_REQUIRED;
135 }
139 }
136 }
140 }
137
141
138 sub is_public_project {
142 sub is_public_project {
139 my $project_id = shift;
143 my $project_id = shift;
140 my $r = shift;
144 my $r = shift;
141
145
142 my $dbh = connect_database($r);
146 my $dbh = connect_database($r);
143 my $sth = $dbh->prepare(
147 my $sth = $dbh->prepare(
144 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
148 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
145 );
149 );
146
150
147 $sth->execute($project_id);
151 $sth->execute($project_id);
148 my $ret = $sth->fetchrow_array ? 1 : 0;
152 my $ret = $sth->fetchrow_array ? 1 : 0;
149 $dbh->disconnect();
153 $dbh->disconnect();
150
154
151 $ret;
155 $ret;
152 }
156 }
153
157
154 # perhaps we should use repository right (other read right) to check public access.
158 # perhaps we should use repository right (other read right) to check public access.
155 # it could be faster BUT it doesn't work for the moment.
159 # it could be faster BUT it doesn't work for the moment.
156 # sub is_public_project_by_file {
160 # sub is_public_project_by_file {
157 # my $project_id = shift;
161 # my $project_id = shift;
158 # my $r = shift;
162 # my $r = shift;
159
163
160 # my $tree = Apache2::Directive::conftree();
164 # my $tree = Apache2::Directive::conftree();
161 # my $node = $tree->lookup('Location', $r->location);
165 # my $node = $tree->lookup('Location', $r->location);
162 # my $hash = $node->as_hash;
166 # my $hash = $node->as_hash;
163
167
164 # my $svnparentpath = $hash->{SVNParentPath};
168 # my $svnparentpath = $hash->{SVNParentPath};
165 # my $repos_path = $svnparentpath . "/" . $project_id;
169 # my $repos_path = $svnparentpath . "/" . $project_id;
166 # return 1 if (stat($repos_path))[2] & 00007;
170 # return 1 if (stat($repos_path))[2] & 00007;
167 # }
171 # }
168
172
169 sub is_member {
173 sub is_member {
170 my $redmine_user = shift;
174 my $redmine_user = shift;
171 my $redmine_pass = shift;
175 my $redmine_pass = shift;
172 my $r = shift;
176 my $r = shift;
173
177
174 my $dbh = connect_database($r);
178 my $dbh = connect_database($r);
175 my $project_id = get_project_identifier($r);
179 my $project_id = get_project_identifier($r);
176
180
177 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
181 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
178
182
179 my $sth = $dbh->prepare(
183 my $sth = $dbh->prepare(
180 "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=?;"
184 "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=?;"
181 );
185 );
182 $sth->execute($redmine_user, $project_id);
186 $sth->execute($redmine_user, $project_id);
183
187
184 my $ret;
188 my $ret;
185 while (my @row = $sth->fetchrow_array) {
189 while (my @row = $sth->fetchrow_array) {
186 unless ($row[1]) {
190 unless ($row[1]) {
187 if ($row[0] eq $pass_digest) {
191 if ($row[0] eq $pass_digest) {
188 $ret = 1;
192 $ret = 1;
189 last;
193 last;
190 }
194 }
191 } else {
195 } elsif ($CanUseLDAPAuth) {
192 my $sthldap = $dbh->prepare(
196 my $sthldap = $dbh->prepare(
193 "SELECT host,port,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
197 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
194 );
198 );
195 $sthldap->execute($row[1]);
199 $sthldap->execute($row[1]);
196 while (my @rowldap = $sthldap->fetchrow_array) {
200 while (my @rowldap = $sthldap->fetchrow_array) {
197 my $ldap = Authen::Simple::LDAP->new(
201 my $ldap = Authen::Simple::LDAP->new(
198 host => $rowldap[0],
202 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
199 port => $rowldap[1],
203 port => $rowldap[1],
200 basedn => $rowldap[4],
204 basedn => $rowldap[5],
201 binddn => $rowldap[2] ? $rowldap[2] : "",
205 binddn => $rowldap[3] ? $rowldap[3] : "",
202 bindpw => $rowldap[3] ? $rowldap[3] : "",
206 bindpw => $rowldap[4] ? $rowldap[4] : "",
203 filter => "(".$rowldap[5]."=%s)"
207 filter => "(".$rowldap[6]."=%s)"
204 );
208 );
205 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
209 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
206 }
210 }
207 $sthldap->finish();
211 $sthldap->finish();
208 }
212 }
209 }
213 }
210 $sth->finish();
214 $sth->finish();
211 $dbh->disconnect();
215 $dbh->disconnect();
212
216
213 $ret;
217 $ret;
214 }
218 }
215
219
216 sub get_project_identifier {
220 sub get_project_identifier {
217 my $r = shift;
221 my $r = shift;
218
222
219 my $location = $r->location;
223 my $location = $r->location;
220 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
224 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
221 $identifier;
225 $identifier;
222 }
226 }
223
227
224 sub connect_database {
228 sub connect_database {
225 my $r = shift;
229 my $r = shift;
226
230
227 my ($dsn, $db_user, $db_pass) = map { $r->dir_config($_) } qw/dsn db_user db_pass/;
231 my ($dsn, $db_user, $db_pass) = map { $r->dir_config($_) } qw/dsn db_user db_pass/;
228 return DBI->connect($dsn, $db_user, $db_pass);
232 return DBI->connect($dsn, $db_user, $db_pass);
229 }
233 }
230
234
231 1;
235 1;
General Comments 0
You need to be logged in to leave comments. Login now