Chris@0
|
1 package Apache::Authn::Redmine;
|
Chris@0
|
2
|
Chris@0
|
3 =head1 Apache::Authn::Redmine
|
Chris@0
|
4
|
Chris@0
|
5 Redmine - a mod_perl module to authenticate webdav subversion users
|
Chris@0
|
6 against redmine database
|
Chris@0
|
7
|
Chris@0
|
8 =head1 SYNOPSIS
|
Chris@0
|
9
|
Chris@0
|
10 This module allow anonymous users to browse public project and
|
Chris@0
|
11 registred users to browse and commit their project. Authentication is
|
Chris@0
|
12 done against the redmine database or the LDAP configured in redmine.
|
Chris@0
|
13
|
Chris@0
|
14 This method is far simpler than the one with pam_* and works with all
|
Chris@0
|
15 database without an hassle but you need to have apache/mod_perl on the
|
Chris@0
|
16 svn server.
|
Chris@0
|
17
|
Chris@0
|
18 =head1 INSTALLATION
|
Chris@0
|
19
|
Chris@0
|
20 For this to automagically work, you need to have a recent reposman.rb
|
Chris@0
|
21 (after r860) and if you already use reposman, read the last section to
|
Chris@0
|
22 migrate.
|
Chris@0
|
23
|
Chris@0
|
24 Sorry ruby users but you need some perl modules, at least mod_perl2,
|
Chris@0
|
25 DBI and DBD::mysql (or the DBD driver for you database as it should
|
Chris@0
|
26 work on allmost all databases).
|
Chris@0
|
27
|
Chris@0
|
28 On debian/ubuntu you must do :
|
Chris@0
|
29
|
Chris@0
|
30 aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
|
Chris@0
|
31
|
Chris@0
|
32 If your Redmine users use LDAP authentication, you will also need
|
Chris@0
|
33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
|
Chris@0
|
34
|
Chris@0
|
35 aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl
|
Chris@0
|
36
|
Chris@0
|
37 =head1 CONFIGURATION
|
Chris@0
|
38
|
Chris@0
|
39 ## This module has to be in your perl path
|
Chris@0
|
40 ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm
|
Chris@0
|
41 PerlLoadModule Apache::Authn::Redmine
|
Chris@0
|
42 <Location /svn>
|
Chris@0
|
43 DAV svn
|
Chris@0
|
44 SVNParentPath "/var/svn"
|
Chris@0
|
45
|
Chris@0
|
46 AuthType Basic
|
Chris@0
|
47 AuthName redmine
|
Chris@0
|
48 Require valid-user
|
Chris@0
|
49
|
Chris@0
|
50 PerlAccessHandler Apache::Authn::Redmine::access_handler
|
Chris@0
|
51 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
|
Chris@909
|
52
|
Chris@0
|
53 ## for mysql
|
Chris@0
|
54 RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
|
Chris@0
|
55 ## for postgres
|
Chris@0
|
56 # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server"
|
Chris@0
|
57
|
Chris@0
|
58 RedmineDbUser "redmine"
|
Chris@0
|
59 RedmineDbPass "password"
|
Chris@0
|
60 ## Optional where clause (fulltext search would be slow and
|
Chris@0
|
61 ## database dependant).
|
Chris@0
|
62 # RedmineDbWhereClause "and members.role_id IN (1,2)"
|
Chris@0
|
63 ## Optional credentials cache size
|
Chris@0
|
64 # RedmineCacheCredsMax 50
|
Chris@0
|
65 </Location>
|
Chris@0
|
66
|
Chris@0
|
67 To be able to browse repository inside redmine, you must add something
|
Chris@0
|
68 like that :
|
Chris@0
|
69
|
Chris@0
|
70 <Location /svn-private>
|
Chris@0
|
71 DAV svn
|
Chris@0
|
72 SVNParentPath "/var/svn"
|
Chris@0
|
73 Order deny,allow
|
Chris@0
|
74 Deny from all
|
Chris@0
|
75 # only allow reading orders
|
Chris@0
|
76 <Limit GET PROPFIND OPTIONS REPORT>
|
Chris@0
|
77 Allow from redmine.server.ip
|
Chris@0
|
78 </Limit>
|
Chris@0
|
79 </Location>
|
Chris@0
|
80
|
Chris@0
|
81 and you will have to use this reposman.rb command line to create repository :
|
Chris@0
|
82
|
Chris@0
|
83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
|
Chris@0
|
84
|
Chris@1115
|
85 =head1 REPOSITORIES NAMING
|
Chris@1115
|
86
|
Chris@1115
|
87 A projet repository must be named with the projet identifier. In case
|
Chris@1115
|
88 of multiple repositories for the same project, use the project identifier
|
Chris@1115
|
89 and the repository identifier separated with a dot:
|
Chris@1115
|
90
|
Chris@1115
|
91 /var/svn/foo
|
Chris@1115
|
92 /var/svn/foo.otherrepo
|
Chris@1115
|
93
|
Chris@0
|
94 =head1 MIGRATION FROM OLDER RELEASES
|
Chris@0
|
95
|
Chris@0
|
96 If you use an older reposman.rb (r860 or before), you need to change
|
Chris@0
|
97 rights on repositories to allow the apache user to read and write
|
Chris@0
|
98 S<them :>
|
Chris@0
|
99
|
Chris@0
|
100 sudo chown -R www-data /var/svn/*
|
Chris@0
|
101 sudo chmod -R u+w /var/svn/*
|
Chris@0
|
102
|
Chris@0
|
103 And you need to upgrade at least reposman.rb (after r860).
|
Chris@0
|
104
|
Chris@1115
|
105 =head1 GIT SMART HTTP SUPPORT
|
Chris@1115
|
106
|
Chris@1115
|
107 Git's smart HTTP protocol (available since Git 1.7.0) will not work with the
|
Chris@1115
|
108 above settings. Redmine.pm normally does access control depending on the HTTP
|
Chris@1115
|
109 method used: read-only methods are OK for everyone in public projects and
|
Chris@1115
|
110 members with read rights in private projects. The rest require membership with
|
Chris@1115
|
111 commit rights in the project.
|
Chris@1115
|
112
|
Chris@1115
|
113 However, this scheme doesn't work for Git's smart HTTP protocol, as it will use
|
Chris@1115
|
114 POST even for a simple clone. Instead, read-only requests must be detected using
|
Chris@1115
|
115 the full URL (including the query string): anything that doesn't belong to the
|
Chris@1115
|
116 git-receive-pack service is read-only.
|
Chris@1115
|
117
|
Chris@1115
|
118 To activate this mode of operation, add this line inside your <Location /git>
|
Chris@1115
|
119 block:
|
Chris@1115
|
120
|
Chris@1115
|
121 RedmineGitSmartHttp yes
|
Chris@1115
|
122
|
Chris@1115
|
123 Here's a sample Apache configuration which integrates git-http-backend with
|
Chris@1115
|
124 a MySQL database and this new option:
|
Chris@1115
|
125
|
Chris@1115
|
126 SetEnv GIT_PROJECT_ROOT /var/www/git/
|
Chris@1115
|
127 SetEnv GIT_HTTP_EXPORT_ALL
|
Chris@1115
|
128 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
|
Chris@1115
|
129 <Location /git>
|
Chris@1115
|
130 Order allow,deny
|
Chris@1115
|
131 Allow from all
|
Chris@1115
|
132
|
Chris@1115
|
133 AuthType Basic
|
Chris@1115
|
134 AuthName Git
|
Chris@1115
|
135 Require valid-user
|
Chris@1115
|
136
|
Chris@1115
|
137 PerlAccessHandler Apache::Authn::Redmine::access_handler
|
Chris@1115
|
138 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
|
Chris@1115
|
139 # for mysql
|
Chris@1115
|
140 RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
|
Chris@1115
|
141 RedmineDbUser "redmine"
|
Chris@1115
|
142 RedmineDbPass "xxx"
|
Chris@1115
|
143 RedmineGitSmartHttp yes
|
Chris@1115
|
144 </Location>
|
Chris@1115
|
145
|
Chris@1115
|
146 Make sure that all the names of the repositories under /var/www/git/ have a
|
Chris@1115
|
147 matching identifier for some project: /var/www/git/myproject and
|
Chris@1115
|
148 /var/www/git/myproject.git will work. You can put both bare and non-bare
|
Chris@1115
|
149 repositories in /var/www/git, though bare repositories are strongly
|
Chris@1115
|
150 recommended. You should create them with the rights of the user running Redmine,
|
Chris@1115
|
151 like this:
|
Chris@1115
|
152
|
Chris@1115
|
153 cd /var/www/git
|
Chris@1115
|
154 sudo -u user-running-redmine mkdir myproject
|
Chris@1115
|
155 cd myproject
|
Chris@1115
|
156 sudo -u user-running-redmine git init --bare
|
Chris@1115
|
157
|
Chris@1115
|
158 Once you have activated this option, you have three options when cloning a
|
Chris@1115
|
159 repository:
|
Chris@1115
|
160
|
Chris@1115
|
161 - Cloning using "http://user@host/git/repo(.git)" works, but will ask for the password
|
Chris@1115
|
162 all the time.
|
Chris@1115
|
163
|
Chris@1115
|
164 - Cloning with "http://user:pass@host/git/repo(.git)" does not have this problem, but
|
Chris@1115
|
165 this could reveal accidentally your password to the console in some versions
|
Chris@1115
|
166 of Git, and you would have to ensure that .git/config is not readable except
|
Chris@1115
|
167 by the owner for each of your projects.
|
Chris@1115
|
168
|
Chris@1115
|
169 - Use "http://host/git/repo(.git)", and store your credentials in the ~/.netrc
|
Chris@1115
|
170 file. This is the recommended solution, as you only have one file to protect
|
Chris@1115
|
171 and passwords will not be leaked accidentally to the console.
|
Chris@1115
|
172
|
Chris@1115
|
173 IMPORTANT NOTE: It is *very important* that the file cannot be read by other
|
Chris@1115
|
174 users, as it will contain your password in cleartext. To create the file, you
|
Chris@1115
|
175 can use the following commands, replacing yourhost, youruser and yourpassword
|
Chris@1115
|
176 with the right values:
|
Chris@1115
|
177
|
Chris@1115
|
178 touch ~/.netrc
|
Chris@1115
|
179 chmod 600 ~/.netrc
|
Chris@1115
|
180 echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc
|
Chris@1115
|
181
|
Chris@0
|
182 =cut
|
Chris@0
|
183
|
Chris@0
|
184 use strict;
|
Chris@0
|
185 use warnings FATAL => 'all', NONFATAL => 'redefine';
|
Chris@0
|
186
|
Chris@0
|
187 use DBI;
|
Chris@929
|
188 use Digest::SHA;
|
Chris@0
|
189 # optional module for LDAP authentication
|
Chris@0
|
190 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
|
Chris@0
|
191
|
Chris@0
|
192 use Apache2::Module;
|
Chris@0
|
193 use Apache2::Access;
|
Chris@0
|
194 use Apache2::ServerRec qw();
|
Chris@0
|
195 use Apache2::RequestRec qw();
|
Chris@0
|
196 use Apache2::RequestUtil qw();
|
Chris@0
|
197 use Apache2::Const qw(:common :override :cmd_how);
|
Chris@0
|
198 use APR::Pool ();
|
Chris@0
|
199 use APR::Table ();
|
Chris@0
|
200
|
Chris@0
|
201 # use Apache2::Directive qw();
|
Chris@0
|
202
|
Chris@0
|
203 my @directives = (
|
Chris@0
|
204 {
|
Chris@0
|
205 name => 'RedmineDSN',
|
Chris@0
|
206 req_override => OR_AUTHCFG,
|
Chris@0
|
207 args_how => TAKE1,
|
Chris@0
|
208 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
|
Chris@0
|
209 },
|
Chris@0
|
210 {
|
Chris@0
|
211 name => 'RedmineDbUser',
|
Chris@0
|
212 req_override => OR_AUTHCFG,
|
Chris@0
|
213 args_how => TAKE1,
|
Chris@0
|
214 },
|
Chris@0
|
215 {
|
Chris@0
|
216 name => 'RedmineDbPass',
|
Chris@0
|
217 req_override => OR_AUTHCFG,
|
Chris@0
|
218 args_how => TAKE1,
|
Chris@0
|
219 },
|
Chris@0
|
220 {
|
Chris@0
|
221 name => 'RedmineDbWhereClause',
|
Chris@0
|
222 req_override => OR_AUTHCFG,
|
Chris@0
|
223 args_how => TAKE1,
|
Chris@0
|
224 },
|
Chris@0
|
225 {
|
Chris@0
|
226 name => 'RedmineCacheCredsMax',
|
Chris@0
|
227 req_override => OR_AUTHCFG,
|
Chris@0
|
228 args_how => TAKE1,
|
Chris@0
|
229 errmsg => 'RedmineCacheCredsMax must be decimal number',
|
Chris@0
|
230 },
|
Chris@1115
|
231 {
|
Chris@1115
|
232 name => 'RedmineGitSmartHttp',
|
Chris@1115
|
233 req_override => OR_AUTHCFG,
|
Chris@1115
|
234 args_how => TAKE1,
|
Chris@1115
|
235 },
|
Chris@0
|
236 );
|
Chris@0
|
237
|
Chris@909
|
238 sub RedmineDSN {
|
Chris@0
|
239 my ($self, $parms, $arg) = @_;
|
Chris@0
|
240 $self->{RedmineDSN} = $arg;
|
Chris@0
|
241 my $query = "SELECT
|
Chris@1115
|
242 users.hashed_password, users.salt, users.auth_source_id, roles.permissions, projects.status
|
Chris@909
|
243 FROM projects, users, roles
|
Chris@0
|
244 WHERE
|
Chris@909
|
245 users.login=?
|
Chris@909
|
246 AND projects.identifier=?
|
Chris@0
|
247 AND users.status=1
|
Chris@909
|
248 AND (
|
Chris@909
|
249 roles.id IN (SELECT member_roles.role_id FROM members, member_roles WHERE members.user_id = users.id AND members.project_id = projects.id AND members.id = member_roles.member_id)
|
Chris@909
|
250 OR
|
Chris@909
|
251 (roles.builtin=1 AND cast(projects.is_public as CHAR) IN ('t', '1'))
|
Chris@1115
|
252 )
|
Chris@1115
|
253 AND roles.permissions IS NOT NULL";
|
Chris@0
|
254 $self->{RedmineQuery} = trim($query);
|
Chris@0
|
255 }
|
Chris@0
|
256
|
Chris@0
|
257 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
|
Chris@0
|
258 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
|
Chris@909
|
259 sub RedmineDbWhereClause {
|
Chris@0
|
260 my ($self, $parms, $arg) = @_;
|
Chris@0
|
261 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
|
Chris@0
|
262 }
|
Chris@0
|
263
|
Chris@909
|
264 sub RedmineCacheCredsMax {
|
Chris@0
|
265 my ($self, $parms, $arg) = @_;
|
Chris@0
|
266 if ($arg) {
|
Chris@0
|
267 $self->{RedmineCachePool} = APR::Pool->new;
|
Chris@0
|
268 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
|
Chris@0
|
269 $self->{RedmineCacheCredsCount} = 0;
|
Chris@0
|
270 $self->{RedmineCacheCredsMax} = $arg;
|
Chris@0
|
271 }
|
Chris@0
|
272 }
|
Chris@0
|
273
|
Chris@1115
|
274 sub RedmineGitSmartHttp {
|
Chris@1115
|
275 my ($self, $parms, $arg) = @_;
|
Chris@1115
|
276 $arg = lc $arg;
|
Chris@1115
|
277
|
Chris@1115
|
278 if ($arg eq "yes" || $arg eq "true") {
|
Chris@1115
|
279 $self->{RedmineGitSmartHttp} = 1;
|
Chris@1115
|
280 } else {
|
Chris@1115
|
281 $self->{RedmineGitSmartHttp} = 0;
|
Chris@1115
|
282 }
|
Chris@1115
|
283 }
|
Chris@1115
|
284
|
Chris@0
|
285 sub trim {
|
Chris@0
|
286 my $string = shift;
|
Chris@0
|
287 $string =~ s/\s{2,}/ /g;
|
Chris@0
|
288 return $string;
|
Chris@0
|
289 }
|
Chris@0
|
290
|
Chris@0
|
291 sub set_val {
|
Chris@0
|
292 my ($key, $self, $parms, $arg) = @_;
|
Chris@0
|
293 $self->{$key} = $arg;
|
Chris@0
|
294 }
|
Chris@0
|
295
|
Chris@0
|
296 Apache2::Module::add(__PACKAGE__, \@directives);
|
Chris@0
|
297
|
Chris@0
|
298
|
Chris@1115
|
299 my %read_only_methods = map { $_ => 1 } qw/GET HEAD PROPFIND REPORT OPTIONS/;
|
Chris@1115
|
300
|
Chris@1115
|
301 sub request_is_read_only {
|
Chris@1115
|
302 my ($r) = @_;
|
Chris@1115
|
303 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
|
Chris@1115
|
304
|
Chris@1115
|
305 # Do we use Git's smart HTTP protocol, or not?
|
Chris@1115
|
306 if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
|
Chris@1115
|
307 my $uri = $r->unparsed_uri;
|
Chris@1115
|
308 my $location = $r->location;
|
Chris@1115
|
309 my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
|
Chris@1115
|
310 return $is_read_only;
|
Chris@1115
|
311 } else {
|
Chris@1115
|
312 # Standard behaviour: check the HTTP method
|
Chris@1115
|
313 my $method = $r->method;
|
Chris@1115
|
314 return defined $read_only_methods{$method};
|
Chris@1115
|
315 }
|
Chris@1115
|
316 }
|
Chris@0
|
317
|
Chris@0
|
318 sub access_handler {
|
Chris@0
|
319 my $r = shift;
|
Chris@0
|
320
|
Chris@0
|
321 unless ($r->some_auth_required) {
|
Chris@0
|
322 $r->log_reason("No authentication has been configured");
|
Chris@0
|
323 return FORBIDDEN;
|
Chris@0
|
324 }
|
Chris@0
|
325
|
Chris@1115
|
326 return OK unless request_is_read_only($r);
|
Chris@0
|
327
|
Chris@0
|
328 my $project_id = get_project_identifier($r);
|
Chris@0
|
329
|
Chris@0
|
330 $r->set_handlers(PerlAuthenHandler => [\&OK])
|
Chris@909
|
331 if is_public_project($project_id, $r) && anonymous_role_allows_browse_repository($r);
|
Chris@0
|
332
|
Chris@0
|
333 return OK
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 sub authen_handler {
|
Chris@0
|
337 my $r = shift;
|
Chris@909
|
338
|
Chris@0
|
339 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
|
Chris@0
|
340 return $res unless $res == OK;
|
Chris@909
|
341
|
Chris@0
|
342 if (is_member($r->user, $redmine_pass, $r)) {
|
Chris@0
|
343 return OK;
|
Chris@0
|
344 } else {
|
Chris@0
|
345 $r->note_auth_failure();
|
Chris@1115
|
346 return DECLINED;
|
Chris@0
|
347 }
|
Chris@0
|
348 }
|
Chris@0
|
349
|
Chris@0
|
350 # check if authentication is forced
|
Chris@0
|
351 sub is_authentication_forced {
|
Chris@0
|
352 my $r = shift;
|
Chris@0
|
353
|
Chris@0
|
354 my $dbh = connect_database($r);
|
Chris@0
|
355 my $sth = $dbh->prepare(
|
Chris@0
|
356 "SELECT value FROM settings where settings.name = 'login_required';"
|
Chris@0
|
357 );
|
Chris@0
|
358
|
Chris@0
|
359 $sth->execute();
|
Chris@0
|
360 my $ret = 0;
|
Chris@0
|
361 if (my @row = $sth->fetchrow_array) {
|
Chris@0
|
362 if ($row[0] eq "1" || $row[0] eq "t") {
|
Chris@0
|
363 $ret = 1;
|
Chris@0
|
364 }
|
Chris@0
|
365 }
|
Chris@0
|
366 $sth->finish();
|
Chris@0
|
367 undef $sth;
|
Chris@909
|
368
|
Chris@0
|
369 $dbh->disconnect();
|
Chris@0
|
370 undef $dbh;
|
Chris@0
|
371
|
Chris@0
|
372 $ret;
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 sub is_public_project {
|
Chris@0
|
376 my $project_id = shift;
|
Chris@0
|
377 my $r = shift;
|
Chris@909
|
378
|
Chris@0
|
379 if (is_authentication_forced($r)) {
|
Chris@0
|
380 return 0;
|
Chris@0
|
381 }
|
Chris@0
|
382
|
Chris@0
|
383 my $dbh = connect_database($r);
|
Chris@0
|
384 my $sth = $dbh->prepare(
|
Chris@1115
|
385 "SELECT is_public FROM projects WHERE projects.identifier = ? AND projects.status <> 9;"
|
Chris@0
|
386 );
|
Chris@0
|
387
|
Chris@0
|
388 $sth->execute($project_id);
|
Chris@0
|
389 my $ret = 0;
|
Chris@0
|
390 if (my @row = $sth->fetchrow_array) {
|
Chris@1464
|
391 if ($row[0] eq "1" || $row[0] eq "t") {
|
Chris@1464
|
392 $ret = 1;
|
Chris@1464
|
393 }
|
Chris@0
|
394 }
|
Chris@0
|
395 $sth->finish();
|
Chris@0
|
396 undef $sth;
|
Chris@0
|
397 $dbh->disconnect();
|
Chris@0
|
398 undef $dbh;
|
Chris@0
|
399
|
Chris@0
|
400 $ret;
|
Chris@0
|
401 }
|
Chris@0
|
402
|
Chris@909
|
403 sub anonymous_role_allows_browse_repository {
|
Chris@909
|
404 my $r = shift;
|
Chris@909
|
405
|
Chris@909
|
406 my $dbh = connect_database($r);
|
Chris@909
|
407 my $sth = $dbh->prepare(
|
Chris@909
|
408 "SELECT permissions FROM roles WHERE builtin = 2;"
|
Chris@909
|
409 );
|
Chris@909
|
410
|
Chris@909
|
411 $sth->execute();
|
Chris@909
|
412 my $ret = 0;
|
Chris@909
|
413 if (my @row = $sth->fetchrow_array) {
|
Chris@909
|
414 if ($row[0] =~ /:browse_repository/) {
|
Chris@909
|
415 $ret = 1;
|
Chris@909
|
416 }
|
Chris@909
|
417 }
|
Chris@909
|
418 $sth->finish();
|
Chris@909
|
419 undef $sth;
|
Chris@909
|
420 $dbh->disconnect();
|
Chris@909
|
421 undef $dbh;
|
Chris@909
|
422
|
Chris@909
|
423 $ret;
|
Chris@909
|
424 }
|
Chris@909
|
425
|
Chris@0
|
426 # perhaps we should use repository right (other read right) to check public access.
|
Chris@0
|
427 # it could be faster BUT it doesn't work for the moment.
|
Chris@0
|
428 # sub is_public_project_by_file {
|
Chris@0
|
429 # my $project_id = shift;
|
Chris@0
|
430 # my $r = shift;
|
Chris@0
|
431
|
Chris@0
|
432 # my $tree = Apache2::Directive::conftree();
|
Chris@0
|
433 # my $node = $tree->lookup('Location', $r->location);
|
Chris@0
|
434 # my $hash = $node->as_hash;
|
Chris@0
|
435
|
Chris@0
|
436 # my $svnparentpath = $hash->{SVNParentPath};
|
Chris@0
|
437 # my $repos_path = $svnparentpath . "/" . $project_id;
|
Chris@0
|
438 # return 1 if (stat($repos_path))[2] & 00007;
|
Chris@0
|
439 # }
|
Chris@0
|
440
|
Chris@0
|
441 sub is_member {
|
Chris@0
|
442 my $redmine_user = shift;
|
Chris@0
|
443 my $redmine_pass = shift;
|
Chris@0
|
444 my $r = shift;
|
Chris@0
|
445
|
Chris@0
|
446 my $dbh = connect_database($r);
|
Chris@0
|
447 my $project_id = get_project_identifier($r);
|
Chris@0
|
448
|
Chris@929
|
449 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
|
Chris@0
|
450
|
Chris@1115
|
451 my $access_mode = request_is_read_only($r) ? "R" : "W";
|
Chris@909
|
452
|
Chris@0
|
453 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
|
Chris@0
|
454 my $usrprojpass;
|
Chris@0
|
455 if ($cfg->{RedmineCacheCredsMax}) {
|
Chris@909
|
456 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
|
Chris@0
|
457 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
|
Chris@0
|
458 }
|
Chris@0
|
459 my $query = $cfg->{RedmineQuery};
|
Chris@0
|
460 my $sth = $dbh->prepare($query);
|
Chris@0
|
461 $sth->execute($redmine_user, $project_id);
|
Chris@0
|
462
|
Chris@0
|
463 my $ret;
|
Chris@1115
|
464 while (my ($hashed_password, $salt, $auth_source_id, $permissions, $project_status) = $sth->fetchrow_array) {
|
Chris@1115
|
465 if ($project_status eq "9" || ($project_status ne "1" && $access_mode eq "W")) {
|
Chris@1115
|
466 last;
|
Chris@1115
|
467 }
|
Chris@0
|
468
|
Chris@0
|
469 unless ($auth_source_id) {
|
Chris@1464
|
470 my $method = $r->method;
|
Chris@929
|
471 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
|
Chris@1464
|
472 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
|
Chris@0
|
473 $ret = 1;
|
Chris@0
|
474 last;
|
Chris@0
|
475 }
|
Chris@0
|
476 } elsif ($CanUseLDAPAuth) {
|
Chris@0
|
477 my $sthldap = $dbh->prepare(
|
Chris@0
|
478 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
|
Chris@0
|
479 );
|
Chris@0
|
480 $sthldap->execute($auth_source_id);
|
Chris@0
|
481 while (my @rowldap = $sthldap->fetchrow_array) {
|
Chris@1115
|
482 my $bind_as = $rowldap[3] ? $rowldap[3] : "";
|
Chris@1115
|
483 my $bind_pw = $rowldap[4] ? $rowldap[4] : "";
|
Chris@1115
|
484 if ($bind_as =~ m/\$login/) {
|
Chris@1115
|
485 # replace $login with $redmine_user and use $redmine_pass
|
Chris@1115
|
486 $bind_as =~ s/\$login/$redmine_user/g;
|
Chris@1115
|
487 $bind_pw = $redmine_pass
|
Chris@1115
|
488 }
|
Chris@0
|
489 my $ldap = Authen::Simple::LDAP->new(
|
chris@37
|
490 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
|
Chris@0
|
491 port => $rowldap[1],
|
Chris@0
|
492 basedn => $rowldap[5],
|
Chris@1115
|
493 binddn => $bind_as,
|
Chris@1115
|
494 bindpw => $bind_pw,
|
Chris@0
|
495 filter => "(".$rowldap[6]."=%s)"
|
Chris@0
|
496 );
|
Chris@0
|
497 my $method = $r->method;
|
Chris@909
|
498 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
|
Chris@0
|
499
|
Chris@0
|
500 }
|
Chris@0
|
501 $sthldap->finish();
|
Chris@0
|
502 undef $sthldap;
|
Chris@0
|
503 }
|
Chris@0
|
504 }
|
Chris@0
|
505 $sth->finish();
|
Chris@0
|
506 undef $sth;
|
Chris@0
|
507 $dbh->disconnect();
|
Chris@0
|
508 undef $dbh;
|
Chris@0
|
509
|
Chris@0
|
510 if ($cfg->{RedmineCacheCredsMax} and $ret) {
|
Chris@0
|
511 if (defined $usrprojpass) {
|
Chris@909
|
512 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
|
Chris@0
|
513 } else {
|
Chris@0
|
514 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
|
Chris@909
|
515 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
|
Chris@0
|
516 $cfg->{RedmineCacheCredsCount}++;
|
Chris@0
|
517 } else {
|
Chris@0
|
518 $cfg->{RedmineCacheCreds}->clear();
|
Chris@0
|
519 $cfg->{RedmineCacheCredsCount} = 0;
|
Chris@0
|
520 }
|
Chris@0
|
521 }
|
Chris@0
|
522 }
|
Chris@0
|
523
|
Chris@0
|
524 $ret;
|
Chris@0
|
525 }
|
Chris@0
|
526
|
Chris@0
|
527 sub get_project_identifier {
|
Chris@0
|
528 my $r = shift;
|
Chris@909
|
529
|
Chris@1115
|
530 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
|
Chris@0
|
531 my $location = $r->location;
|
Chris@1115
|
532 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
|
Chris@1115
|
533 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
|
Chris@0
|
534 $identifier;
|
Chris@0
|
535 }
|
Chris@0
|
536
|
Chris@0
|
537 sub connect_database {
|
Chris@0
|
538 my $r = shift;
|
Chris@909
|
539
|
Chris@0
|
540 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
|
Chris@0
|
541 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
|
Chris@0
|
542 }
|
Chris@0
|
543
|
Chris@0
|
544 1;
|