##// END OF EJS Templates
Check that repository module is enabled (#24307)....
Jean-Philippe Lang -
r15904:427a745184d1
parent child
Show More
@@ -1,557 +1,560
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 project repository must be named with the project identifier. In case
87 A project repository must be named with the project 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 users.hashed_password, users.salt, users.auth_source_id, roles.permissions, projects.status
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 EXISTS (SELECT 1 FROM enabled_modules em WHERE em.project_id = projects.id AND em.name = 'repository')
247 AND users.type='User'
248 AND users.type='User'
248 AND users.status=1
249 AND users.status=1
249 AND (
250 AND (
250 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)
251 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)
251 OR
252 OR
252 (cast(projects.is_public as CHAR) IN ('t', '1')
253 (cast(projects.is_public as CHAR) IN ('t', '1')
253 AND (roles.builtin=1
254 AND (roles.builtin=1
254 OR roles.id IN (SELECT member_roles.role_id FROM members, member_roles, users g
255 OR roles.id IN (SELECT member_roles.role_id FROM members, member_roles, users g
255 WHERE members.user_id = g.id AND members.project_id = projects.id AND members.id = member_roles.member_id
256 WHERE members.user_id = g.id AND members.project_id = projects.id AND members.id = member_roles.member_id
256 AND g.type = 'GroupNonMember'))
257 AND g.type = 'GroupNonMember'))
257 )
258 )
258 )
259 )
259 AND roles.permissions IS NOT NULL";
260 AND roles.permissions IS NOT NULL";
260 $self->{RedmineQuery} = trim($query);
261 $self->{RedmineQuery} = trim($query);
261 }
262 }
262
263
263 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
264 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
264 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
265 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
265 sub RedmineDbWhereClause {
266 sub RedmineDbWhereClause {
266 my ($self, $parms, $arg) = @_;
267 my ($self, $parms, $arg) = @_;
267 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
268 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
268 }
269 }
269
270
270 sub RedmineCacheCredsMax {
271 sub RedmineCacheCredsMax {
271 my ($self, $parms, $arg) = @_;
272 my ($self, $parms, $arg) = @_;
272 if ($arg) {
273 if ($arg) {
273 $self->{RedmineCachePool} = APR::Pool->new;
274 $self->{RedmineCachePool} = APR::Pool->new;
274 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
275 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
275 $self->{RedmineCacheCredsCount} = 0;
276 $self->{RedmineCacheCredsCount} = 0;
276 $self->{RedmineCacheCredsMax} = $arg;
277 $self->{RedmineCacheCredsMax} = $arg;
277 }
278 }
278 }
279 }
279
280
280 sub RedmineGitSmartHttp {
281 sub RedmineGitSmartHttp {
281 my ($self, $parms, $arg) = @_;
282 my ($self, $parms, $arg) = @_;
282 $arg = lc $arg;
283 $arg = lc $arg;
283
284
284 if ($arg eq "yes" || $arg eq "true") {
285 if ($arg eq "yes" || $arg eq "true") {
285 $self->{RedmineGitSmartHttp} = 1;
286 $self->{RedmineGitSmartHttp} = 1;
286 } else {
287 } else {
287 $self->{RedmineGitSmartHttp} = 0;
288 $self->{RedmineGitSmartHttp} = 0;
288 }
289 }
289 }
290 }
290
291
291 sub trim {
292 sub trim {
292 my $string = shift;
293 my $string = shift;
293 $string =~ s/\s{2,}/ /g;
294 $string =~ s/\s{2,}/ /g;
294 return $string;
295 return $string;
295 }
296 }
296
297
297 sub set_val {
298 sub set_val {
298 my ($key, $self, $parms, $arg) = @_;
299 my ($key, $self, $parms, $arg) = @_;
299 $self->{$key} = $arg;
300 $self->{$key} = $arg;
300 }
301 }
301
302
302 Apache2::Module::add(__PACKAGE__, \@directives);
303 Apache2::Module::add(__PACKAGE__, \@directives);
303
304
304
305
305 my %read_only_methods = map { $_ => 1 } qw/GET HEAD PROPFIND REPORT OPTIONS/;
306 my %read_only_methods = map { $_ => 1 } qw/GET HEAD PROPFIND REPORT OPTIONS/;
306
307
307 sub request_is_read_only {
308 sub request_is_read_only {
308 my ($r) = @_;
309 my ($r) = @_;
309 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
310 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
310
311
311 # Do we use Git's smart HTTP protocol, or not?
312 # Do we use Git's smart HTTP protocol, or not?
312 if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
313 if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
313 my $uri = $r->unparsed_uri;
314 my $uri = $r->unparsed_uri;
314 my $location = $r->location;
315 my $location = $r->location;
315 my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
316 my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
316 return $is_read_only;
317 return $is_read_only;
317 } else {
318 } else {
318 # Standard behaviour: check the HTTP method
319 # Standard behaviour: check the HTTP method
319 my $method = $r->method;
320 my $method = $r->method;
320 return defined $read_only_methods{$method};
321 return defined $read_only_methods{$method};
321 }
322 }
322 }
323 }
323
324
324 sub access_handler {
325 sub access_handler {
325 my $r = shift;
326 my $r = shift;
326
327
327 unless ($r->some_auth_required) {
328 unless ($r->some_auth_required) {
328 $r->log_reason("No authentication has been configured");
329 $r->log_reason("No authentication has been configured");
329 return FORBIDDEN;
330 return FORBIDDEN;
330 }
331 }
331
332
332 return OK unless request_is_read_only($r);
333 return OK unless request_is_read_only($r);
333
334
334 my $project_id = get_project_identifier($r);
335 my $project_id = get_project_identifier($r);
335
336
336 if (is_public_project($project_id, $r) && anonymous_allowed_to_browse_repository($project_id, $r)) {
337 if (is_public_project($project_id, $r) && anonymous_allowed_to_browse_repository($project_id, $r)) {
337 $r->user("");
338 $r->user("");
338 $r->set_handlers(PerlAuthenHandler => [\&OK]);
339 $r->set_handlers(PerlAuthenHandler => [\&OK]);
339 }
340 }
340
341
341 return OK
342 return OK
342 }
343 }
343
344
344 sub authen_handler {
345 sub authen_handler {
345 my $r = shift;
346 my $r = shift;
346
347
347 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
348 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
348 return $res unless $res == OK;
349 return $res unless $res == OK;
349
350
350 if (is_member($r->user, $redmine_pass, $r)) {
351 if (is_member($r->user, $redmine_pass, $r)) {
351 return OK;
352 return OK;
352 } else {
353 } else {
353 $r->note_auth_failure();
354 $r->note_auth_failure();
354 return DECLINED;
355 return DECLINED;
355 }
356 }
356 }
357 }
357
358
358 # check if authentication is forced
359 # check if authentication is forced
359 sub is_authentication_forced {
360 sub is_authentication_forced {
360 my $r = shift;
361 my $r = shift;
361
362
362 my $dbh = connect_database($r);
363 my $dbh = connect_database($r);
363 my $sth = $dbh->prepare(
364 my $sth = $dbh->prepare(
364 "SELECT value FROM settings where settings.name = 'login_required';"
365 "SELECT value FROM settings where settings.name = 'login_required';"
365 );
366 );
366
367
367 $sth->execute();
368 $sth->execute();
368 my $ret = 0;
369 my $ret = 0;
369 if (my @row = $sth->fetchrow_array) {
370 if (my @row = $sth->fetchrow_array) {
370 if ($row[0] eq "1" || $row[0] eq "t") {
371 if ($row[0] eq "1" || $row[0] eq "t") {
371 $ret = 1;
372 $ret = 1;
372 }
373 }
373 }
374 }
374 $sth->finish();
375 $sth->finish();
375 undef $sth;
376 undef $sth;
376
377
377 $dbh->disconnect();
378 $dbh->disconnect();
378 undef $dbh;
379 undef $dbh;
379
380
380 $ret;
381 $ret;
381 }
382 }
382
383
383 sub is_public_project {
384 sub is_public_project {
384 my $project_id = shift;
385 my $project_id = shift;
385 my $r = shift;
386 my $r = shift;
386
387
387 if (is_authentication_forced($r)) {
388 if (is_authentication_forced($r)) {
388 return 0;
389 return 0;
389 }
390 }
390
391
391 my $dbh = connect_database($r);
392 my $dbh = connect_database($r);
392 my $sth = $dbh->prepare(
393 my $sth = $dbh->prepare(
393 "SELECT is_public FROM projects WHERE projects.identifier = ? AND projects.status <> 9;"
394 "SELECT is_public FROM projects
395 WHERE projects.identifier = ? AND projects.status <> 9
396 AND EXISTS (SELECT 1 FROM enabled_modules em WHERE em.project_id = projects.id AND em.name = 'repository');"
394 );
397 );
395
398
396 $sth->execute($project_id);
399 $sth->execute($project_id);
397 my $ret = 0;
400 my $ret = 0;
398 if (my @row = $sth->fetchrow_array) {
401 if (my @row = $sth->fetchrow_array) {
399 if ($row[0] eq "1" || $row[0] eq "t") {
402 if ($row[0] eq "1" || $row[0] eq "t") {
400 $ret = 1;
403 $ret = 1;
401 }
404 }
402 }
405 }
403 $sth->finish();
406 $sth->finish();
404 undef $sth;
407 undef $sth;
405 $dbh->disconnect();
408 $dbh->disconnect();
406 undef $dbh;
409 undef $dbh;
407
410
408 $ret;
411 $ret;
409 }
412 }
410
413
411 sub anonymous_allowed_to_browse_repository {
414 sub anonymous_allowed_to_browse_repository {
412 my $project_id = shift;
415 my $project_id = shift;
413 my $r = shift;
416 my $r = shift;
414
417
415 my $dbh = connect_database($r);
418 my $dbh = connect_database($r);
416 my $sth = $dbh->prepare(
419 my $sth = $dbh->prepare(
417 "SELECT permissions FROM roles WHERE permissions like '%browse_repository%'
420 "SELECT permissions FROM roles WHERE permissions like '%browse_repository%'
418 AND (roles.builtin = 2
421 AND (roles.builtin = 2
419 OR roles.id IN (SELECT member_roles.role_id FROM projects, members, member_roles, users
422 OR roles.id IN (SELECT member_roles.role_id FROM projects, members, member_roles, users
420 WHERE members.user_id = users.id AND members.project_id = projects.id AND members.id = member_roles.member_id
423 WHERE members.user_id = users.id AND members.project_id = projects.id AND members.id = member_roles.member_id
421 AND projects.identifier = ? AND users.type = 'GroupAnonymous'));"
424 AND projects.identifier = ? AND users.type = 'GroupAnonymous'));"
422 );
425 );
423
426
424 $sth->execute($project_id);
427 $sth->execute($project_id);
425 my $ret = 0;
428 my $ret = 0;
426 if (my @row = $sth->fetchrow_array) {
429 if (my @row = $sth->fetchrow_array) {
427 if ($row[0] =~ /:browse_repository/) {
430 if ($row[0] =~ /:browse_repository/) {
428 $ret = 1;
431 $ret = 1;
429 }
432 }
430 }
433 }
431 $sth->finish();
434 $sth->finish();
432 undef $sth;
435 undef $sth;
433 $dbh->disconnect();
436 $dbh->disconnect();
434 undef $dbh;
437 undef $dbh;
435
438
436 $ret;
439 $ret;
437 }
440 }
438
441
439 # perhaps we should use repository right (other read right) to check public access.
442 # perhaps we should use repository right (other read right) to check public access.
440 # it could be faster BUT it doesn't work for the moment.
443 # it could be faster BUT it doesn't work for the moment.
441 # sub is_public_project_by_file {
444 # sub is_public_project_by_file {
442 # my $project_id = shift;
445 # my $project_id = shift;
443 # my $r = shift;
446 # my $r = shift;
444
447
445 # my $tree = Apache2::Directive::conftree();
448 # my $tree = Apache2::Directive::conftree();
446 # my $node = $tree->lookup('Location', $r->location);
449 # my $node = $tree->lookup('Location', $r->location);
447 # my $hash = $node->as_hash;
450 # my $hash = $node->as_hash;
448
451
449 # my $svnparentpath = $hash->{SVNParentPath};
452 # my $svnparentpath = $hash->{SVNParentPath};
450 # my $repos_path = $svnparentpath . "/" . $project_id;
453 # my $repos_path = $svnparentpath . "/" . $project_id;
451 # return 1 if (stat($repos_path))[2] & 00007;
454 # return 1 if (stat($repos_path))[2] & 00007;
452 # }
455 # }
453
456
454 sub is_member {
457 sub is_member {
455 my $redmine_user = shift;
458 my $redmine_user = shift;
456 my $redmine_pass = shift;
459 my $redmine_pass = shift;
457 my $r = shift;
460 my $r = shift;
458
461
459 my $project_id = get_project_identifier($r);
462 my $project_id = get_project_identifier($r);
460
463
461 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
464 my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
462
465
463 my $access_mode = request_is_read_only($r) ? "R" : "W";
466 my $access_mode = request_is_read_only($r) ? "R" : "W";
464
467
465 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
468 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
466 my $usrprojpass;
469 my $usrprojpass;
467 if ($cfg->{RedmineCacheCredsMax}) {
470 if ($cfg->{RedmineCacheCredsMax}) {
468 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
471 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
469 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
472 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
470 }
473 }
471 my $dbh = connect_database($r);
474 my $dbh = connect_database($r);
472 my $query = $cfg->{RedmineQuery};
475 my $query = $cfg->{RedmineQuery};
473 my $sth = $dbh->prepare($query);
476 my $sth = $dbh->prepare($query);
474 $sth->execute($redmine_user, $project_id);
477 $sth->execute($redmine_user, $project_id);
475
478
476 my $ret;
479 my $ret;
477 while (my ($hashed_password, $salt, $auth_source_id, $permissions, $project_status) = $sth->fetchrow_array) {
480 while (my ($hashed_password, $salt, $auth_source_id, $permissions, $project_status) = $sth->fetchrow_array) {
478 if ($project_status eq "9" || ($project_status ne "1" && $access_mode eq "W")) {
481 if ($project_status eq "9" || ($project_status ne "1" && $access_mode eq "W")) {
479 last;
482 last;
480 }
483 }
481
484
482 unless ($auth_source_id) {
485 unless ($auth_source_id) {
483 my $method = $r->method;
486 my $method = $r->method;
484 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
487 my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
485 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
488 if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
486 $ret = 1;
489 $ret = 1;
487 last;
490 last;
488 }
491 }
489 } elsif ($CanUseLDAPAuth) {
492 } elsif ($CanUseLDAPAuth) {
490 my $sthldap = $dbh->prepare(
493 my $sthldap = $dbh->prepare(
491 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
494 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
492 );
495 );
493 $sthldap->execute($auth_source_id);
496 $sthldap->execute($auth_source_id);
494 while (my @rowldap = $sthldap->fetchrow_array) {
497 while (my @rowldap = $sthldap->fetchrow_array) {
495 my $bind_as = $rowldap[3] ? $rowldap[3] : "";
498 my $bind_as = $rowldap[3] ? $rowldap[3] : "";
496 my $bind_pw = $rowldap[4] ? $rowldap[4] : "";
499 my $bind_pw = $rowldap[4] ? $rowldap[4] : "";
497 if ($bind_as =~ m/\$login/) {
500 if ($bind_as =~ m/\$login/) {
498 # replace $login with $redmine_user and use $redmine_pass
501 # replace $login with $redmine_user and use $redmine_pass
499 $bind_as =~ s/\$login/$redmine_user/g;
502 $bind_as =~ s/\$login/$redmine_user/g;
500 $bind_pw = $redmine_pass
503 $bind_pw = $redmine_pass
501 }
504 }
502 my $ldap = Authen::Simple::LDAP->new(
505 my $ldap = Authen::Simple::LDAP->new(
503 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
506 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
504 port => $rowldap[1],
507 port => $rowldap[1],
505 basedn => $rowldap[5],
508 basedn => $rowldap[5],
506 binddn => $bind_as,
509 binddn => $bind_as,
507 bindpw => $bind_pw,
510 bindpw => $bind_pw,
508 filter => "(".$rowldap[6]."=%s)"
511 filter => "(".$rowldap[6]."=%s)"
509 );
512 );
510 my $method = $r->method;
513 my $method = $r->method;
511 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
514 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
512
515
513 }
516 }
514 $sthldap->finish();
517 $sthldap->finish();
515 undef $sthldap;
518 undef $sthldap;
516 }
519 }
517 }
520 }
518 $sth->finish();
521 $sth->finish();
519 undef $sth;
522 undef $sth;
520 $dbh->disconnect();
523 $dbh->disconnect();
521 undef $dbh;
524 undef $dbh;
522
525
523 if ($cfg->{RedmineCacheCredsMax} and $ret) {
526 if ($cfg->{RedmineCacheCredsMax} and $ret) {
524 if (defined $usrprojpass) {
527 if (defined $usrprojpass) {
525 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
528 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
526 } else {
529 } else {
527 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
530 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
528 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
531 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
529 $cfg->{RedmineCacheCredsCount}++;
532 $cfg->{RedmineCacheCredsCount}++;
530 } else {
533 } else {
531 $cfg->{RedmineCacheCreds}->clear();
534 $cfg->{RedmineCacheCreds}->clear();
532 $cfg->{RedmineCacheCredsCount} = 0;
535 $cfg->{RedmineCacheCredsCount} = 0;
533 }
536 }
534 }
537 }
535 }
538 }
536
539
537 $ret;
540 $ret;
538 }
541 }
539
542
540 sub get_project_identifier {
543 sub get_project_identifier {
541 my $r = shift;
544 my $r = shift;
542
545
543 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
546 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
544 my $location = $r->location;
547 my $location = $r->location;
545 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
548 $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
546 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
549 my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
547 $identifier;
550 $identifier;
548 }
551 }
549
552
550 sub connect_database {
553 sub connect_database {
551 my $r = shift;
554 my $r = shift;
552
555
553 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
556 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
554 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
557 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
555 }
558 }
556
559
557 1;
560 1;
@@ -1,309 +1,332
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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, :enabled_modules
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_with_anonymous_group_permission_should_succeed
30 def test_anonymous_read_on_public_repo_with_anonymous_group_permission_should_succeed
31 Role.anonymous.remove_permission! :browse_repository
31 Role.anonymous.remove_permission! :browse_repository
32 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
32 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
33 assert_success "ls", svn_url
33 assert_success "ls", svn_url
34 end
34 end
35
35
36 def test_anonymous_read_on_public_repo_without_permission_should_fail
36 def test_anonymous_read_on_public_repo_without_permission_should_fail
37 Role.anonymous.remove_permission! :browse_repository
37 Role.anonymous.remove_permission! :browse_repository
38 assert_failure "ls", svn_url
38 assert_failure "ls", svn_url
39 end
39 end
40
40
41 def test_anonymous_read_on_public_project_with_module_disabled_should_fail
42 Project.find(1).disable_module! :repository
43 assert_failure "ls", svn_url
44 end
45
41 def test_anonymous_read_on_private_repo_should_fail
46 def test_anonymous_read_on_private_repo_should_fail
42 Project.find(1).update_attribute :is_public, false
47 Project.find(1).update_attribute :is_public, false
43 assert_failure "ls", svn_url
48 assert_failure "ls", svn_url
44 end
49 end
45
50
46 def test_anonymous_commit_on_public_repo_should_fail
51 def test_anonymous_commit_on_public_repo_should_fail
47 Role.anonymous.add_permission! :commit_access
52 Role.anonymous.add_permission! :commit_access
48 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
53 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
49 end
54 end
50
55
51 def test_anonymous_commit_on_private_repo_should_fail
56 def test_anonymous_commit_on_private_repo_should_fail
52 Role.anonymous.add_permission! :commit_access
57 Role.anonymous.add_permission! :commit_access
53 Project.find(1).update_attribute :is_public, false
58 Project.find(1).update_attribute :is_public, false
54 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
59 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
55 end
60 end
56
61
57 def test_non_member_read_on_public_repo_with_permission_should_succeed
62 def test_non_member_read_on_public_repo_with_permission_should_succeed
58 Role.anonymous.remove_permission! :browse_repository
63 Role.anonymous.remove_permission! :browse_repository
59 with_credentials "miscuser8", "foo" do
64 with_credentials "miscuser8", "foo" do
60 assert_success "ls", svn_url
65 assert_success "ls", svn_url
61 end
66 end
62 end
67 end
63
68
64 def test_non_member_read_on_public_repo_with_non_member_group_permission_should_succeed
69 def test_non_member_read_on_public_repo_with_non_member_group_permission_should_succeed
65 Role.anonymous.remove_permission! :browse_repository
70 Role.anonymous.remove_permission! :browse_repository
66 Role.non_member.remove_permission! :browse_repository
71 Role.non_member.remove_permission! :browse_repository
67 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
72 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
68 with_credentials "miscuser8", "foo" do
73 with_credentials "miscuser8", "foo" do
69 assert_success "ls", svn_url
74 assert_success "ls", svn_url
70 end
75 end
71 end
76 end
72
77
73 def test_non_member_read_on_public_repo_without_permission_should_fail
78 def test_non_member_read_on_public_repo_without_permission_should_fail
74 Role.anonymous.remove_permission! :browse_repository
79 Role.anonymous.remove_permission! :browse_repository
75 Role.non_member.remove_permission! :browse_repository
80 Role.non_member.remove_permission! :browse_repository
76 with_credentials "miscuser8", "foo" do
81 with_credentials "miscuser8", "foo" do
77 assert_failure "ls", svn_url
82 assert_failure "ls", svn_url
78 end
83 end
79 end
84 end
80
85
81 def test_non_member_read_on_private_repo_should_fail
86 def test_non_member_read_on_private_repo_should_fail
82 Project.find(1).update_attribute :is_public, false
87 Project.find(1).update_attribute :is_public, false
83 with_credentials "miscuser8", "foo" do
88 with_credentials "miscuser8", "foo" do
84 assert_failure "ls", svn_url
89 assert_failure "ls", svn_url
85 end
90 end
86 end
91 end
87
92
88 def test_non_member_commit_on_public_repo_should_fail
93 def test_non_member_commit_on_public_repo_should_fail
89 Role.non_member.add_permission! :commit_access
94 Role.non_member.add_permission! :commit_access
90 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
95 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
91 end
96 end
92
97
93 def test_non_member_commit_on_private_repo_should_fail
98 def test_non_member_commit_on_private_repo_should_fail
94 Role.non_member.add_permission! :commit_access
99 Role.non_member.add_permission! :commit_access
95 Project.find(1).update_attribute :is_public, false
100 Project.find(1).update_attribute :is_public, false
96 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
101 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
97 end
102 end
98
103
99 def test_member_read_on_public_repo_with_permission_should_succeed
104 def test_member_read_on_public_repo_with_permission_should_succeed
100 Role.anonymous.remove_permission! :browse_repository
105 Role.anonymous.remove_permission! :browse_repository
101 Role.non_member.remove_permission! :browse_repository
106 Role.non_member.remove_permission! :browse_repository
102 with_credentials "dlopper", "foo" do
107 with_credentials "dlopper", "foo" do
103 assert_success "ls", svn_url
108 assert_success "ls", svn_url
104 end
109 end
105 end
110 end
106
111
107 def test_member_read_on_public_repo_without_permission_should_fail
112 def test_member_read_on_public_repo_without_permission_should_fail
108 Role.anonymous.remove_permission! :browse_repository
113 Role.anonymous.remove_permission! :browse_repository
109 Role.non_member.remove_permission! :browse_repository
114 Role.non_member.remove_permission! :browse_repository
110 Role.find(2).remove_permission! :browse_repository
115 Role.find(2).remove_permission! :browse_repository
111 with_credentials "dlopper", "foo" do
116 with_credentials "dlopper", "foo" do
112 assert_failure "ls", svn_url
117 assert_failure "ls", svn_url
113 end
118 end
114 end
119 end
115
120
116 def test_member_read_on_private_repo_with_permission_should_succeed
121 def test_member_read_on_private_repo_with_permission_should_succeed
117 Project.find(1).update_attribute :is_public, false
122 Project.find(1).update_attribute :is_public, false
118 with_credentials "dlopper", "foo" do
123 with_credentials "dlopper", "foo" do
119 assert_success "ls", svn_url
124 assert_success "ls", svn_url
120 end
125 end
121 end
126 end
122
127
123 def test_member_read_on_private_repo_without_permission_should_fail
128 def test_member_read_on_private_repo_without_permission_should_fail
124 Role.find(2).remove_permission! :browse_repository
129 Role.find(2).remove_permission! :browse_repository
125 Project.find(1).update_attribute :is_public, false
130 Project.find(1).update_attribute :is_public, false
126 with_credentials "dlopper", "foo" do
131 with_credentials "dlopper", "foo" do
127 assert_failure "ls", svn_url
132 assert_failure "ls", svn_url
128 end
133 end
129 end
134 end
130
135
136 def test_member_read_on_private_repo_with_module_disabled_should_fail
137 Role.find(2).add_permission! :browse_repository
138 Project.find(1).update_attribute :is_public, false
139 Project.find(1).disable_module! :repository
140 with_credentials "dlopper", "foo" do
141 assert_failure "ls", svn_url
142 end
143 end
144
131 def test_member_commit_on_public_repo_with_permission_should_succeed
145 def test_member_commit_on_public_repo_with_permission_should_succeed
132 Role.find(2).add_permission! :commit_access
146 Role.find(2).add_permission! :commit_access
133 with_credentials "dlopper", "foo" do
147 with_credentials "dlopper", "foo" do
134 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
148 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
135 end
149 end
136 end
150 end
137
151
138 def test_member_commit_on_public_repo_without_permission_should_fail
152 def test_member_commit_on_public_repo_without_permission_should_fail
139 Role.find(2).remove_permission! :commit_access
153 Role.find(2).remove_permission! :commit_access
140 with_credentials "dlopper", "foo" do
154 with_credentials "dlopper", "foo" do
141 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
155 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
142 end
156 end
143 end
157 end
144
158
145 def test_member_commit_on_private_repo_with_permission_should_succeed
159 def test_member_commit_on_private_repo_with_permission_should_succeed
146 Role.find(2).add_permission! :commit_access
160 Role.find(2).add_permission! :commit_access
147 Project.find(1).update_attribute :is_public, false
161 Project.find(1).update_attribute :is_public, false
148 with_credentials "dlopper", "foo" do
162 with_credentials "dlopper", "foo" do
149 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
163 assert_success "mkdir --message Creating_a_directory", svn_url(random_filename)
150 end
164 end
151 end
165 end
152
166
153 def test_member_commit_on_private_repo_without_permission_should_fail
167 def test_member_commit_on_private_repo_without_permission_should_fail
154 Role.find(2).remove_permission! :commit_access
168 Role.find(2).remove_permission! :commit_access
155 Project.find(1).update_attribute :is_public, false
169 Project.find(1).update_attribute :is_public, false
156 with_credentials "dlopper", "foo" do
170 with_credentials "dlopper", "foo" do
157 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
171 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
158 end
172 end
159 end
173 end
160
174
175 def test_member_commit_on_private_repo_with_module_disabled_should_fail
176 Role.find(2).add_permission! :commit_access
177 Project.find(1).update_attribute :is_public, false
178 Project.find(1).disable_module! :repository
179 with_credentials "dlopper", "foo" do
180 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
181 end
182 end
183
161 def test_invalid_credentials_should_fail
184 def test_invalid_credentials_should_fail
162 Project.find(1).update_attribute :is_public, false
185 Project.find(1).update_attribute :is_public, false
163 with_credentials "dlopper", "foo" do
186 with_credentials "dlopper", "foo" do
164 assert_success "ls", svn_url
187 assert_success "ls", svn_url
165 end
188 end
166 with_credentials "dlopper", "wrong" do
189 with_credentials "dlopper", "wrong" do
167 assert_failure "ls", svn_url
190 assert_failure "ls", svn_url
168 end
191 end
169 end
192 end
170
193
171 def test_anonymous_read_should_fail_with_login_required
194 def test_anonymous_read_should_fail_with_login_required
172 assert_success "ls", svn_url
195 assert_success "ls", svn_url
173 with_settings :login_required => '1' do
196 with_settings :login_required => '1' do
174 assert_failure "ls", svn_url
197 assert_failure "ls", svn_url
175 end
198 end
176 end
199 end
177
200
178 def test_authenticated_read_should_succeed_with_login_required
201 def test_authenticated_read_should_succeed_with_login_required
179 with_settings :login_required => '1' do
202 with_settings :login_required => '1' do
180 with_credentials "miscuser8", "foo" do
203 with_credentials "miscuser8", "foo" do
181 assert_success "ls", svn_url
204 assert_success "ls", svn_url
182 end
205 end
183 end
206 end
184 end
207 end
185
208
186 def test_read_on_archived_projects_should_fail
209 def test_read_on_archived_projects_should_fail
187 Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED
210 Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED
188 assert_failure "ls", svn_url
211 assert_failure "ls", svn_url
189 end
212 end
190
213
191 def test_read_on_archived_private_projects_should_fail
214 def test_read_on_archived_private_projects_should_fail
192 Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED
215 Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED
193 Project.find(1).update_attribute :is_public, false
216 Project.find(1).update_attribute :is_public, false
194 with_credentials "dlopper", "foo" do
217 with_credentials "dlopper", "foo" do
195 assert_failure "ls", svn_url
218 assert_failure "ls", svn_url
196 end
219 end
197 end
220 end
198
221
199 def test_read_on_closed_projects_should_succeed
222 def test_read_on_closed_projects_should_succeed
200 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
223 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
201 assert_success "ls", svn_url
224 assert_success "ls", svn_url
202 end
225 end
203
226
204 def test_read_on_closed_private_projects_should_succeed
227 def test_read_on_closed_private_projects_should_succeed
205 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
228 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
206 Project.find(1).update_attribute :is_public, false
229 Project.find(1).update_attribute :is_public, false
207 with_credentials "dlopper", "foo" do
230 with_credentials "dlopper", "foo" do
208 assert_success "ls", svn_url
231 assert_success "ls", svn_url
209 end
232 end
210 end
233 end
211
234
212 def test_commit_on_closed_projects_should_fail
235 def test_commit_on_closed_projects_should_fail
213 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
236 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
214 Role.find(2).add_permission! :commit_access
237 Role.find(2).add_permission! :commit_access
215 with_credentials "dlopper", "foo" do
238 with_credentials "dlopper", "foo" do
216 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
239 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
217 end
240 end
218 end
241 end
219
242
220 def test_commit_on_closed_private_projects_should_fail
243 def test_commit_on_closed_private_projects_should_fail
221 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
244 Project.find(1).update_attribute :status, Project::STATUS_CLOSED
222 Project.find(1).update_attribute :is_public, false
245 Project.find(1).update_attribute :is_public, false
223 Role.find(2).add_permission! :commit_access
246 Role.find(2).add_permission! :commit_access
224 with_credentials "dlopper", "foo" do
247 with_credentials "dlopper", "foo" do
225 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
248 assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename)
226 end
249 end
227 end
250 end
228
251
229 if ldap_configured?
252 if ldap_configured?
230 def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials
253 def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials
231 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
254 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
232 ldap_user.login = 'example1'
255 ldap_user.login = 'example1'
233 ldap_user.save!
256 ldap_user.save!
234
257
235 with_settings :login_required => '1' do
258 with_settings :login_required => '1' do
236 with_credentials "example1", "123456" do
259 with_credentials "example1", "123456" do
237 assert_success "ls", svn_url
260 assert_success "ls", svn_url
238 end
261 end
239 end
262 end
240
263
241 with_settings :login_required => '1' do
264 with_settings :login_required => '1' do
242 with_credentials "example1", "wrong" do
265 with_credentials "example1", "wrong" do
243 assert_failure "ls", svn_url
266 assert_failure "ls", svn_url
244 end
267 end
245 end
268 end
246 end
269 end
247 end
270 end
248
271
249 def test_checkout
272 def test_checkout
250 Dir.mktmpdir do |dir|
273 Dir.mktmpdir do |dir|
251 assert_success "checkout", svn_url, dir
274 assert_success "checkout", svn_url, dir
252 end
275 end
253 end
276 end
254
277
255 def test_read_commands
278 def test_read_commands
256 assert_success "info", svn_url
279 assert_success "info", svn_url
257 assert_success "ls", svn_url
280 assert_success "ls", svn_url
258 assert_success "log", svn_url
281 assert_success "log", svn_url
259 end
282 end
260
283
261 def test_write_commands
284 def test_write_commands
262 Role.find(2).add_permission! :commit_access
285 Role.find(2).add_permission! :commit_access
263 filename = random_filename
286 filename = random_filename
264
287
265 Dir.mktmpdir do |dir|
288 Dir.mktmpdir do |dir|
266 assert_success "checkout", svn_url, dir
289 assert_success "checkout", svn_url, dir
267 Dir.chdir(dir) do
290 Dir.chdir(dir) do
268 # creates a file in the working copy
291 # creates a file in the working copy
269 f = File.new(File.join(dir, filename), "w")
292 f = File.new(File.join(dir, filename), "w")
270 f.write "test file content"
293 f.write "test file content"
271 f.close
294 f.close
272
295
273 assert_success "add", filename
296 assert_success "add", filename
274 with_credentials "dlopper", "foo" do
297 with_credentials "dlopper", "foo" do
275 assert_success "commit --message Committing_a_file"
298 assert_success "commit --message Committing_a_file"
276 assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy")
299 assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy")
277 assert_success "delete --message Deleting_a_file", svn_url(filename)
300 assert_success "delete --message Deleting_a_file", svn_url(filename)
278 assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir")
301 assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir")
279 end
302 end
280 assert_success "update"
303 assert_success "update"
281
304
282 # checks that the working copy was updated
305 # checks that the working copy was updated
283 assert File.exists?(File.join(dir, "#{filename}_copy"))
306 assert File.exists?(File.join(dir, "#{filename}_copy"))
284 assert File.directory?(File.join(dir, "#{filename}_dir"))
307 assert File.directory?(File.join(dir, "#{filename}_dir"))
285 end
308 end
286 end
309 end
287 end
310 end
288
311
289 def test_read_invalid_repo_should_fail
312 def test_read_invalid_repo_should_fail
290 assert_failure "ls", svn_url("invalid")
313 assert_failure "ls", svn_url("invalid")
291 end
314 end
292
315
293 protected
316 protected
294
317
295 def execute(*args)
318 def execute(*args)
296 a = [SVN_BIN, "--no-auth-cache --non-interactive"]
319 a = [SVN_BIN, "--no-auth-cache --non-interactive"]
297 a << "--username #{username}" if username
320 a << "--username #{username}" if username
298 a << "--password #{password}" if password
321 a << "--password #{password}" if password
299
322
300 super a, *args
323 super a, *args
301 end
324 end
302
325
303 def svn_url(path=nil)
326 def svn_url(path=nil)
304 host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1'
327 host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1'
305 url = "http://#{host}/svn/ecookbook"
328 url = "http://#{host}/svn/ecookbook"
306 url << "/#{path}" if path
329 url << "/#{path}" if path
307 url
330 url
308 end
331 end
309 end
332 end
General Comments 0
You need to be logged in to leave comments. Login now