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