##// END OF EJS Templates
Makes Redmine.pm handle project status (#3640)....
Jean-Philippe Lang -
r9704:21ac4a3d0e07
parent child
Show More
@@ -1,540 +1,543
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 ## Optional credentials cache size
63 ## Optional credentials cache size
64 # RedmineCacheCredsMax 50
64 # RedmineCacheCredsMax 50
65 </Location>
65 </Location>
66
66
67 To be able to browse repository inside redmine, you must add something
67 To be able to browse repository inside redmine, you must add something
68 like that :
68 like that :
69
69
70 <Location /svn-private>
70 <Location /svn-private>
71 DAV svn
71 DAV svn
72 SVNParentPath "/var/svn"
72 SVNParentPath "/var/svn"
73 Order deny,allow
73 Order deny,allow
74 Deny from all
74 Deny from all
75 # only allow reading orders
75 # only allow reading orders
76 <Limit GET PROPFIND OPTIONS REPORT>
76 <Limit GET PROPFIND OPTIONS REPORT>
77 Allow from redmine.server.ip
77 Allow from redmine.server.ip
78 </Limit>
78 </Limit>
79 </Location>
79 </Location>
80
80
81 and you will have to use this reposman.rb command line to create repository :
81 and you will have to use this reposman.rb command line to create repository :
82
82
83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
84
84
85 =head1 REPOSITORIES NAMING
85 =head1 REPOSITORIES NAMING
86
86
87 A projet repository must be named with the projet identifier. In case
87 A projet repository must be named with the projet identifier. In case
88 of multiple repositories for the same project, use the project identifier
88 of multiple repositories for the same project, use the project identifier
89 and the repository identifier separated with a dot:
89 and the repository identifier separated with a dot:
90
90
91 /var/svn/foo
91 /var/svn/foo
92 /var/svn/foo.otherrepo
92 /var/svn/foo.otherrepo
93
93
94 =head1 MIGRATION FROM OLDER RELEASES
94 =head1 MIGRATION FROM OLDER RELEASES
95
95
96 If you use an older reposman.rb (r860 or before), you need to change
96 If you use an older reposman.rb (r860 or before), you need to change
97 rights on repositories to allow the apache user to read and write
97 rights on repositories to allow the apache user to read and write
98 S<them :>
98 S<them :>
99
99
100 sudo chown -R www-data /var/svn/*
100 sudo chown -R www-data /var/svn/*
101 sudo chmod -R u+w /var/svn/*
101 sudo chmod -R u+w /var/svn/*
102
102
103 And you need to upgrade at least reposman.rb (after r860).
103 And you need to upgrade at least reposman.rb (after r860).
104
104
105 =head1 GIT SMART HTTP SUPPORT
105 =head1 GIT SMART HTTP SUPPORT
106
106
107 Git's smart HTTP protocol (available since Git 1.7.0) will not work with the
107 Git's smart HTTP protocol (available since Git 1.7.0) will not work with the
108 above settings. Redmine.pm normally does access control depending on the HTTP
108 above settings. Redmine.pm normally does access control depending on the HTTP
109 method used: read-only methods are OK for everyone in public projects and
109 method used: read-only methods are OK for everyone in public projects and
110 members with read rights in private projects. The rest require membership with
110 members with read rights in private projects. The rest require membership with
111 commit rights in the project.
111 commit rights in the project.
112
112
113 However, this scheme doesn't work for Git's smart HTTP protocol, as it will use
113 However, this scheme doesn't work for Git's smart HTTP protocol, as it will use
114 POST even for a simple clone. Instead, read-only requests must be detected using
114 POST even for a simple clone. Instead, read-only requests must be detected using
115 the full URL (including the query string): anything that doesn't belong to the
115 the full URL (including the query string): anything that doesn't belong to the
116 git-receive-pack service is read-only.
116 git-receive-pack service is read-only.
117
117
118 To activate this mode of operation, add this line inside your <Location /git>
118 To activate this mode of operation, add this line inside your <Location /git>
119 block:
119 block:
120
120
121 RedmineGitSmartHttp yes
121 RedmineGitSmartHttp yes
122
122
123 Here's a sample Apache configuration which integrates git-http-backend with
123 Here's a sample Apache configuration which integrates git-http-backend with
124 a MySQL database and this new option:
124 a MySQL database and this new option:
125
125
126 SetEnv GIT_PROJECT_ROOT /var/www/git/
126 SetEnv GIT_PROJECT_ROOT /var/www/git/
127 SetEnv GIT_HTTP_EXPORT_ALL
127 SetEnv GIT_HTTP_EXPORT_ALL
128 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
128 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
129 <Location /git>
129 <Location /git>
130 Order allow,deny
130 Order allow,deny
131 Allow from all
131 Allow from all
132
132
133 AuthType Basic
133 AuthType Basic
134 AuthName Git
134 AuthName Git
135 Require valid-user
135 Require valid-user
136
136
137 PerlAccessHandler Apache::Authn::Redmine::access_handler
137 PerlAccessHandler Apache::Authn::Redmine::access_handler
138 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
138 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
139 # for mysql
139 # for mysql
140 RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
140 RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
141 RedmineDbUser "redmine"
141 RedmineDbUser "redmine"
142 RedmineDbPass "xxx"
142 RedmineDbPass "xxx"
143 RedmineGitSmartHttp yes
143 RedmineGitSmartHttp yes
144 </Location>
144 </Location>
145
145
146 Make sure that all the names of the repositories under /var/www/git/ have a
146 Make sure that all the names of the repositories under /var/www/git/ have a
147 matching identifier for some project: /var/www/git/myproject and
147 matching identifier for some project: /var/www/git/myproject and
148 /var/www/git/myproject.git will work. You can put both bare and non-bare
148 /var/www/git/myproject.git will work. You can put both bare and non-bare
149 repositories in /var/www/git, though bare repositories are strongly
149 repositories in /var/www/git, though bare repositories are strongly
150 recommended. You should create them with the rights of the user running Redmine,
150 recommended. You should create them with the rights of the user running Redmine,
151 like this:
151 like this:
152
152
153 cd /var/www/git
153 cd /var/www/git
154 sudo -u user-running-redmine mkdir myproject
154 sudo -u user-running-redmine mkdir myproject
155 cd myproject
155 cd myproject
156 sudo -u user-running-redmine git init --bare
156 sudo -u user-running-redmine git init --bare
157
157
158 Once you have activated this option, you have three options when cloning a
158 Once you have activated this option, you have three options when cloning a
159 repository:
159 repository:
160
160
161 - Cloning using "http://user@host/git/repo(.git)" works, but will ask for the password
161 - Cloning using "http://user@host/git/repo(.git)" works, but will ask for the password
162 all the time.
162 all the time.
163
163
164 - Cloning with "http://user:pass@host/git/repo(.git)" does not have this problem, but
164 - Cloning with "http://user:pass@host/git/repo(.git)" does not have this problem, but
165 this could reveal accidentally your password to the console in some versions
165 this could reveal accidentally your password to the console in some versions
166 of Git, and you would have to ensure that .git/config is not readable except
166 of Git, and you would have to ensure that .git/config is not readable except
167 by the owner for each of your projects.
167 by the owner for each of your projects.
168
168
169 - Use "http://host/git/repo(.git)", and store your credentials in the ~/.netrc
169 - Use "http://host/git/repo(.git)", and store your credentials in the ~/.netrc
170 file. This is the recommended solution, as you only have one file to protect
170 file. This is the recommended solution, as you only have one file to protect
171 and passwords will not be leaked accidentally to the console.
171 and passwords will not be leaked accidentally to the console.
172
172
173 IMPORTANT NOTE: It is *very important* that the file cannot be read by other
173 IMPORTANT NOTE: It is *very important* that the file cannot be read by other
174 users, as it will contain your password in cleartext. To create the file, you
174 users, as it will contain your password in cleartext. To create the file, you
175 can use the following commands, replacing yourhost, youruser and yourpassword
175 can use the following commands, replacing yourhost, youruser and yourpassword
176 with the right values:
176 with the right values:
177
177
178 touch ~/.netrc
178 touch ~/.netrc
179 chmod 600 ~/.netrc
179 chmod 600 ~/.netrc
180 echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc
180 echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc
181
181
182 =cut
182 =cut
183
183
184 use strict;
184 use strict;
185 use warnings FATAL => 'all', NONFATAL => 'redefine';
185 use warnings FATAL => 'all', NONFATAL => 'redefine';
186
186
187 use DBI;
187 use DBI;
188 use Digest::SHA;
188 use Digest::SHA;
189 # optional module for LDAP authentication
189 # optional module for LDAP authentication
190 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
190 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
191
191
192 use Apache2::Module;
192 use Apache2::Module;
193 use Apache2::Access;
193 use Apache2::Access;
194 use Apache2::ServerRec qw();
194 use Apache2::ServerRec qw();
195 use Apache2::RequestRec qw();
195 use Apache2::RequestRec qw();
196 use Apache2::RequestUtil qw();
196 use Apache2::RequestUtil qw();
197 use Apache2::Const qw(:common :override :cmd_how);
197 use Apache2::Const qw(:common :override :cmd_how);
198 use APR::Pool ();
198 use APR::Pool ();
199 use APR::Table ();
199 use APR::Table ();
200
200
201 # use Apache2::Directive qw();
201 # use Apache2::Directive qw();
202
202
203 my @directives = (
203 my @directives = (
204 {
204 {
205 name => 'RedmineDSN',
205 name => 'RedmineDSN',
206 req_override => OR_AUTHCFG,
206 req_override => OR_AUTHCFG,
207 args_how => TAKE1,
207 args_how => TAKE1,
208 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
208 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
209 },
209 },
210 {
210 {
211 name => 'RedmineDbUser',
211 name => 'RedmineDbUser',
212 req_override => OR_AUTHCFG,
212 req_override => OR_AUTHCFG,
213 args_how => TAKE1,
213 args_how => TAKE1,
214 },
214 },
215 {
215 {
216 name => 'RedmineDbPass',
216 name => 'RedmineDbPass',
217 req_override => OR_AUTHCFG,
217 req_override => OR_AUTHCFG,
218 args_how => TAKE1,
218 args_how => TAKE1,
219 },
219 },
220 {
220 {
221 name => 'RedmineDbWhereClause',
221 name => 'RedmineDbWhereClause',
222 req_override => OR_AUTHCFG,
222 req_override => OR_AUTHCFG,
223 args_how => TAKE1,
223 args_how => TAKE1,
224 },
224 },
225 {
225 {
226 name => 'RedmineCacheCredsMax',
226 name => 'RedmineCacheCredsMax',
227 req_override => OR_AUTHCFG,
227 req_override => OR_AUTHCFG,
228 args_how => TAKE1,
228 args_how => TAKE1,
229 errmsg => 'RedmineCacheCredsMax must be decimal number',
229 errmsg => 'RedmineCacheCredsMax must be decimal number',
230 },
230 },
231 {
231 {
232 name => 'RedmineGitSmartHttp',
232 name => 'RedmineGitSmartHttp',
233 req_override => OR_AUTHCFG,
233 req_override => OR_AUTHCFG,
234 args_how => TAKE1,
234 args_how => TAKE1,
235 },
235 },
236 );
236 );
237
237
238 sub RedmineDSN {
238 sub RedmineDSN {
239 my ($self, $parms, $arg) = @_;
239 my ($self, $parms, $arg) = @_;
240 $self->{RedmineDSN} = $arg;
240 $self->{RedmineDSN} = $arg;
241 my $query = "SELECT
241 my $query = "SELECT
242 hashed_password, salt, auth_source_id, permissions
242 users.hashed_password, users.salt, users.auth_source_id, roles.permissions, projects.status
243 FROM projects, users, roles
243 FROM projects, users, roles
244 WHERE
244 WHERE
245 users.login=?
245 users.login=?
246 AND projects.identifier=?
246 AND projects.identifier=?
247 AND users.status=1
247 AND users.status=1
248 AND (
248 AND (
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)
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)
250 OR
250 OR
251 (roles.builtin=1 AND cast(projects.is_public as CHAR) IN ('t', '1'))
251 (roles.builtin=1 AND cast(projects.is_public as CHAR) IN ('t', '1'))
252 ) ";
252 ) ";
253 $self->{RedmineQuery} = trim($query);
253 $self->{RedmineQuery} = trim($query);
254 }
254 }
255
255
256 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
256 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
257 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
257 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
258 sub RedmineDbWhereClause {
258 sub RedmineDbWhereClause {
259 my ($self, $parms, $arg) = @_;
259 my ($self, $parms, $arg) = @_;
260 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
260 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
261 }
261 }
262
262
263 sub RedmineCacheCredsMax {
263 sub RedmineCacheCredsMax {
264 my ($self, $parms, $arg) = @_;
264 my ($self, $parms, $arg) = @_;
265 if ($arg) {
265 if ($arg) {
266 $self->{RedmineCachePool} = APR::Pool->new;
266 $self->{RedmineCachePool} = APR::Pool->new;
267 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
267 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
268 $self->{RedmineCacheCredsCount} = 0;
268 $self->{RedmineCacheCredsCount} = 0;
269 $self->{RedmineCacheCredsMax} = $arg;
269 $self->{RedmineCacheCredsMax} = $arg;
270 }
270 }
271 }
271 }
272
272
273 sub RedmineGitSmartHttp {
273 sub RedmineGitSmartHttp {
274 my ($self, $parms, $arg) = @_;
274 my ($self, $parms, $arg) = @_;
275 $arg = lc $arg;
275 $arg = lc $arg;
276
276
277 if ($arg eq "yes" || $arg eq "true") {
277 if ($arg eq "yes" || $arg eq "true") {
278 $self->{RedmineGitSmartHttp} = 1;
278 $self->{RedmineGitSmartHttp} = 1;
279 } else {
279 } else {
280 $self->{RedmineGitSmartHttp} = 0;
280 $self->{RedmineGitSmartHttp} = 0;
281 }
281 }
282 }
282 }
283
283
284 sub trim {
284 sub trim {
285 my $string = shift;
285 my $string = shift;
286 $string =~ s/\s{2,}/ /g;
286 $string =~ s/\s{2,}/ /g;
287 return $string;
287 return $string;
288 }
288 }
289
289
290 sub set_val {
290 sub set_val {
291 my ($key, $self, $parms, $arg) = @_;
291 my ($key, $self, $parms, $arg) = @_;
292 $self->{$key} = $arg;
292 $self->{$key} = $arg;
293 }
293 }
294
294
295 Apache2::Module::add(__PACKAGE__, \@directives);
295 Apache2::Module::add(__PACKAGE__, \@directives);
296
296
297
297
298 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
298 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
299
299
300 sub request_is_read_only {
300 sub request_is_read_only {
301 my ($r) = @_;
301 my ($r) = @_;
302 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
302 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
303
303
304 # Do we use Git's smart HTTP protocol, or not?
304 # Do we use Git's smart HTTP protocol, or not?
305 if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
305 if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
306 my $uri = $r->unparsed_uri;
306 my $uri = $r->unparsed_uri;
307 my $location = $r->location;
307 my $location = $r->location;
308 my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
308 my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
309 return $is_read_only;
309 return $is_read_only;
310 } else {
310 } else {
311 # Standard behaviour: check the HTTP method
311 # Standard behaviour: check the HTTP method
312 my $method = $r->method;
312 my $method = $r->method;
313 return defined $read_only_methods{$method};
313 return defined $read_only_methods{$method};
314 }
314 }
315 }
315 }
316
316
317 sub access_handler {
317 sub access_handler {
318 my $r = shift;
318 my $r = shift;
319
319
320 unless ($r->some_auth_required) {
320 unless ($r->some_auth_required) {
321 $r->log_reason("No authentication has been configured");
321 $r->log_reason("No authentication has been configured");
322 return FORBIDDEN;
322 return FORBIDDEN;
323 }
323 }
324
324
325 return OK unless request_is_read_only($r);
325 return OK unless request_is_read_only($r);
326
326
327 my $project_id = get_project_identifier($r);
327 my $project_id = get_project_identifier($r);
328
328
329 $r->set_handlers(PerlAuthenHandler => [\&OK])
329 $r->set_handlers(PerlAuthenHandler => [\&OK])
330 if is_public_project($project_id, $r) && anonymous_role_allows_browse_repository($r);
330 if is_public_project($project_id, $r) && anonymous_role_allows_browse_repository($r);
331
331
332 return OK
332 return OK
333 }
333 }
334
334
335 sub authen_handler {
335 sub authen_handler {
336 my $r = shift;
336 my $r = shift;
337
337
338 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
338 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
339 return $res unless $res == OK;
339 return $res unless $res == OK;
340
340
341 if (is_member($r->user, $redmine_pass, $r)) {
341 if (is_member($r->user, $redmine_pass, $r)) {
342 return OK;
342 return OK;
343 } else {
343 } else {
344 $r->note_auth_failure();
344 $r->note_auth_failure();
345 return AUTH_REQUIRED;
345 return AUTH_REQUIRED;
346 }
346 }
347 }
347 }
348
348
349 # check if authentication is forced
349 # check if authentication is forced
350 sub is_authentication_forced {
350 sub is_authentication_forced {
351 my $r = shift;
351 my $r = shift;
352
352
353 my $dbh = connect_database($r);
353 my $dbh = connect_database($r);
354 my $sth = $dbh->prepare(
354 my $sth = $dbh->prepare(
355 "SELECT value FROM settings where settings.name = 'login_required';"
355 "SELECT value FROM settings where settings.name = 'login_required';"
356 );
356 );
357
357
358 $sth->execute();
358 $sth->execute();
359 my $ret = 0;
359 my $ret = 0;
360 if (my @row = $sth->fetchrow_array) {
360 if (my @row = $sth->fetchrow_array) {
361 if ($row[0] eq "1" || $row[0] eq "t") {
361 if ($row[0] eq "1" || $row[0] eq "t") {
362 $ret = 1;
362 $ret = 1;
363 }
363 }
364 }
364 }
365 $sth->finish();
365 $sth->finish();
366 undef $sth;
366 undef $sth;
367
367
368 $dbh->disconnect();
368 $dbh->disconnect();
369 undef $dbh;
369 undef $dbh;
370
370
371 $ret;
371 $ret;
372 }
372 }
373
373
374 sub is_public_project {
374 sub is_public_project {
375 my $project_id = shift;
375 my $project_id = shift;
376 my $r = shift;
376 my $r = shift;
377
377
378 if (is_authentication_forced($r)) {
378 if (is_authentication_forced($r)) {
379 return 0;
379 return 0;
380 }
380 }
381
381
382 my $dbh = connect_database($r);
382 my $dbh = connect_database($r);
383 my $sth = $dbh->prepare(
383 my $sth = $dbh->prepare(
384 "SELECT is_public FROM projects WHERE projects.identifier = ?;"
384 "SELECT is_public FROM projects WHERE projects.identifier = ? AND projects.status <> 9;"
385 );
385 );
386
386
387 $sth->execute($project_id);
387 $sth->execute($project_id);
388 my $ret = 0;
388 my $ret = 0;
389 if (my @row = $sth->fetchrow_array) {
389 if (my @row = $sth->fetchrow_array) {
390 if ($row[0] eq "1" || $row[0] eq "t") {
390 if ($row[0] eq "1" || $row[0] eq "t") {
391 $ret = 1;
391 $ret = 1;
392 }
392 }
393 }
393 }
394 $sth->finish();
394 $sth->finish();
395 undef $sth;
395 undef $sth;
396 $dbh->disconnect();
396 $dbh->disconnect();
397 undef $dbh;
397 undef $dbh;
398
398
399 $ret;
399 $ret;
400 }
400 }
401
401
402 sub anonymous_role_allows_browse_repository {
402 sub anonymous_role_allows_browse_repository {
403 my $r = shift;
403 my $r = shift;
404
404
405 my $dbh = connect_database($r);
405 my $dbh = connect_database($r);
406 my $sth = $dbh->prepare(
406 my $sth = $dbh->prepare(
407 "SELECT permissions FROM roles WHERE builtin = 2;"
407 "SELECT permissions FROM roles WHERE builtin = 2;"
408 );
408 );
409
409
410 $sth->execute();
410 $sth->execute();
411 my $ret = 0;
411 my $ret = 0;
412 if (my @row = $sth->fetchrow_array) {
412 if (my @row = $sth->fetchrow_array) {
413 if ($row[0] =~ /:browse_repository/) {
413 if ($row[0] =~ /:browse_repository/) {
414 $ret = 1;
414 $ret = 1;
415 }
415 }
416 }
416 }
417 $sth->finish();
417 $sth->finish();
418 undef $sth;
418 undef $sth;
419 $dbh->disconnect();
419 $dbh->disconnect();
420 undef $dbh;
420 undef $dbh;
421
421
422 $ret;
422 $ret;
423 }
423 }
424
424
425 # perhaps we should use repository right (other read right) to check public access.
425 # perhaps we should use repository right (other read right) to check public access.
426 # it could be faster BUT it doesn't work for the moment.
426 # it could be faster BUT it doesn't work for the moment.
427 # sub is_public_project_by_file {
427 # sub is_public_project_by_file {
428 # my $project_id = shift;
428 # my $project_id = shift;
429 # my $r = shift;
429 # my $r = shift;
430
430
431 # my $tree = Apache2::Directive::conftree();
431 # my $tree = Apache2::Directive::conftree();
432 # my $node = $tree->lookup('Location', $r->location);
432 # my $node = $tree->lookup('Location', $r->location);
433 # my $hash = $node->as_hash;
433 # my $hash = $node->as_hash;
434
434
435 # my $svnparentpath = $hash->{SVNParentPath};
435 # my $svnparentpath = $hash->{SVNParentPath};
436 # my $repos_path = $svnparentpath . "/" . $project_id;
436 # my $repos_path = $svnparentpath . "/" . $project_id;
437 # return 1 if (stat($repos_path))[2] & 00007;
437 # return 1 if (stat($repos_path))[2] & 00007;
438 # }
438 # }
439
439
440 sub is_member {
440 sub is_member {
441 my $redmine_user = shift;
441 my $redmine_user = shift;
442 my $redmine_pass = shift;
442 my $redmine_pass = shift;
443 my $r = shift;
443 my $r = shift;
444
444
445 my $dbh = connect_database($r);
445 my $dbh = connect_database($r);
446 my $project_id = get_project_identifier($r);
446 my $project_id = get_project_identifier($r);
447
447
448 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
448 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
449
449
450 my $access_mode = request_is_read_only($r) ? "R" : "W";
450 my $access_mode = request_is_read_only($r) ? "R" : "W";
451
451
452 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
452 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
453 my $usrprojpass;
453 my $usrprojpass;
454 if ($cfg->{RedmineCacheCredsMax}) {
454 if ($cfg->{RedmineCacheCredsMax}) {
455 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
455 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
456 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
456 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
457 }
457 }
458 my $query = $cfg->{RedmineQuery};
458 my $query = $cfg->{RedmineQuery};
459 my $sth = $dbh->prepare($query);
459 my $sth = $dbh->prepare($query);
460 $sth->execute($redmine_user, $project_id);
460 $sth->execute($redmine_user, $project_id);
461
461
462 my $ret;
462 my $ret;
463 while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) {
463 while (my ($hashed_password, $salt, $auth_source_id, $permissions, $project_status) = $sth->fetchrow_array) {
464 if ($project_status eq "9" || ($project_status ne "1" && $access_mode eq "W")) {
465 last;
466 }
464
467
465 unless ($auth_source_id) {
468 unless ($auth_source_id) {
466 my $method = $r->method;
469 my $method = $r->method;
467 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
470 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
468 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
471 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
469 $ret = 1;
472 $ret = 1;
470 last;
473 last;
471 }
474 }
472 } elsif ($CanUseLDAPAuth) {
475 } elsif ($CanUseLDAPAuth) {
473 my $sthldap = $dbh->prepare(
476 my $sthldap = $dbh->prepare(
474 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
477 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
475 );
478 );
476 $sthldap->execute($auth_source_id);
479 $sthldap->execute($auth_source_id);
477 while (my @rowldap = $sthldap->fetchrow_array) {
480 while (my @rowldap = $sthldap->fetchrow_array) {
478 my $bind_as = $rowldap[3] ? $rowldap[3] : "";
481 my $bind_as = $rowldap[3] ? $rowldap[3] : "";
479 my $bind_pw = $rowldap[4] ? $rowldap[4] : "";
482 my $bind_pw = $rowldap[4] ? $rowldap[4] : "";
480 if ($bind_as =~ m/\$login/) {
483 if ($bind_as =~ m/\$login/) {
481 # replace $login with $redmine_user and use $redmine_pass
484 # replace $login with $redmine_user and use $redmine_pass
482 $bind_as =~ s/\$login/$redmine_user/g;
485 $bind_as =~ s/\$login/$redmine_user/g;
483 $bind_pw = $redmine_pass
486 $bind_pw = $redmine_pass
484 }
487 }
485 my $ldap = Authen::Simple::LDAP->new(
488 my $ldap = Authen::Simple::LDAP->new(
486 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
489 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
487 port => $rowldap[1],
490 port => $rowldap[1],
488 basedn => $rowldap[5],
491 basedn => $rowldap[5],
489 binddn => $bind_as,
492 binddn => $bind_as,
490 bindpw => $bind_pw,
493 bindpw => $bind_pw,
491 filter => "(".$rowldap[6]."=%s)"
494 filter => "(".$rowldap[6]."=%s)"
492 );
495 );
493 my $method = $r->method;
496 my $method = $r->method;
494 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
497 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
495
498
496 }
499 }
497 $sthldap->finish();
500 $sthldap->finish();
498 undef $sthldap;
501 undef $sthldap;
499 }
502 }
500 }
503 }
501 $sth->finish();
504 $sth->finish();
502 undef $sth;
505 undef $sth;
503 $dbh->disconnect();
506 $dbh->disconnect();
504 undef $dbh;
507 undef $dbh;
505
508
506 if ($cfg->{RedmineCacheCredsMax} and $ret) {
509 if ($cfg->{RedmineCacheCredsMax} and $ret) {
507 if (defined $usrprojpass) {
510 if (defined $usrprojpass) {
508 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
511 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
509 } else {
512 } else {
510 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
513 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
511 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
514 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
512 $cfg->{RedmineCacheCredsCount}++;
515 $cfg->{RedmineCacheCredsCount}++;
513 } else {
516 } else {
514 $cfg->{RedmineCacheCreds}->clear();
517 $cfg->{RedmineCacheCreds}->clear();
515 $cfg->{RedmineCacheCredsCount} = 0;
518 $cfg->{RedmineCacheCredsCount} = 0;
516 }
519 }
517 }
520 }
518 }
521 }
519
522
520 $ret;
523 $ret;
521 }
524 }
522
525
523 sub get_project_identifier {
526 sub get_project_identifier {
524 my $r = shift;
527 my $r = shift;
525
528
526 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
529 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
527 my $location = $r->location;
530 my $location = $r->location;
528 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
531 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
529 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
532 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
530 $identifier;
533 $identifier;
531 }
534 }
532
535
533 sub connect_database {
536 sub connect_database {
534 my $r = shift;
537 my $r = shift;
535
538
536 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
539 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
537 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
540 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
538 }
541 }
539
542
540 1;
543 1;
@@ -1,251 +1,294
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../test_case', __FILE__)
18 require File.expand_path('../test_case', __FILE__)
19 require 'tmpdir'
19 require 'tmpdir'
20
20
21 class RedminePmTest::RepositorySubversionTest < RedminePmTest::TestCase
21 class RedminePmTest::RepositorySubversionTest < RedminePmTest::TestCase
22 fixtures :projects, :users, :members, :roles, :member_roles, :auth_sources
22 fixtures :projects, :users, :members, :roles, :member_roles, :auth_sources
23
23
24 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
24 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
25
25
26 def test_anonymous_read_on_public_repo_with_permission_should_succeed
26 def test_anonymous_read_on_public_repo_with_permission_should_succeed
27 assert_success "ls", svn_url
27 assert_success "ls", svn_url
28 end
28 end
29
29
30 def test_anonymous_read_on_public_repo_without_permission_should_fail
30 def test_anonymous_read_on_public_repo_without_permission_should_fail
31 Role.anonymous.remove_permission! :browse_repository
31 Role.anonymous.remove_permission! :browse_repository
32 assert_failure "ls", svn_url
32 assert_failure "ls", svn_url
33 end
33 end
34
34
35 def test_anonymous_read_on_private_repo_should_fail
35 def test_anonymous_read_on_private_repo_should_fail
36 Project.find(1).update_attribute :is_public, false
36 Project.find(1).update_attribute :is_public, false
37 assert_failure "ls", svn_url
37 assert_failure "ls", svn_url
38 end
38 end
39
39
40 def test_anonymous_commit_on_public_repo_should_fail
40 def test_anonymous_commit_on_public_repo_should_fail
41 Role.anonymous.add_permission! :commit_access
41 Role.anonymous.add_permission! :commit_access
42 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
42 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
43 end
43 end
44
44
45 def test_anonymous_commit_on_private_repo_should_fail
45 def test_anonymous_commit_on_private_repo_should_fail
46 Role.anonymous.add_permission! :commit_access
46 Role.anonymous.add_permission! :commit_access
47 Project.find(1).update_attribute :is_public, false
47 Project.find(1).update_attribute :is_public, false
48 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
48 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
49 end
49 end
50
50
51 def test_non_member_read_on_public_repo_with_permission_should_succeed
51 def test_non_member_read_on_public_repo_with_permission_should_succeed
52 Role.anonymous.remove_permission! :browse_repository
52 Role.anonymous.remove_permission! :browse_repository
53 with_credentials "miscuser8", "foo" do
53 with_credentials "miscuser8", "foo" do
54 assert_success "ls", svn_url
54 assert_success "ls", svn_url
55 end
55 end
56 end
56 end
57
57
58 def test_non_member_read_on_public_repo_without_permission_should_fail
58 def test_non_member_read_on_public_repo_without_permission_should_fail
59 Role.anonymous.remove_permission! :browse_repository
59 Role.anonymous.remove_permission! :browse_repository
60 Role.non_member.remove_permission! :browse_repository
60 Role.non_member.remove_permission! :browse_repository
61 with_credentials "miscuser8", "foo" do
61 with_credentials "miscuser8", "foo" do
62 assert_failure "ls", svn_url
62 assert_failure "ls", svn_url
63 end
63 end
64 end
64 end
65
65
66 def test_non_member_read_on_private_repo_should_fail
66 def test_non_member_read_on_private_repo_should_fail
67 Project.find(1).update_attribute :is_public, false
67 Project.find(1).update_attribute :is_public, false
68 with_credentials "miscuser8", "foo" do
68 with_credentials "miscuser8", "foo" do
69 assert_failure "ls", svn_url
69 assert_failure "ls", svn_url
70 end
70 end
71 end
71 end
72
72
73 def test_non_member_commit_on_public_repo_should_fail
73 def test_non_member_commit_on_public_repo_should_fail
74 Role.non_member.add_permission! :commit_access
74 Role.non_member.add_permission! :commit_access
75 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
75 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
76 end
76 end
77
77
78 def test_non_member_commit_on_private_repo_should_fail
78 def test_non_member_commit_on_private_repo_should_fail
79 Role.non_member.add_permission! :commit_access
79 Role.non_member.add_permission! :commit_access
80 Project.find(1).update_attribute :is_public, false
80 Project.find(1).update_attribute :is_public, false
81 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
81 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
82 end
82 end
83
83
84 def test_member_read_on_public_repo_with_permission_should_succeed
84 def test_member_read_on_public_repo_with_permission_should_succeed
85 Role.anonymous.remove_permission! :browse_repository
85 Role.anonymous.remove_permission! :browse_repository
86 Role.non_member.remove_permission! :browse_repository
86 Role.non_member.remove_permission! :browse_repository
87 with_credentials "dlopper", "foo" do
87 with_credentials "dlopper", "foo" do
88 assert_success "ls", svn_url
88 assert_success "ls", svn_url
89 end
89 end
90 end
90 end
91
91
92 def test_member_read_on_public_repo_without_permission_should_fail
92 def test_member_read_on_public_repo_without_permission_should_fail
93 Role.anonymous.remove_permission! :browse_repository
93 Role.anonymous.remove_permission! :browse_repository
94 Role.non_member.remove_permission! :browse_repository
94 Role.non_member.remove_permission! :browse_repository
95 Role.find(2).remove_permission! :browse_repository
95 Role.find(2).remove_permission! :browse_repository
96 with_credentials "dlopper", "foo" do
96 with_credentials "dlopper", "foo" do
97 assert_failure "ls", svn_url
97 assert_failure "ls", svn_url
98 end
98 end
99 end
99 end
100
100
101 def test_member_read_on_private_repo_with_permission_should_succeed
101 def test_member_read_on_private_repo_with_permission_should_succeed
102 Project.find(1).update_attribute :is_public, false
102 Project.find(1).update_attribute :is_public, false
103 with_credentials "dlopper", "foo" do
103 with_credentials "dlopper", "foo" do
104 assert_success "ls", svn_url
104 assert_success "ls", svn_url
105 end
105 end
106 end
106 end
107
107
108 def test_member_read_on_private_repo_without_permission_should_fail
108 def test_member_read_on_private_repo_without_permission_should_fail
109 Role.find(2).remove_permission! :browse_repository
109 Role.find(2).remove_permission! :browse_repository
110 Project.find(1).update_attribute :is_public, false
110 Project.find(1).update_attribute :is_public, false
111 with_credentials "dlopper", "foo" do
111 with_credentials "dlopper", "foo" do
112 assert_failure "ls", svn_url
112 assert_failure "ls", svn_url
113 end
113 end
114 end
114 end
115
115
116 def test_member_commit_on_public_repo_with_permission_should_succeed
116 def test_member_commit_on_public_repo_with_permission_should_succeed
117 Role.find(2).add_permission! :commit_access
117 Role.find(2).add_permission! :commit_access
118 with_credentials "dlopper", "foo" do
118 with_credentials "dlopper", "foo" do
119 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
119 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
120 end
120 end
121 end
121 end
122
122
123 def test_member_commit_on_public_repo_without_permission_should_fail
123 def test_member_commit_on_public_repo_without_permission_should_fail
124 Role.find(2).remove_permission! :commit_access
124 Role.find(2).remove_permission! :commit_access
125 with_credentials "dlopper", "foo" do
125 with_credentials "dlopper", "foo" do
126 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
126 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
127 end
127 end
128 end
128 end
129
129
130 def test_member_commit_on_private_repo_with_permission_should_succeed
130 def test_member_commit_on_private_repo_with_permission_should_succeed
131 Role.find(2).add_permission! :commit_access
131 Role.find(2).add_permission! :commit_access
132 Project.find(1).update_attribute :is_public, false
132 Project.find(1).update_attribute :is_public, false
133 with_credentials "dlopper", "foo" do
133 with_credentials "dlopper", "foo" do
134 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
134 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
135 end
135 end
136 end
136 end
137
137
138 def test_member_commit_on_private_repo_without_permission_should_fail
138 def test_member_commit_on_private_repo_without_permission_should_fail
139 Role.find(2).remove_permission! :commit_access
139 Role.find(2).remove_permission! :commit_access
140 Project.find(1).update_attribute :is_public, false
140 Project.find(1).update_attribute :is_public, false
141 with_credentials "dlopper", "foo" do
141 with_credentials "dlopper", "foo" do
142 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
142 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
143 end
143 end
144 end
144 end
145
145
146 def test_invalid_credentials_should_fail
146 def test_invalid_credentials_should_fail
147 Project.find(1).update_attribute :is_public, false
147 Project.find(1).update_attribute :is_public, false
148 with_credentials "dlopper", "foo" do
148 with_credentials "dlopper", "foo" do
149 assert_success "ls", svn_url
149 assert_success "ls", svn_url
150 end
150 end
151 with_credentials "dlopper", "wrong" do
151 with_credentials "dlopper", "wrong" do
152 assert_failure "ls", svn_url
152 assert_failure "ls", svn_url
153 end
153 end
154 end
154 end
155
155
156 def test_anonymous_read_should_fail_with_login_required
156 def test_anonymous_read_should_fail_with_login_required
157 assert_success "ls", svn_url
157 assert_success "ls", svn_url
158 with_settings :login_required => '1' do
158 with_settings :login_required => '1' do
159 assert_failure "ls", svn_url
159 assert_failure "ls", svn_url
160 end
160 end
161 end
161 end
162
162
163 def test_authenticated_read_should_succeed_with_login_required
163 def test_authenticated_read_should_succeed_with_login_required
164 with_settings :login_required => '1' do
164 with_settings :login_required => '1' do
165 with_credentials "miscuser8", "foo" do
165 with_credentials "miscuser8", "foo" do
166 assert_success "ls", svn_url
166 assert_success "ls", svn_url
167 end
167 end
168 end
168 end
169 end
169 end
170
170
171 def test_read_on_archived_projects_should_fail
172 Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED
173 assert_failure "ls", svn_url
174 end
175
176 def test_read_on_archived_private_projects_should_fail
177 Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED
178 Project.find(1).update_attribute :is_public, false
179 with_credentials "dlopper", "foo" do
180 assert_failure "ls", svn_url
181 end
182 end
183
184 def test_read_on_closed_projects_should_succeed
185 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
186 assert_success "ls", svn_url
187 end
188
189 def test_read_on_closed_private_projects_should_succeed
190 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
191 Project.find(1).update_attribute :is_public, false
192 with_credentials "dlopper", "foo" do
193 assert_success "ls", svn_url
194 end
195 end
196
197 def test_commit_on_closed_projects_should_fail
198 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
199 Role.find(2).add_permission! :commit_access
200 with_credentials "dlopper", "foo" do
201 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
202 end
203 end
204
205 def test_commit_on_closed_private_projects_should_fail
206 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
207 Project.find(1).update_attribute :is_public, false
208 Role.find(2).add_permission! :commit_access
209 with_credentials "dlopper", "foo" do
210 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
211 end
212 end
213
171 if ldap_configured?
214 if ldap_configured?
172 def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials
215 def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials
173 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
216 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
174 ldap_user.login = 'example1'
217 ldap_user.login = 'example1'
175 ldap_user.save!
218 ldap_user.save!
176
219
177 with_settings :login_required => '1' do
220 with_settings :login_required => '1' do
178 with_credentials "example1", "123456" do
221 with_credentials "example1", "123456" do
179 assert_success "ls", svn_url
222 assert_success "ls", svn_url
180 end
223 end
181 end
224 end
182
225
183 with_settings :login_required => '1' do
226 with_settings :login_required => '1' do
184 with_credentials "example1", "wrong" do
227 with_credentials "example1", "wrong" do
185 assert_failure "ls", svn_url
228 assert_failure "ls", svn_url
186 end
229 end
187 end
230 end
188 end
231 end
189 end
232 end
190
233
191 def test_checkout
234 def test_checkout
192 Dir.mktmpdir do |dir|
235 Dir.mktmpdir do |dir|
193 assert_success "checkout", svn_url, dir
236 assert_success "checkout", svn_url, dir
194 end
237 end
195 end
238 end
196
239
197 def test_read_commands
240 def test_read_commands
198 assert_success "info", svn_url
241 assert_success "info", svn_url
199 assert_success "ls", svn_url
242 assert_success "ls", svn_url
200 assert_success "log", svn_url
243 assert_success "log", svn_url
201 end
244 end
202
245
203 def test_write_commands
246 def test_write_commands
204 Role.find(2).add_permission! :commit_access
247 Role.find(2).add_permission! :commit_access
205 filename = random_filename
248 filename = random_filename
206
249
207 Dir.mktmpdir do |dir|
250 Dir.mktmpdir do |dir|
208 assert_success "checkout", svn_url, dir
251 assert_success "checkout", svn_url, dir
209 Dir.chdir(dir) do
252 Dir.chdir(dir) do
210 # creates a file in the working copy
253 # creates a file in the working copy
211 f = File.new(File.join(dir, filename), "w")
254 f = File.new(File.join(dir, filename), "w")
212 f.write "test file content"
255 f.write "test file content"
213 f.close
256 f.close
214
257
215 assert_success "add", filename
258 assert_success "add", filename
216 with_credentials "dlopper", "foo" do
259 with_credentials "dlopper", "foo" do
217 assert_success "commit --message Committing_a_file"
260 assert_success "commit --message Committing_a_file"
218 assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy")
261 assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy")
219 assert_success "delete --message Deleting_a_file", svn_url(filename)
262 assert_success "delete --message Deleting_a_file", svn_url(filename)
220 assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir")
263 assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir")
221 end
264 end
222 assert_success "update"
265 assert_success "update"
223
266
224 # checks that the working copy was updated
267 # checks that the working copy was updated
225 assert File.exists?(File.join(dir, "#{filename}_copy"))
268 assert File.exists?(File.join(dir, "#{filename}_copy"))
226 assert File.directory?(File.join(dir, "#{filename}_dir"))
269 assert File.directory?(File.join(dir, "#{filename}_dir"))
227 end
270 end
228 end
271 end
229 end
272 end
230
273
231 def test_read_invalid_repo_should_fail
274 def test_read_invalid_repo_should_fail
232 assert_failure "ls", svn_url("invalid")
275 assert_failure "ls", svn_url("invalid")
233 end
276 end
234
277
235 protected
278 protected
236
279
237 def execute(*args)
280 def execute(*args)
238 a = [SVN_BIN, "--no-auth-cache --non-interactive"]
281 a = [SVN_BIN, "--no-auth-cache --non-interactive"]
239 a << "--username #{username}" if username
282 a << "--username #{username}" if username
240 a << "--password #{password}" if password
283 a << "--password #{password}" if password
241
284
242 super a, *args
285 super a, *args
243 end
286 end
244
287
245 def svn_url(path=nil)
288 def svn_url(path=nil)
246 host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1'
289 host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1'
247 url = "http://#{host}/svn/ecookbook"
290 url = "http://#{host}/svn/ecookbook"
248 url << "/#{path}" if path
291 url << "/#{path}" if path
249 url
292 url
250 end
293 end
251 end
294 end
General Comments 0
You need to be logged in to leave comments. Login now