##// END OF EJS Templates
authentication: introduce login restriction option for builtin rhodecode plugin.
marcink -
r3387:8a62bda2 default
parent child Browse files
Show More
@@ -1,559 +1,569 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
27 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
28 no_newline_id_generator)
28 no_newline_id_generator)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.lib.auth import check_password
30 from rhodecode.lib.auth import check_password
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.db import User, Notification, UserApiKeys
33 from rhodecode.model.db import User, Notification, UserApiKeys
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35
35
36 fixture = Fixture()
36 fixture = Fixture()
37
37
38 whitelist_view = ['RepoCommitsView:repo_commit_raw']
38 whitelist_view = ['RepoCommitsView:repo_commit_raw']
39
39
40
40
41 def route_path(name, params=None, **kwargs):
41 def route_path(name, params=None, **kwargs):
42 import urllib
42 import urllib
43 from rhodecode.apps._base import ADMIN_PREFIX
43 from rhodecode.apps._base import ADMIN_PREFIX
44
44
45 base_url = {
45 base_url = {
46 'login': ADMIN_PREFIX + '/login',
46 'login': ADMIN_PREFIX + '/login',
47 'logout': ADMIN_PREFIX + '/logout',
47 'logout': ADMIN_PREFIX + '/logout',
48 'register': ADMIN_PREFIX + '/register',
48 'register': ADMIN_PREFIX + '/register',
49 'reset_password':
49 'reset_password':
50 ADMIN_PREFIX + '/password_reset',
50 ADMIN_PREFIX + '/password_reset',
51 'reset_password_confirmation':
51 'reset_password_confirmation':
52 ADMIN_PREFIX + '/password_reset_confirmation',
52 ADMIN_PREFIX + '/password_reset_confirmation',
53
53
54 'admin_permissions_application':
54 'admin_permissions_application':
55 ADMIN_PREFIX + '/permissions/application',
55 ADMIN_PREFIX + '/permissions/application',
56 'admin_permissions_application_update':
56 'admin_permissions_application_update':
57 ADMIN_PREFIX + '/permissions/application/update',
57 ADMIN_PREFIX + '/permissions/application/update',
58
58
59 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}'
59 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}'
60
60
61 }[name].format(**kwargs)
61 }[name].format(**kwargs)
62
62
63 if params:
63 if params:
64 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
64 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
65 return base_url
65 return base_url
66
66
67
67
68 @pytest.mark.usefixtures('app')
68 @pytest.mark.usefixtures('app')
69 class TestLoginController(object):
69 class TestLoginController(object):
70 destroy_users = set()
70 destroy_users = set()
71
71
72 @classmethod
72 @classmethod
73 def teardown_class(cls):
73 def teardown_class(cls):
74 fixture.destroy_users(cls.destroy_users)
74 fixture.destroy_users(cls.destroy_users)
75
75
76 def teardown_method(self, method):
76 def teardown_method(self, method):
77 for n in Notification.query().all():
77 for n in Notification.query().all():
78 Session().delete(n)
78 Session().delete(n)
79
79
80 Session().commit()
80 Session().commit()
81 assert Notification.query().all() == []
81 assert Notification.query().all() == []
82
82
83 def test_index(self):
83 def test_index(self):
84 response = self.app.get(route_path('login'))
84 response = self.app.get(route_path('login'))
85 assert response.status == '200 OK'
85 assert response.status == '200 OK'
86 # Test response...
86 # Test response...
87
87
88 def test_login_admin_ok(self):
88 def test_login_admin_ok(self):
89 response = self.app.post(route_path('login'),
89 response = self.app.post(route_path('login'),
90 {'username': 'test_admin',
90 {'username': 'test_admin',
91 'password': 'test12'}, status=302)
91 'password': 'test12'}, status=302)
92 response = response.follow()
92 response = response.follow()
93 session = response.get_session_from_response()
93 session = response.get_session_from_response()
94 username = session['rhodecode_user'].get('username')
94 username = session['rhodecode_user'].get('username')
95 assert username == 'test_admin'
95 assert username == 'test_admin'
96 response.mustcontain('/%s' % HG_REPO)
96 response.mustcontain('/%s' % HG_REPO)
97
97
98 def test_login_regular_ok(self):
98 def test_login_regular_ok(self):
99 response = self.app.post(route_path('login'),
99 response = self.app.post(route_path('login'),
100 {'username': 'test_regular',
100 {'username': 'test_regular',
101 'password': 'test12'}, status=302)
101 'password': 'test12'}, status=302)
102
102
103 response = response.follow()
103 response = response.follow()
104 session = response.get_session_from_response()
104 session = response.get_session_from_response()
105 username = session['rhodecode_user'].get('username')
105 username = session['rhodecode_user'].get('username')
106 assert username == 'test_regular'
106 assert username == 'test_regular'
107
107
108 response.mustcontain('/%s' % HG_REPO)
108 response.mustcontain('/%s' % HG_REPO)
109
109
110 def test_login_regular_forbidden_when_super_admin_restriction(self):
111 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
112 with fixture.login_restriction(RhodeCodeAuthPlugin.LOGIN_RESTRICTION_SUPER_ADMIN):
113 response = self.app.post(route_path('login'),
114 {'username': 'test_regular',
115 'password': 'test12'})
116
117 response.mustcontain('invalid user name')
118 response.mustcontain('invalid password')
119
110 def test_login_ok_came_from(self):
120 def test_login_ok_came_from(self):
111 test_came_from = '/_admin/users?branch=stable'
121 test_came_from = '/_admin/users?branch=stable'
112 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
122 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
113 response = self.app.post(
123 response = self.app.post(
114 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
124 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
115
125
116 assert 'branch=stable' in response.location
126 assert 'branch=stable' in response.location
117 response = response.follow()
127 response = response.follow()
118
128
119 assert response.status == '200 OK'
129 assert response.status == '200 OK'
120 response.mustcontain('Users administration')
130 response.mustcontain('Users administration')
121
131
122 def test_redirect_to_login_with_get_args(self):
132 def test_redirect_to_login_with_get_args(self):
123 with fixture.anon_access(False):
133 with fixture.anon_access(False):
124 kwargs = {'branch': 'stable'}
134 kwargs = {'branch': 'stable'}
125 response = self.app.get(
135 response = self.app.get(
126 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs),
136 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs),
127 status=302)
137 status=302)
128
138
129 response_query = urlparse.parse_qsl(response.location)
139 response_query = urlparse.parse_qsl(response.location)
130 assert 'branch=stable' in response_query[0][1]
140 assert 'branch=stable' in response_query[0][1]
131
141
132 def test_login_form_with_get_args(self):
142 def test_login_form_with_get_args(self):
133 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
143 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
134 response = self.app.get(_url)
144 response = self.app.get(_url)
135 assert 'branch%3Dstable' in response.form.action
145 assert 'branch%3Dstable' in response.form.action
136
146
137 @pytest.mark.parametrize("url_came_from", [
147 @pytest.mark.parametrize("url_came_from", [
138 'data:text/html,<script>window.alert("xss")</script>',
148 'data:text/html,<script>window.alert("xss")</script>',
139 'mailto:test@rhodecode.org',
149 'mailto:test@rhodecode.org',
140 'file:///etc/passwd',
150 'file:///etc/passwd',
141 'ftp://some.ftp.server',
151 'ftp://some.ftp.server',
142 'http://other.domain',
152 'http://other.domain',
143 '/\r\nX-Forwarded-Host: http://example.org',
153 '/\r\nX-Forwarded-Host: http://example.org',
144 ], ids=no_newline_id_generator)
154 ], ids=no_newline_id_generator)
145 def test_login_bad_came_froms(self, url_came_from):
155 def test_login_bad_came_froms(self, url_came_from):
146 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
156 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
147 response = self.app.post(
157 response = self.app.post(
148 _url,
158 _url,
149 {'username': 'test_admin', 'password': 'test12'})
159 {'username': 'test_admin', 'password': 'test12'})
150 assert response.status == '302 Found'
160 assert response.status == '302 Found'
151 response = response.follow()
161 response = response.follow()
152 assert response.status == '200 OK'
162 assert response.status == '200 OK'
153 assert response.request.path == '/'
163 assert response.request.path == '/'
154
164
155 def test_login_short_password(self):
165 def test_login_short_password(self):
156 response = self.app.post(route_path('login'),
166 response = self.app.post(route_path('login'),
157 {'username': 'test_admin',
167 {'username': 'test_admin',
158 'password': 'as'})
168 'password': 'as'})
159 assert response.status == '200 OK'
169 assert response.status == '200 OK'
160
170
161 response.mustcontain('Enter 3 characters or more')
171 response.mustcontain('Enter 3 characters or more')
162
172
163 def test_login_wrong_non_ascii_password(self, user_regular):
173 def test_login_wrong_non_ascii_password(self, user_regular):
164 response = self.app.post(
174 response = self.app.post(
165 route_path('login'),
175 route_path('login'),
166 {'username': user_regular.username,
176 {'username': user_regular.username,
167 'password': u'invalid-non-asci\xe4'.encode('utf8')})
177 'password': u'invalid-non-asci\xe4'.encode('utf8')})
168
178
169 response.mustcontain('invalid user name')
179 response.mustcontain('invalid user name')
170 response.mustcontain('invalid password')
180 response.mustcontain('invalid password')
171
181
172 def test_login_with_non_ascii_password(self, user_util):
182 def test_login_with_non_ascii_password(self, user_util):
173 password = u'valid-non-ascii\xe4'
183 password = u'valid-non-ascii\xe4'
174 user = user_util.create_user(password=password)
184 user = user_util.create_user(password=password)
175 response = self.app.post(
185 response = self.app.post(
176 route_path('login'),
186 route_path('login'),
177 {'username': user.username,
187 {'username': user.username,
178 'password': password.encode('utf-8')})
188 'password': password.encode('utf-8')})
179 assert response.status_code == 302
189 assert response.status_code == 302
180
190
181 def test_login_wrong_username_password(self):
191 def test_login_wrong_username_password(self):
182 response = self.app.post(route_path('login'),
192 response = self.app.post(route_path('login'),
183 {'username': 'error',
193 {'username': 'error',
184 'password': 'test12'})
194 'password': 'test12'})
185
195
186 response.mustcontain('invalid user name')
196 response.mustcontain('invalid user name')
187 response.mustcontain('invalid password')
197 response.mustcontain('invalid password')
188
198
189 def test_login_admin_ok_password_migration(self, real_crypto_backend):
199 def test_login_admin_ok_password_migration(self, real_crypto_backend):
190 from rhodecode.lib import auth
200 from rhodecode.lib import auth
191
201
192 # create new user, with sha256 password
202 # create new user, with sha256 password
193 temp_user = 'test_admin_sha256'
203 temp_user = 'test_admin_sha256'
194 user = fixture.create_user(temp_user)
204 user = fixture.create_user(temp_user)
195 user.password = auth._RhodeCodeCryptoSha256().hash_create(
205 user.password = auth._RhodeCodeCryptoSha256().hash_create(
196 b'test123')
206 b'test123')
197 Session().add(user)
207 Session().add(user)
198 Session().commit()
208 Session().commit()
199 self.destroy_users.add(temp_user)
209 self.destroy_users.add(temp_user)
200 response = self.app.post(route_path('login'),
210 response = self.app.post(route_path('login'),
201 {'username': temp_user,
211 {'username': temp_user,
202 'password': 'test123'}, status=302)
212 'password': 'test123'}, status=302)
203
213
204 response = response.follow()
214 response = response.follow()
205 session = response.get_session_from_response()
215 session = response.get_session_from_response()
206 username = session['rhodecode_user'].get('username')
216 username = session['rhodecode_user'].get('username')
207 assert username == temp_user
217 assert username == temp_user
208 response.mustcontain('/%s' % HG_REPO)
218 response.mustcontain('/%s' % HG_REPO)
209
219
210 # new password should be bcrypted, after log-in and transfer
220 # new password should be bcrypted, after log-in and transfer
211 user = User.get_by_username(temp_user)
221 user = User.get_by_username(temp_user)
212 assert user.password.startswith('$')
222 assert user.password.startswith('$')
213
223
214 # REGISTRATIONS
224 # REGISTRATIONS
215 def test_register(self):
225 def test_register(self):
216 response = self.app.get(route_path('register'))
226 response = self.app.get(route_path('register'))
217 response.mustcontain('Create an Account')
227 response.mustcontain('Create an Account')
218
228
219 def test_register_err_same_username(self):
229 def test_register_err_same_username(self):
220 uname = 'test_admin'
230 uname = 'test_admin'
221 response = self.app.post(
231 response = self.app.post(
222 route_path('register'),
232 route_path('register'),
223 {
233 {
224 'username': uname,
234 'username': uname,
225 'password': 'test12',
235 'password': 'test12',
226 'password_confirmation': 'test12',
236 'password_confirmation': 'test12',
227 'email': 'goodmail@domain.com',
237 'email': 'goodmail@domain.com',
228 'firstname': 'test',
238 'firstname': 'test',
229 'lastname': 'test'
239 'lastname': 'test'
230 }
240 }
231 )
241 )
232
242
233 assertr = response.assert_response()
243 assertr = response.assert_response()
234 msg = 'Username "%(username)s" already exists'
244 msg = 'Username "%(username)s" already exists'
235 msg = msg % {'username': uname}
245 msg = msg % {'username': uname}
236 assertr.element_contains('#username+.error-message', msg)
246 assertr.element_contains('#username+.error-message', msg)
237
247
238 def test_register_err_same_email(self):
248 def test_register_err_same_email(self):
239 response = self.app.post(
249 response = self.app.post(
240 route_path('register'),
250 route_path('register'),
241 {
251 {
242 'username': 'test_admin_0',
252 'username': 'test_admin_0',
243 'password': 'test12',
253 'password': 'test12',
244 'password_confirmation': 'test12',
254 'password_confirmation': 'test12',
245 'email': 'test_admin@mail.com',
255 'email': 'test_admin@mail.com',
246 'firstname': 'test',
256 'firstname': 'test',
247 'lastname': 'test'
257 'lastname': 'test'
248 }
258 }
249 )
259 )
250
260
251 assertr = response.assert_response()
261 assertr = response.assert_response()
252 msg = u'This e-mail address is already taken'
262 msg = u'This e-mail address is already taken'
253 assertr.element_contains('#email+.error-message', msg)
263 assertr.element_contains('#email+.error-message', msg)
254
264
255 def test_register_err_same_email_case_sensitive(self):
265 def test_register_err_same_email_case_sensitive(self):
256 response = self.app.post(
266 response = self.app.post(
257 route_path('register'),
267 route_path('register'),
258 {
268 {
259 'username': 'test_admin_1',
269 'username': 'test_admin_1',
260 'password': 'test12',
270 'password': 'test12',
261 'password_confirmation': 'test12',
271 'password_confirmation': 'test12',
262 'email': 'TesT_Admin@mail.COM',
272 'email': 'TesT_Admin@mail.COM',
263 'firstname': 'test',
273 'firstname': 'test',
264 'lastname': 'test'
274 'lastname': 'test'
265 }
275 }
266 )
276 )
267 assertr = response.assert_response()
277 assertr = response.assert_response()
268 msg = u'This e-mail address is already taken'
278 msg = u'This e-mail address is already taken'
269 assertr.element_contains('#email+.error-message', msg)
279 assertr.element_contains('#email+.error-message', msg)
270
280
271 def test_register_err_wrong_data(self):
281 def test_register_err_wrong_data(self):
272 response = self.app.post(
282 response = self.app.post(
273 route_path('register'),
283 route_path('register'),
274 {
284 {
275 'username': 'xs',
285 'username': 'xs',
276 'password': 'test',
286 'password': 'test',
277 'password_confirmation': 'test',
287 'password_confirmation': 'test',
278 'email': 'goodmailm',
288 'email': 'goodmailm',
279 'firstname': 'test',
289 'firstname': 'test',
280 'lastname': 'test'
290 'lastname': 'test'
281 }
291 }
282 )
292 )
283 assert response.status == '200 OK'
293 assert response.status == '200 OK'
284 response.mustcontain('An email address must contain a single @')
294 response.mustcontain('An email address must contain a single @')
285 response.mustcontain('Enter a value 6 characters long or more')
295 response.mustcontain('Enter a value 6 characters long or more')
286
296
287 def test_register_err_username(self):
297 def test_register_err_username(self):
288 response = self.app.post(
298 response = self.app.post(
289 route_path('register'),
299 route_path('register'),
290 {
300 {
291 'username': 'error user',
301 'username': 'error user',
292 'password': 'test12',
302 'password': 'test12',
293 'password_confirmation': 'test12',
303 'password_confirmation': 'test12',
294 'email': 'goodmailm',
304 'email': 'goodmailm',
295 'firstname': 'test',
305 'firstname': 'test',
296 'lastname': 'test'
306 'lastname': 'test'
297 }
307 }
298 )
308 )
299
309
300 response.mustcontain('An email address must contain a single @')
310 response.mustcontain('An email address must contain a single @')
301 response.mustcontain(
311 response.mustcontain(
302 'Username may only contain '
312 'Username may only contain '
303 'alphanumeric characters underscores, '
313 'alphanumeric characters underscores, '
304 'periods or dashes and must begin with '
314 'periods or dashes and must begin with '
305 'alphanumeric character')
315 'alphanumeric character')
306
316
307 def test_register_err_case_sensitive(self):
317 def test_register_err_case_sensitive(self):
308 usr = 'Test_Admin'
318 usr = 'Test_Admin'
309 response = self.app.post(
319 response = self.app.post(
310 route_path('register'),
320 route_path('register'),
311 {
321 {
312 'username': usr,
322 'username': usr,
313 'password': 'test12',
323 'password': 'test12',
314 'password_confirmation': 'test12',
324 'password_confirmation': 'test12',
315 'email': 'goodmailm',
325 'email': 'goodmailm',
316 'firstname': 'test',
326 'firstname': 'test',
317 'lastname': 'test'
327 'lastname': 'test'
318 }
328 }
319 )
329 )
320
330
321 assertr = response.assert_response()
331 assertr = response.assert_response()
322 msg = u'Username "%(username)s" already exists'
332 msg = u'Username "%(username)s" already exists'
323 msg = msg % {'username': usr}
333 msg = msg % {'username': usr}
324 assertr.element_contains('#username+.error-message', msg)
334 assertr.element_contains('#username+.error-message', msg)
325
335
326 def test_register_special_chars(self):
336 def test_register_special_chars(self):
327 response = self.app.post(
337 response = self.app.post(
328 route_path('register'),
338 route_path('register'),
329 {
339 {
330 'username': 'xxxaxn',
340 'username': 'xxxaxn',
331 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
341 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
332 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
342 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
333 'email': 'goodmailm@test.plx',
343 'email': 'goodmailm@test.plx',
334 'firstname': 'test',
344 'firstname': 'test',
335 'lastname': 'test'
345 'lastname': 'test'
336 }
346 }
337 )
347 )
338
348
339 msg = u'Invalid characters (non-ascii) in password'
349 msg = u'Invalid characters (non-ascii) in password'
340 response.mustcontain(msg)
350 response.mustcontain(msg)
341
351
342 def test_register_password_mismatch(self):
352 def test_register_password_mismatch(self):
343 response = self.app.post(
353 response = self.app.post(
344 route_path('register'),
354 route_path('register'),
345 {
355 {
346 'username': 'xs',
356 'username': 'xs',
347 'password': '123qwe',
357 'password': '123qwe',
348 'password_confirmation': 'qwe123',
358 'password_confirmation': 'qwe123',
349 'email': 'goodmailm@test.plxa',
359 'email': 'goodmailm@test.plxa',
350 'firstname': 'test',
360 'firstname': 'test',
351 'lastname': 'test'
361 'lastname': 'test'
352 }
362 }
353 )
363 )
354 msg = u'Passwords do not match'
364 msg = u'Passwords do not match'
355 response.mustcontain(msg)
365 response.mustcontain(msg)
356
366
357 def test_register_ok(self):
367 def test_register_ok(self):
358 username = 'test_regular4'
368 username = 'test_regular4'
359 password = 'qweqwe'
369 password = 'qweqwe'
360 email = 'marcin@test.com'
370 email = 'marcin@test.com'
361 name = 'testname'
371 name = 'testname'
362 lastname = 'testlastname'
372 lastname = 'testlastname'
363
373
364 # this initializes a session
374 # this initializes a session
365 response = self.app.get(route_path('register'))
375 response = self.app.get(route_path('register'))
366 response.mustcontain('Create an Account')
376 response.mustcontain('Create an Account')
367
377
368
378
369 response = self.app.post(
379 response = self.app.post(
370 route_path('register'),
380 route_path('register'),
371 {
381 {
372 'username': username,
382 'username': username,
373 'password': password,
383 'password': password,
374 'password_confirmation': password,
384 'password_confirmation': password,
375 'email': email,
385 'email': email,
376 'firstname': name,
386 'firstname': name,
377 'lastname': lastname,
387 'lastname': lastname,
378 'admin': True
388 'admin': True
379 },
389 },
380 status=302
390 status=302
381 ) # This should be overridden
391 ) # This should be overridden
382
392
383 assert_session_flash(
393 assert_session_flash(
384 response, 'You have successfully registered with RhodeCode')
394 response, 'You have successfully registered with RhodeCode')
385
395
386 ret = Session().query(User).filter(
396 ret = Session().query(User).filter(
387 User.username == 'test_regular4').one()
397 User.username == 'test_regular4').one()
388 assert ret.username == username
398 assert ret.username == username
389 assert check_password(password, ret.password)
399 assert check_password(password, ret.password)
390 assert ret.email == email
400 assert ret.email == email
391 assert ret.name == name
401 assert ret.name == name
392 assert ret.lastname == lastname
402 assert ret.lastname == lastname
393 assert ret.auth_tokens is not None
403 assert ret.auth_tokens is not None
394 assert not ret.admin
404 assert not ret.admin
395
405
396 def test_forgot_password_wrong_mail(self):
406 def test_forgot_password_wrong_mail(self):
397 bad_email = 'marcin@wrongmail.org'
407 bad_email = 'marcin@wrongmail.org'
398 # this initializes a session
408 # this initializes a session
399 self.app.get(route_path('reset_password'))
409 self.app.get(route_path('reset_password'))
400
410
401 response = self.app.post(
411 response = self.app.post(
402 route_path('reset_password'), {'email': bad_email, }
412 route_path('reset_password'), {'email': bad_email, }
403 )
413 )
404 assert_session_flash(response,
414 assert_session_flash(response,
405 'If such email exists, a password reset link was sent to it.')
415 'If such email exists, a password reset link was sent to it.')
406
416
407 def test_forgot_password(self, user_util):
417 def test_forgot_password(self, user_util):
408 # this initializes a session
418 # this initializes a session
409 self.app.get(route_path('reset_password'))
419 self.app.get(route_path('reset_password'))
410
420
411 user = user_util.create_user()
421 user = user_util.create_user()
412 user_id = user.user_id
422 user_id = user.user_id
413 email = user.email
423 email = user.email
414
424
415 response = self.app.post(route_path('reset_password'), {'email': email, })
425 response = self.app.post(route_path('reset_password'), {'email': email, })
416
426
417 assert_session_flash(response,
427 assert_session_flash(response,
418 'If such email exists, a password reset link was sent to it.')
428 'If such email exists, a password reset link was sent to it.')
419
429
420 # BAD KEY
430 # BAD KEY
421 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
431 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
422 response = self.app.get(confirm_url, status=302)
432 response = self.app.get(confirm_url, status=302)
423 assert response.location.endswith(route_path('reset_password'))
433 assert response.location.endswith(route_path('reset_password'))
424 assert_session_flash(response, 'Given reset token is invalid')
434 assert_session_flash(response, 'Given reset token is invalid')
425
435
426 response.follow() # cleanup flash
436 response.follow() # cleanup flash
427
437
428 # GOOD KEY
438 # GOOD KEY
429 key = UserApiKeys.query()\
439 key = UserApiKeys.query()\
430 .filter(UserApiKeys.user_id == user_id)\
440 .filter(UserApiKeys.user_id == user_id)\
431 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
441 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
432 .first()
442 .first()
433
443
434 assert key
444 assert key
435
445
436 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
446 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
437 response = self.app.get(confirm_url)
447 response = self.app.get(confirm_url)
438 assert response.status == '302 Found'
448 assert response.status == '302 Found'
439 assert response.location.endswith(route_path('login'))
449 assert response.location.endswith(route_path('login'))
440
450
441 assert_session_flash(
451 assert_session_flash(
442 response,
452 response,
443 'Your password reset was successful, '
453 'Your password reset was successful, '
444 'a new password has been sent to your email')
454 'a new password has been sent to your email')
445
455
446 response.follow()
456 response.follow()
447
457
448 def _get_api_whitelist(self, values=None):
458 def _get_api_whitelist(self, values=None):
449 config = {'api_access_controllers_whitelist': values or []}
459 config = {'api_access_controllers_whitelist': values or []}
450 return config
460 return config
451
461
452 @pytest.mark.parametrize("test_name, auth_token", [
462 @pytest.mark.parametrize("test_name, auth_token", [
453 ('none', None),
463 ('none', None),
454 ('empty_string', ''),
464 ('empty_string', ''),
455 ('fake_number', '123456'),
465 ('fake_number', '123456'),
456 ('proper_auth_token', None)
466 ('proper_auth_token', None)
457 ])
467 ])
458 def test_access_not_whitelisted_page_via_auth_token(
468 def test_access_not_whitelisted_page_via_auth_token(
459 self, test_name, auth_token, user_admin):
469 self, test_name, auth_token, user_admin):
460
470
461 whitelist = self._get_api_whitelist([])
471 whitelist = self._get_api_whitelist([])
462 with mock.patch.dict('rhodecode.CONFIG', whitelist):
472 with mock.patch.dict('rhodecode.CONFIG', whitelist):
463 assert [] == whitelist['api_access_controllers_whitelist']
473 assert [] == whitelist['api_access_controllers_whitelist']
464 if test_name == 'proper_auth_token':
474 if test_name == 'proper_auth_token':
465 # use builtin if api_key is None
475 # use builtin if api_key is None
466 auth_token = user_admin.api_key
476 auth_token = user_admin.api_key
467
477
468 with fixture.anon_access(False):
478 with fixture.anon_access(False):
469 self.app.get(
479 self.app.get(
470 route_path('repo_commit_raw',
480 route_path('repo_commit_raw',
471 repo_name=HG_REPO, commit_id='tip',
481 repo_name=HG_REPO, commit_id='tip',
472 params=dict(api_key=auth_token)),
482 params=dict(api_key=auth_token)),
473 status=302)
483 status=302)
474
484
475 @pytest.mark.parametrize("test_name, auth_token, code", [
485 @pytest.mark.parametrize("test_name, auth_token, code", [
476 ('none', None, 302),
486 ('none', None, 302),
477 ('empty_string', '', 302),
487 ('empty_string', '', 302),
478 ('fake_number', '123456', 302),
488 ('fake_number', '123456', 302),
479 ('proper_auth_token', None, 200)
489 ('proper_auth_token', None, 200)
480 ])
490 ])
481 def test_access_whitelisted_page_via_auth_token(
491 def test_access_whitelisted_page_via_auth_token(
482 self, test_name, auth_token, code, user_admin):
492 self, test_name, auth_token, code, user_admin):
483
493
484 whitelist = self._get_api_whitelist(whitelist_view)
494 whitelist = self._get_api_whitelist(whitelist_view)
485
495
486 with mock.patch.dict('rhodecode.CONFIG', whitelist):
496 with mock.patch.dict('rhodecode.CONFIG', whitelist):
487 assert whitelist_view == whitelist['api_access_controllers_whitelist']
497 assert whitelist_view == whitelist['api_access_controllers_whitelist']
488
498
489 if test_name == 'proper_auth_token':
499 if test_name == 'proper_auth_token':
490 auth_token = user_admin.api_key
500 auth_token = user_admin.api_key
491 assert auth_token
501 assert auth_token
492
502
493 with fixture.anon_access(False):
503 with fixture.anon_access(False):
494 self.app.get(
504 self.app.get(
495 route_path('repo_commit_raw',
505 route_path('repo_commit_raw',
496 repo_name=HG_REPO, commit_id='tip',
506 repo_name=HG_REPO, commit_id='tip',
497 params=dict(api_key=auth_token)),
507 params=dict(api_key=auth_token)),
498 status=code)
508 status=code)
499
509
500 @pytest.mark.parametrize("test_name, auth_token, code", [
510 @pytest.mark.parametrize("test_name, auth_token, code", [
501 ('proper_auth_token', None, 200),
511 ('proper_auth_token', None, 200),
502 ('wrong_auth_token', '123456', 302),
512 ('wrong_auth_token', '123456', 302),
503 ])
513 ])
504 def test_access_whitelisted_page_via_auth_token_bound_to_token(
514 def test_access_whitelisted_page_via_auth_token_bound_to_token(
505 self, test_name, auth_token, code, user_admin):
515 self, test_name, auth_token, code, user_admin):
506
516
507 expected_token = auth_token
517 expected_token = auth_token
508 if test_name == 'proper_auth_token':
518 if test_name == 'proper_auth_token':
509 auth_token = user_admin.api_key
519 auth_token = user_admin.api_key
510 expected_token = auth_token
520 expected_token = auth_token
511 assert auth_token
521 assert auth_token
512
522
513 whitelist = self._get_api_whitelist([
523 whitelist = self._get_api_whitelist([
514 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
524 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
515
525
516 with mock.patch.dict('rhodecode.CONFIG', whitelist):
526 with mock.patch.dict('rhodecode.CONFIG', whitelist):
517
527
518 with fixture.anon_access(False):
528 with fixture.anon_access(False):
519 self.app.get(
529 self.app.get(
520 route_path('repo_commit_raw',
530 route_path('repo_commit_raw',
521 repo_name=HG_REPO, commit_id='tip',
531 repo_name=HG_REPO, commit_id='tip',
522 params=dict(api_key=auth_token)),
532 params=dict(api_key=auth_token)),
523 status=code)
533 status=code)
524
534
525 def test_access_page_via_extra_auth_token(self):
535 def test_access_page_via_extra_auth_token(self):
526 whitelist = self._get_api_whitelist(whitelist_view)
536 whitelist = self._get_api_whitelist(whitelist_view)
527 with mock.patch.dict('rhodecode.CONFIG', whitelist):
537 with mock.patch.dict('rhodecode.CONFIG', whitelist):
528 assert whitelist_view == \
538 assert whitelist_view == \
529 whitelist['api_access_controllers_whitelist']
539 whitelist['api_access_controllers_whitelist']
530
540
531 new_auth_token = AuthTokenModel().create(
541 new_auth_token = AuthTokenModel().create(
532 TEST_USER_ADMIN_LOGIN, 'test')
542 TEST_USER_ADMIN_LOGIN, 'test')
533 Session().commit()
543 Session().commit()
534 with fixture.anon_access(False):
544 with fixture.anon_access(False):
535 self.app.get(
545 self.app.get(
536 route_path('repo_commit_raw',
546 route_path('repo_commit_raw',
537 repo_name=HG_REPO, commit_id='tip',
547 repo_name=HG_REPO, commit_id='tip',
538 params=dict(api_key=new_auth_token.api_key)),
548 params=dict(api_key=new_auth_token.api_key)),
539 status=200)
549 status=200)
540
550
541 def test_access_page_via_expired_auth_token(self):
551 def test_access_page_via_expired_auth_token(self):
542 whitelist = self._get_api_whitelist(whitelist_view)
552 whitelist = self._get_api_whitelist(whitelist_view)
543 with mock.patch.dict('rhodecode.CONFIG', whitelist):
553 with mock.patch.dict('rhodecode.CONFIG', whitelist):
544 assert whitelist_view == \
554 assert whitelist_view == \
545 whitelist['api_access_controllers_whitelist']
555 whitelist['api_access_controllers_whitelist']
546
556
547 new_auth_token = AuthTokenModel().create(
557 new_auth_token = AuthTokenModel().create(
548 TEST_USER_ADMIN_LOGIN, 'test')
558 TEST_USER_ADMIN_LOGIN, 'test')
549 Session().commit()
559 Session().commit()
550 # patch the api key and make it expired
560 # patch the api key and make it expired
551 new_auth_token.expires = 0
561 new_auth_token.expires = 0
552 Session().add(new_auth_token)
562 Session().add(new_auth_token)
553 Session().commit()
563 Session().commit()
554 with fixture.anon_access(False):
564 with fixture.anon_access(False):
555 self.app.get(
565 self.app.get(
556 route_path('repo_commit_raw',
566 route_path('repo_commit_raw',
557 repo_name=HG_REPO, commit_id='tip',
567 repo_name=HG_REPO, commit_id='tip',
558 params=dict(api_key=new_auth_token.api_key)),
568 params=dict(api_key=new_auth_token.api_key)),
559 status=302)
569 status=302)
@@ -1,171 +1,186 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for built in internal auth
22 RhodeCode authentication plugin for built in internal auth
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 import colander
27 import colander
28
28
29 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
29 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
30 from rhodecode.translation import _
30 from rhodecode.translation import _
31
31
32 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property
32 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property
33 from rhodecode.authentication.routes import AuthnPluginResourceBase
33 from rhodecode.authentication.routes import AuthnPluginResourceBase
34 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
35 from rhodecode.model.db import User
35 from rhodecode.model.db import User
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 def plugin_factory(plugin_id, *args, **kwargs):
40 def plugin_factory(plugin_id, *args, **kwargs):
41 plugin = RhodeCodeAuthPlugin(plugin_id)
41 plugin = RhodeCodeAuthPlugin(plugin_id)
42 return plugin
42 return plugin
43
43
44
44
45 class RhodecodeAuthnResource(AuthnPluginResourceBase):
45 class RhodecodeAuthnResource(AuthnPluginResourceBase):
46 pass
46 pass
47
47
48
48
49 class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase):
50
51 superadmin_restriction = colander.SchemaNode(
52 colander.Bool(),
53 default=False,
54 description=_('Only allow super-admins to log-in using this plugin.'),
55 missing=False,
56 title=_('Enabled'),
57 widget='bool',
58 )
59
60
61 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
49 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
62 uid = 'rhodecode'
50 uid = 'rhodecode'
51 LOGIN_RESTRICTION_NONE = 'none'
52 LOGIN_RESTRICTION_SUPER_ADMIN = 'super_admin'
63
53
64 def includeme(self, config):
54 def includeme(self, config):
65 config.add_authn_plugin(self)
55 config.add_authn_plugin(self)
66 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
56 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
67 config.add_view(
57 config.add_view(
68 'rhodecode.authentication.views.AuthnPluginViewBase',
58 'rhodecode.authentication.views.AuthnPluginViewBase',
69 attr='settings_get',
59 attr='settings_get',
70 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
60 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
71 request_method='GET',
61 request_method='GET',
72 route_name='auth_home',
62 route_name='auth_home',
73 context=RhodecodeAuthnResource)
63 context=RhodecodeAuthnResource)
74 config.add_view(
64 config.add_view(
75 'rhodecode.authentication.views.AuthnPluginViewBase',
65 'rhodecode.authentication.views.AuthnPluginViewBase',
76 attr='settings_post',
66 attr='settings_post',
77 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
67 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
78 request_method='POST',
68 request_method='POST',
79 route_name='auth_home',
69 route_name='auth_home',
80 context=RhodecodeAuthnResource)
70 context=RhodecodeAuthnResource)
81
71
82 def get_settings_schema(self):
72 def get_settings_schema(self):
83 return RhodeCodeSettingsSchema()
73 return RhodeCodeSettingsSchema()
84
74
85 def get_display_name(self):
75 def get_display_name(self):
86 return _('RhodeCode Internal')
76 return _('RhodeCode Internal')
87
77
88 @classmethod
78 @classmethod
89 def docs(cls):
79 def docs(cls):
90 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth.html"
80 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth.html"
91
81
92 @hybrid_property
82 @hybrid_property
93 def name(self):
83 def name(self):
94 return u"rhodecode"
84 return u"rhodecode"
95
85
96 def user_activation_state(self):
86 def user_activation_state(self):
97 def_user_perms = User.get_default_user().AuthUser().permissions['global']
87 def_user_perms = User.get_default_user().AuthUser().permissions['global']
98 return 'hg.register.auto_activate' in def_user_perms
88 return 'hg.register.auto_activate' in def_user_perms
99
89
100 def allows_authentication_from(
90 def allows_authentication_from(
101 self, user, allows_non_existing_user=True,
91 self, user, allows_non_existing_user=True,
102 allowed_auth_plugins=None, allowed_auth_sources=None):
92 allowed_auth_plugins=None, allowed_auth_sources=None):
103 """
93 """
104 Custom method for this auth that doesn't accept non existing users.
94 Custom method for this auth that doesn't accept non existing users.
105 We know that user exists in our database.
95 We know that user exists in our database.
106 """
96 """
107 allows_non_existing_user = False
97 allows_non_existing_user = False
108 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
98 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
109 user, allows_non_existing_user=allows_non_existing_user)
99 user, allows_non_existing_user=allows_non_existing_user)
110
100
111 def auth(self, userobj, username, password, settings, **kwargs):
101 def auth(self, userobj, username, password, settings, **kwargs):
112 if not userobj:
102 if not userobj:
113 log.debug('userobj was:%s skipping', userobj)
103 log.debug('userobj was:%s skipping', userobj)
114 return None
104 return None
105
115 if userobj.extern_type != self.name:
106 if userobj.extern_type != self.name:
116 log.warning(
107 log.warning(
117 "userobj:%s extern_type mismatch got:`%s` expected:`%s`",
108 "userobj:%s extern_type mismatch got:`%s` expected:`%s`",
118 userobj, userobj.extern_type, self.name)
109 userobj, userobj.extern_type, self.name)
119 return None
110 return None
120
111
112 login_restriction = settings.get('login_restriction', '')
113 if login_restriction == self.LOGIN_RESTRICTION_SUPER_ADMIN and userobj.admin is False:
114 log.info(
115 "userobj:%s is not super-admin and login restriction is set to %s",
116 userobj, login_restriction)
117 return None
118
121 user_attrs = {
119 user_attrs = {
122 "username": userobj.username,
120 "username": userobj.username,
123 "firstname": userobj.firstname,
121 "firstname": userobj.firstname,
124 "lastname": userobj.lastname,
122 "lastname": userobj.lastname,
125 "groups": [],
123 "groups": [],
126 'user_group_sync': False,
124 'user_group_sync': False,
127 "email": userobj.email,
125 "email": userobj.email,
128 "admin": userobj.admin,
126 "admin": userobj.admin,
129 "active": userobj.active,
127 "active": userobj.active,
130 "active_from_extern": userobj.active,
128 "active_from_extern": userobj.active,
131 "extern_name": userobj.user_id,
129 "extern_name": userobj.user_id,
132 "extern_type": userobj.extern_type,
130 "extern_type": userobj.extern_type,
133 }
131 }
134
132
135 log.debug("User attributes:%s", user_attrs)
133 log.debug("User attributes:%s", user_attrs)
136 if userobj.active:
134 if userobj.active:
137 from rhodecode.lib import auth
135 from rhodecode.lib import auth
138 crypto_backend = auth.crypto_backend()
136 crypto_backend = auth.crypto_backend()
139 password_encoded = safe_str(password)
137 password_encoded = safe_str(password)
140 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
138 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
141 password_encoded, userobj.password or '')
139 password_encoded, userobj.password or '')
142
140
143 if password_match and new_hash:
141 if password_match and new_hash:
144 log.debug('user %s properly authenticated, but '
142 log.debug('user %s properly authenticated, but '
145 'requires hash change to bcrypt', userobj)
143 'requires hash change to bcrypt', userobj)
146 # if password match, and we use OLD deprecated hash,
144 # if password match, and we use OLD deprecated hash,
147 # we should migrate this user hash password to the new hash
145 # we should migrate this user hash password to the new hash
148 # we store the new returned by hash_check_with_upgrade function
146 # we store the new returned by hash_check_with_upgrade function
149 user_attrs['_hash_migrate'] = new_hash
147 user_attrs['_hash_migrate'] = new_hash
150
148
151 if userobj.username == User.DEFAULT_USER and userobj.active:
149 if userobj.username == User.DEFAULT_USER and userobj.active:
152 log.info(
150 log.info('user `%s` authenticated correctly as anonymous user',
153 'user `%s` authenticated correctly as anonymous user', userobj.username)
151 userobj.username)
154 return user_attrs
152 return user_attrs
155
153
156 elif userobj.username == username and password_match:
154 elif userobj.username == username and password_match:
157 log.info('user `%s` authenticated correctly', userobj.username)
155 log.info('user `%s` authenticated correctly', userobj.username)
158 return user_attrs
156 return user_attrs
159 log.warn("user `%s` used a wrong password when "
157 log.warn("user `%s` used a wrong password when "
160 "authenticating on this plugin", userobj.username)
158 "authenticating on this plugin", userobj.username)
161 return None
159 return None
162 else:
160 else:
163 log.warning(
161 log.warning(
164 'user `%s` failed to authenticate via %s, reason: account not '
162 'user `%s` failed to authenticate via %s, reason: account not '
165 'active.', username, self.name)
163 'active.', username, self.name)
166 return None
164 return None
167
165
168
166
167 class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase):
168 login_restriction_choices = [
169 (RhodeCodeAuthPlugin.LOGIN_RESTRICTION_NONE, 'All users'),
170 (RhodeCodeAuthPlugin.LOGIN_RESTRICTION_SUPER_ADMIN, 'Super admins only')
171 ]
172
173 login_restriction = colander.SchemaNode(
174 colander.String(),
175 default=login_restriction_choices[0],
176 description=_('Choose login restrition for users.'),
177 title=_('Login restriction'),
178 validator=colander.OneOf([x[0] for x in login_restriction_choices]),
179 widget='select_with_labels',
180 choices=login_restriction_choices
181 )
182
183
169 def includeme(config):
184 def includeme(config):
170 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
185 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
171 plugin_factory(plugin_id).includeme(config)
186 plugin_factory(plugin_id).includeme(config)
@@ -1,132 +1,134 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Authentication Settings')}
5 ${_('Authentication Settings')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}}
7 &middot; ${h.branding(c.rhodecode_name)}}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
15 &raquo;
15 &raquo;
16 ${resource.display_name}
16 ${resource.display_name}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class='sidebar-col-wrapper'>
28 <div class='sidebar-col-wrapper'>
29
29
30 ## TODO: This is repeated in the auth root template and should be merged
30 ## TODO: This is repeated in the auth root template and should be merged
31 ## into a single solution.
31 ## into a single solution.
32 <div class="sidebar">
32 <div class="sidebar">
33 <ul class="nav nav-pills nav-stacked">
33 <ul class="nav nav-pills nav-stacked">
34 % for item in resource.get_root().get_nav_list():
34 % for item in resource.get_root().get_nav_list():
35 <li ${'class=active' if item == resource else ''}>
35 <li ${'class=active' if item == resource else ''}>
36 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
36 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
37 </li>
37 </li>
38 % endfor
38 % endfor
39 </ul>
39 </ul>
40 </div>
40 </div>
41
41
42 <div class="main-content-full-width">
42 <div class="main-content-full-width">
43 <div class="panel panel-default">
43 <div class="panel panel-default">
44 <div class="panel-heading">
44 <div class="panel-heading">
45 <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3>
45 <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3>
46 </div>
46 </div>
47 <div class="panel-body">
47 <div class="panel-body">
48 <div class="plugin_form">
48 <div class="plugin_form">
49 <div class="fields">
49 <div class="fields">
50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)}
50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)}
51 <div class="form">
51 <div class="form">
52
52
53 %for node in plugin.get_settings_schema():
53 %for node in plugin.get_settings_schema():
54 <%
54 <%
55 label_to_type = {'label-checkbox': 'bool', 'label-textarea': 'textarea'}
55 label_to_type = {'label-checkbox': 'bool', 'label-textarea': 'textarea'}
56 %>
56 %>
57
57
58 <div class="field">
58 <div class="field">
59 <div class="label ${label_to_type.get(node.widget)}"><label for="${node.name}">${node.title}</label></div>
59 <div class="label ${label_to_type.get(node.widget)}"><label for="${node.name}">${node.title}</label></div>
60 <div class="input">
60 <div class="input">
61 %if node.widget in ["string", "int", "unicode"]:
61 %if node.widget in ["string", "int", "unicode"]:
62 ${h.text(node.name, defaults.get(node.name), class_="large")}
62 ${h.text(node.name, defaults.get(node.name), class_="large")}
63 %elif node.widget == "password":
63 %elif node.widget == "password":
64 ${h.password(node.name, defaults.get(node.name), class_="large")}
64 ${h.password(node.name, defaults.get(node.name), class_="large")}
65 %elif node.widget == "bool":
65 %elif node.widget == "bool":
66 <div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
66 <div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
67 %elif node.widget == "select":
67 %elif node.widget == "select":
68 ${h.select(node.name, defaults.get(node.name), node.validator.choices, class_="select2AuthSetting")}
68 ${h.select(node.name, defaults.get(node.name), node.validator.choices, class_="select2AuthSetting")}
69 %elif node.widget == "select_with_labels":
70 ${h.select(node.name, defaults.get(node.name), node.choices, class_="select2AuthSetting")}
69 %elif node.widget == "textarea":
71 %elif node.widget == "textarea":
70 <div class="textarea" style="margin-left: 0px">${h.textarea(node.name, defaults.get(node.name), rows=10)}</div>
72 <div class="textarea" style="margin-left: 0px">${h.textarea(node.name, defaults.get(node.name), rows=10)}</div>
71 %elif node.widget == "readonly":
73 %elif node.widget == "readonly":
72 ${node.default}
74 ${node.default}
73 %else:
75 %else:
74 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select].
76 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select].
75 %endif
77 %endif
76
78
77 %if node.name in errors:
79 %if node.name in errors:
78 <span class="error-message">${errors.get(node.name)}</span>
80 <span class="error-message">${errors.get(node.name)}</span>
79 <br />
81 <br />
80 %endif
82 %endif
81 <p class="help-block pre-formatting">${node.description}</p>
83 <p class="help-block pre-formatting">${node.description}</p>
82 </div>
84 </div>
83 </div>
85 </div>
84 %endfor
86 %endfor
85
87
86 ## Allow derived templates to add something below the form
88 ## Allow derived templates to add something below the form
87 ## input fields
89 ## input fields
88 %if hasattr(next, 'below_form_fields'):
90 %if hasattr(next, 'below_form_fields'):
89 ${next.below_form_fields()}
91 ${next.below_form_fields()}
90 %endif
92 %endif
91
93
92 <div class="buttons">
94 <div class="buttons">
93 ${h.submit('save',_('Save'),class_="btn")}
95 ${h.submit('save',_('Save'),class_="btn")}
94 </div>
96 </div>
95
97
96 </div>
98 </div>
97 ${h.end_form()}
99 ${h.end_form()}
98 </div>
100 </div>
99 </div>
101 </div>
100
102
101 % if request.GET.get('schema'):
103 % if request.GET.get('schema'):
102 ## this is for development and creation of example configurations for documentation
104 ## this is for development and creation of example configurations for documentation
103 <pre>
105 <pre>
104 % for node in plugin.get_settings_schema():
106 % for node in plugin.get_settings_schema():
105 *option*: `${node.name}` => `${defaults.get(node.name)}`${'\n # '.join(['']+node.description.splitlines())}
107 *option*: `${node.name}` => `${defaults.get(node.name)}`${'\n # '.join(['']+node.description.splitlines())}
106
108
107 % endfor
109 % endfor
108 </pre>
110 </pre>
109
111
110 % endif
112 % endif
111
113
112 </div>
114 </div>
113 </div>
115 </div>
114 </div>
116 </div>
115
117
116 </div>
118 </div>
117 </div>
119 </div>
118
120
119
121
120 <script>
122 <script>
121 $(document).ready(function() {
123 $(document).ready(function() {
122 var select2Options = {
124 var select2Options = {
123 containerCssClass: 'drop-menu',
125 containerCssClass: 'drop-menu',
124 dropdownCssClass: 'drop-menu-dropdown',
126 dropdownCssClass: 'drop-menu-dropdown',
125 dropdownAutoWidth: true,
127 dropdownAutoWidth: true,
126 minimumResultsForSearch: -1
128 minimumResultsForSearch: -1
127 };
129 };
128 $('.select2AuthSetting').select2(select2Options);
130 $('.select2AuthSetting').select2(select2Options);
129
131
130 });
132 });
131 </script>
133 </script>
132 </%def>
134 </%def>
@@ -1,349 +1,383 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helpers for fixture generation
22 Helpers for fixture generation
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import tempfile
27 import tempfile
28 import shutil
28 import shutil
29
29
30 import configobj
30 import configobj
31
31
32 from rhodecode.tests import *
32 from rhodecode.tests import *
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.user_group import UserGroupModel
38 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
40 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.authentication.plugins.auth_rhodecode import \
42 RhodeCodeAuthPlugin
41
43
42 dn = os.path.dirname
44 dn = os.path.dirname
43 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
45 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
44
46
45
47
46 def error_function(*args, **kwargs):
48 def error_function(*args, **kwargs):
47 raise Exception('Total Crash !')
49 raise Exception('Total Crash !')
48
50
49
51
50 class TestINI(object):
52 class TestINI(object):
51 """
53 """
52 Allows to create a new test.ini file as a copy of existing one with edited
54 Allows to create a new test.ini file as a copy of existing one with edited
53 data. Example usage::
55 data. Example usage::
54
56
55 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
57 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
56 print('paster server %s' % new_test_ini)
58 print('paster server %s' % new_test_ini)
57 """
59 """
58
60
59 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
61 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
60 destroy=True, dir=None):
62 destroy=True, dir=None):
61 self.ini_file_path = ini_file_path
63 self.ini_file_path = ini_file_path
62 self.ini_params = ini_params
64 self.ini_params = ini_params
63 self.new_path = None
65 self.new_path = None
64 self.new_path_prefix = new_file_prefix
66 self.new_path_prefix = new_file_prefix
65 self._destroy = destroy
67 self._destroy = destroy
66 self._dir = dir
68 self._dir = dir
67
69
68 def __enter__(self):
70 def __enter__(self):
69 return self.create()
71 return self.create()
70
72
71 def __exit__(self, exc_type, exc_val, exc_tb):
73 def __exit__(self, exc_type, exc_val, exc_tb):
72 self.destroy()
74 self.destroy()
73
75
74 def create(self):
76 def create(self):
75 config = configobj.ConfigObj(
77 config = configobj.ConfigObj(
76 self.ini_file_path, file_error=True, write_empty_values=True)
78 self.ini_file_path, file_error=True, write_empty_values=True)
77
79
78 for data in self.ini_params:
80 for data in self.ini_params:
79 section, ini_params = data.items()[0]
81 section, ini_params = data.items()[0]
80 for key, val in ini_params.items():
82 for key, val in ini_params.items():
81 config[section][key] = val
83 config[section][key] = val
82 with tempfile.NamedTemporaryFile(
84 with tempfile.NamedTemporaryFile(
83 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
85 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
84 delete=False) as new_ini_file:
86 delete=False) as new_ini_file:
85 config.write(new_ini_file)
87 config.write(new_ini_file)
86 self.new_path = new_ini_file.name
88 self.new_path = new_ini_file.name
87
89
88 return self.new_path
90 return self.new_path
89
91
90 def destroy(self):
92 def destroy(self):
91 if self._destroy:
93 if self._destroy:
92 os.remove(self.new_path)
94 os.remove(self.new_path)
93
95
94
96
95 class Fixture(object):
97 class Fixture(object):
96
98
97 def anon_access(self, status):
99 def anon_access(self, status):
98 """
100 """
99 Context process for disabling anonymous access. use like:
101 Context process for disabling anonymous access. use like:
100 fixture = Fixture()
102 fixture = Fixture()
101 with fixture.anon_access(False):
103 with fixture.anon_access(False):
102 #tests
104 #tests
103
105
104 after this block anon access will be set to `not status`
106 after this block anon access will be set to `not status`
105 """
107 """
106
108
107 class context(object):
109 class context(object):
108 def __enter__(self):
110 def __enter__(self):
109 anon = User.get_default_user()
111 anon = User.get_default_user()
110 anon.active = status
112 anon.active = status
111 Session().add(anon)
113 Session().add(anon)
112 Session().commit()
114 Session().commit()
113 time.sleep(1.5) # must sleep for cache (1s to expire)
115 time.sleep(1.5) # must sleep for cache (1s to expire)
114
116
115 def __exit__(self, exc_type, exc_val, exc_tb):
117 def __exit__(self, exc_type, exc_val, exc_tb):
116 anon = User.get_default_user()
118 anon = User.get_default_user()
117 anon.active = not status
119 anon.active = not status
118 Session().add(anon)
120 Session().add(anon)
119 Session().commit()
121 Session().commit()
120
122
121 return context()
123 return context()
122
124
125 def login_restriction(self, login_restriction):
126 """
127 Context process for changing the builtin rhodecode plugin login restrictions.
128 Use like:
129 fixture = Fixture()
130 with fixture.login_restriction('super_admin'):
131 #tests
132
133 after this block login restriction will be taken off
134 """
135
136 class context(object):
137 def _get_pluing(self):
138 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(
139 RhodeCodeAuthPlugin.uid)
140 plugin = RhodeCodeAuthPlugin(plugin_id)
141 return plugin
142
143 def __enter__(self):
144 plugin = self._get_pluing()
145 plugin.create_or_update_setting(
146 'login_restriction', login_restriction)
147 Session().commit()
148
149 def __exit__(self, exc_type, exc_val, exc_tb):
150 plugin = self._get_pluing()
151 plugin.create_or_update_setting(
152 'login_restriction', RhodeCodeAuthPlugin.LOGIN_RESTRICTION_NONE)
153 Session().commit()
154
155 return context()
156
123 def _get_repo_create_params(self, **custom):
157 def _get_repo_create_params(self, **custom):
124 defs = {
158 defs = {
125 'repo_name': None,
159 'repo_name': None,
126 'repo_type': 'hg',
160 'repo_type': 'hg',
127 'clone_uri': '',
161 'clone_uri': '',
128 'push_uri': '',
162 'push_uri': '',
129 'repo_group': '-1',
163 'repo_group': '-1',
130 'repo_description': 'DESC',
164 'repo_description': 'DESC',
131 'repo_private': False,
165 'repo_private': False,
132 'repo_landing_rev': 'rev:tip',
166 'repo_landing_rev': 'rev:tip',
133 'repo_copy_permissions': False,
167 'repo_copy_permissions': False,
134 'repo_state': Repository.STATE_CREATED,
168 'repo_state': Repository.STATE_CREATED,
135 }
169 }
136 defs.update(custom)
170 defs.update(custom)
137 if 'repo_name_full' not in custom:
171 if 'repo_name_full' not in custom:
138 defs.update({'repo_name_full': defs['repo_name']})
172 defs.update({'repo_name_full': defs['repo_name']})
139
173
140 # fix the repo name if passed as repo_name_full
174 # fix the repo name if passed as repo_name_full
141 if defs['repo_name']:
175 if defs['repo_name']:
142 defs['repo_name'] = defs['repo_name'].split('/')[-1]
176 defs['repo_name'] = defs['repo_name'].split('/')[-1]
143
177
144 return defs
178 return defs
145
179
146 def _get_group_create_params(self, **custom):
180 def _get_group_create_params(self, **custom):
147 defs = {
181 defs = {
148 'group_name': None,
182 'group_name': None,
149 'group_description': 'DESC',
183 'group_description': 'DESC',
150 'perm_updates': [],
184 'perm_updates': [],
151 'perm_additions': [],
185 'perm_additions': [],
152 'perm_deletions': [],
186 'perm_deletions': [],
153 'group_parent_id': -1,
187 'group_parent_id': -1,
154 'enable_locking': False,
188 'enable_locking': False,
155 'recursive': False,
189 'recursive': False,
156 }
190 }
157 defs.update(custom)
191 defs.update(custom)
158
192
159 return defs
193 return defs
160
194
161 def _get_user_create_params(self, name, **custom):
195 def _get_user_create_params(self, name, **custom):
162 defs = {
196 defs = {
163 'username': name,
197 'username': name,
164 'password': 'qweqwe',
198 'password': 'qweqwe',
165 'email': '%s+test@rhodecode.org' % name,
199 'email': '%s+test@rhodecode.org' % name,
166 'firstname': 'TestUser',
200 'firstname': 'TestUser',
167 'lastname': 'Test',
201 'lastname': 'Test',
168 'active': True,
202 'active': True,
169 'admin': False,
203 'admin': False,
170 'extern_type': 'rhodecode',
204 'extern_type': 'rhodecode',
171 'extern_name': None,
205 'extern_name': None,
172 }
206 }
173 defs.update(custom)
207 defs.update(custom)
174
208
175 return defs
209 return defs
176
210
177 def _get_user_group_create_params(self, name, **custom):
211 def _get_user_group_create_params(self, name, **custom):
178 defs = {
212 defs = {
179 'users_group_name': name,
213 'users_group_name': name,
180 'user_group_description': 'DESC',
214 'user_group_description': 'DESC',
181 'users_group_active': True,
215 'users_group_active': True,
182 'user_group_data': {},
216 'user_group_data': {},
183 }
217 }
184 defs.update(custom)
218 defs.update(custom)
185
219
186 return defs
220 return defs
187
221
188 def create_repo(self, name, **kwargs):
222 def create_repo(self, name, **kwargs):
189 repo_group = kwargs.get('repo_group')
223 repo_group = kwargs.get('repo_group')
190 if isinstance(repo_group, RepoGroup):
224 if isinstance(repo_group, RepoGroup):
191 kwargs['repo_group'] = repo_group.group_id
225 kwargs['repo_group'] = repo_group.group_id
192 name = name.split(Repository.NAME_SEP)[-1]
226 name = name.split(Repository.NAME_SEP)[-1]
193 name = Repository.NAME_SEP.join((repo_group.group_name, name))
227 name = Repository.NAME_SEP.join((repo_group.group_name, name))
194
228
195 if 'skip_if_exists' in kwargs:
229 if 'skip_if_exists' in kwargs:
196 del kwargs['skip_if_exists']
230 del kwargs['skip_if_exists']
197 r = Repository.get_by_repo_name(name)
231 r = Repository.get_by_repo_name(name)
198 if r:
232 if r:
199 return r
233 return r
200
234
201 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
235 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
202 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
236 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
203 RepoModel().create(form_data, cur_user)
237 RepoModel().create(form_data, cur_user)
204 Session().commit()
238 Session().commit()
205 repo = Repository.get_by_repo_name(name)
239 repo = Repository.get_by_repo_name(name)
206 assert repo
240 assert repo
207 return repo
241 return repo
208
242
209 def create_fork(self, repo_to_fork, fork_name, **kwargs):
243 def create_fork(self, repo_to_fork, fork_name, **kwargs):
210 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
244 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
211
245
212 form_data = self._get_repo_create_params(repo_name=fork_name,
246 form_data = self._get_repo_create_params(repo_name=fork_name,
213 fork_parent_id=repo_to_fork.repo_id,
247 fork_parent_id=repo_to_fork.repo_id,
214 repo_type=repo_to_fork.repo_type,
248 repo_type=repo_to_fork.repo_type,
215 **kwargs)
249 **kwargs)
216 #TODO: fix it !!
250 #TODO: fix it !!
217 form_data['description'] = form_data['repo_description']
251 form_data['description'] = form_data['repo_description']
218 form_data['private'] = form_data['repo_private']
252 form_data['private'] = form_data['repo_private']
219 form_data['landing_rev'] = form_data['repo_landing_rev']
253 form_data['landing_rev'] = form_data['repo_landing_rev']
220
254
221 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
255 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
222 RepoModel().create_fork(form_data, cur_user=owner)
256 RepoModel().create_fork(form_data, cur_user=owner)
223 Session().commit()
257 Session().commit()
224 r = Repository.get_by_repo_name(fork_name)
258 r = Repository.get_by_repo_name(fork_name)
225 assert r
259 assert r
226 return r
260 return r
227
261
228 def destroy_repo(self, repo_name, **kwargs):
262 def destroy_repo(self, repo_name, **kwargs):
229 RepoModel().delete(repo_name, pull_requests='delete', **kwargs)
263 RepoModel().delete(repo_name, pull_requests='delete', **kwargs)
230 Session().commit()
264 Session().commit()
231
265
232 def destroy_repo_on_filesystem(self, repo_name):
266 def destroy_repo_on_filesystem(self, repo_name):
233 rm_path = os.path.join(RepoModel().repos_path, repo_name)
267 rm_path = os.path.join(RepoModel().repos_path, repo_name)
234 if os.path.isdir(rm_path):
268 if os.path.isdir(rm_path):
235 shutil.rmtree(rm_path)
269 shutil.rmtree(rm_path)
236
270
237 def create_repo_group(self, name, **kwargs):
271 def create_repo_group(self, name, **kwargs):
238 if 'skip_if_exists' in kwargs:
272 if 'skip_if_exists' in kwargs:
239 del kwargs['skip_if_exists']
273 del kwargs['skip_if_exists']
240 gr = RepoGroup.get_by_group_name(group_name=name)
274 gr = RepoGroup.get_by_group_name(group_name=name)
241 if gr:
275 if gr:
242 return gr
276 return gr
243 form_data = self._get_group_create_params(group_name=name, **kwargs)
277 form_data = self._get_group_create_params(group_name=name, **kwargs)
244 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
278 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
245 gr = RepoGroupModel().create(
279 gr = RepoGroupModel().create(
246 group_name=form_data['group_name'],
280 group_name=form_data['group_name'],
247 group_description=form_data['group_name'],
281 group_description=form_data['group_name'],
248 owner=owner)
282 owner=owner)
249 Session().commit()
283 Session().commit()
250 gr = RepoGroup.get_by_group_name(gr.group_name)
284 gr = RepoGroup.get_by_group_name(gr.group_name)
251 return gr
285 return gr
252
286
253 def destroy_repo_group(self, repogroupid):
287 def destroy_repo_group(self, repogroupid):
254 RepoGroupModel().delete(repogroupid)
288 RepoGroupModel().delete(repogroupid)
255 Session().commit()
289 Session().commit()
256
290
257 def create_user(self, name, **kwargs):
291 def create_user(self, name, **kwargs):
258 if 'skip_if_exists' in kwargs:
292 if 'skip_if_exists' in kwargs:
259 del kwargs['skip_if_exists']
293 del kwargs['skip_if_exists']
260 user = User.get_by_username(name)
294 user = User.get_by_username(name)
261 if user:
295 if user:
262 return user
296 return user
263 form_data = self._get_user_create_params(name, **kwargs)
297 form_data = self._get_user_create_params(name, **kwargs)
264 user = UserModel().create(form_data)
298 user = UserModel().create(form_data)
265
299
266 # create token for user
300 # create token for user
267 AuthTokenModel().create(
301 AuthTokenModel().create(
268 user=user, description=u'TEST_USER_TOKEN')
302 user=user, description=u'TEST_USER_TOKEN')
269
303
270 Session().commit()
304 Session().commit()
271 user = User.get_by_username(user.username)
305 user = User.get_by_username(user.username)
272 return user
306 return user
273
307
274 def destroy_user(self, userid):
308 def destroy_user(self, userid):
275 UserModel().delete(userid)
309 UserModel().delete(userid)
276 Session().commit()
310 Session().commit()
277
311
278 def create_additional_user_email(self, user, email):
312 def create_additional_user_email(self, user, email):
279 uem = UserEmailMap()
313 uem = UserEmailMap()
280 uem.user = user
314 uem.user = user
281 uem.email = email
315 uem.email = email
282 Session().add(uem)
316 Session().add(uem)
283 return uem
317 return uem
284
318
285 def destroy_users(self, userid_iter):
319 def destroy_users(self, userid_iter):
286 for user_id in userid_iter:
320 for user_id in userid_iter:
287 if User.get_by_username(user_id):
321 if User.get_by_username(user_id):
288 UserModel().delete(user_id)
322 UserModel().delete(user_id)
289 Session().commit()
323 Session().commit()
290
324
291 def create_user_group(self, name, **kwargs):
325 def create_user_group(self, name, **kwargs):
292 if 'skip_if_exists' in kwargs:
326 if 'skip_if_exists' in kwargs:
293 del kwargs['skip_if_exists']
327 del kwargs['skip_if_exists']
294 gr = UserGroup.get_by_group_name(group_name=name)
328 gr = UserGroup.get_by_group_name(group_name=name)
295 if gr:
329 if gr:
296 return gr
330 return gr
297 # map active flag to the real attribute. For API consistency of fixtures
331 # map active flag to the real attribute. For API consistency of fixtures
298 if 'active' in kwargs:
332 if 'active' in kwargs:
299 kwargs['users_group_active'] = kwargs['active']
333 kwargs['users_group_active'] = kwargs['active']
300 del kwargs['active']
334 del kwargs['active']
301 form_data = self._get_user_group_create_params(name, **kwargs)
335 form_data = self._get_user_group_create_params(name, **kwargs)
302 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
336 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
303 user_group = UserGroupModel().create(
337 user_group = UserGroupModel().create(
304 name=form_data['users_group_name'],
338 name=form_data['users_group_name'],
305 description=form_data['user_group_description'],
339 description=form_data['user_group_description'],
306 owner=owner, active=form_data['users_group_active'],
340 owner=owner, active=form_data['users_group_active'],
307 group_data=form_data['user_group_data'])
341 group_data=form_data['user_group_data'])
308 Session().commit()
342 Session().commit()
309 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
343 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
310 return user_group
344 return user_group
311
345
312 def destroy_user_group(self, usergroupid):
346 def destroy_user_group(self, usergroupid):
313 UserGroupModel().delete(user_group=usergroupid, force=True)
347 UserGroupModel().delete(user_group=usergroupid, force=True)
314 Session().commit()
348 Session().commit()
315
349
316 def create_gist(self, **kwargs):
350 def create_gist(self, **kwargs):
317 form_data = {
351 form_data = {
318 'description': 'new-gist',
352 'description': 'new-gist',
319 'owner': TEST_USER_ADMIN_LOGIN,
353 'owner': TEST_USER_ADMIN_LOGIN,
320 'gist_type': GistModel.cls.GIST_PUBLIC,
354 'gist_type': GistModel.cls.GIST_PUBLIC,
321 'lifetime': -1,
355 'lifetime': -1,
322 'acl_level': Gist.ACL_LEVEL_PUBLIC,
356 'acl_level': Gist.ACL_LEVEL_PUBLIC,
323 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
357 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
324 }
358 }
325 form_data.update(kwargs)
359 form_data.update(kwargs)
326 gist = GistModel().create(
360 gist = GistModel().create(
327 description=form_data['description'], owner=form_data['owner'],
361 description=form_data['description'], owner=form_data['owner'],
328 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
362 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
329 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
363 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
330 )
364 )
331 Session().commit()
365 Session().commit()
332 return gist
366 return gist
333
367
334 def destroy_gists(self, gistid=None):
368 def destroy_gists(self, gistid=None):
335 for g in GistModel.cls.get_all():
369 for g in GistModel.cls.get_all():
336 if gistid:
370 if gistid:
337 if gistid == g.gist_access_id:
371 if gistid == g.gist_access_id:
338 GistModel().delete(g)
372 GistModel().delete(g)
339 else:
373 else:
340 GistModel().delete(g)
374 GistModel().delete(g)
341 Session().commit()
375 Session().commit()
342
376
343 def load_resource(self, resource_name, strip=False):
377 def load_resource(self, resource_name, strip=False):
344 with open(os.path.join(FIXTURES, resource_name)) as f:
378 with open(os.path.join(FIXTURES, resource_name)) as f:
345 source = f.read()
379 source = f.read()
346 if strip:
380 if strip:
347 source = source.strip()
381 source = source.strip()
348
382
349 return source
383 return source
General Comments 0
You need to be logged in to leave comments. Login now