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