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 |
|
|
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 | · ${h.branding(c.rhodecode_name)}} |
|
7 | · ${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 | » |
|
13 | » | |
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 | » |
|
15 | » | |
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