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