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