##// END OF EJS Templates
registration: updated flash with more information after user registered.
marcink -
r4058:0e4c6b60 default
parent child Browse files
Show More
@@ -1,579 +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.auth_restriction(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN):
112 with fixture.auth_restriction(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN):
113 response = self.app.post(route_path('login'),
113 response = self.app.post(route_path('login'),
114 {'username': 'test_regular',
114 {'username': 'test_regular',
115 'password': 'test12'})
115 'password': 'test12'})
116
116
117 response.mustcontain('invalid user name')
117 response.mustcontain('invalid user name')
118 response.mustcontain('invalid password')
118 response.mustcontain('invalid password')
119
119
120 def test_login_regular_forbidden_when_scope_restriction(self):
120 def test_login_regular_forbidden_when_scope_restriction(self):
121 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
121 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
122 with fixture.scope_restriction(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_VCS):
122 with fixture.scope_restriction(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_VCS):
123 response = self.app.post(route_path('login'),
123 response = self.app.post(route_path('login'),
124 {'username': 'test_regular',
124 {'username': 'test_regular',
125 'password': 'test12'})
125 'password': 'test12'})
126
126
127 response.mustcontain('invalid user name')
127 response.mustcontain('invalid user name')
128 response.mustcontain('invalid password')
128 response.mustcontain('invalid password')
129
129
130 def test_login_ok_came_from(self):
130 def test_login_ok_came_from(self):
131 test_came_from = '/_admin/users?branch=stable'
131 test_came_from = '/_admin/users?branch=stable'
132 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
132 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
133 response = self.app.post(
133 response = self.app.post(
134 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
134 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
135
135
136 assert 'branch=stable' in response.location
136 assert 'branch=stable' in response.location
137 response = response.follow()
137 response = response.follow()
138
138
139 assert response.status == '200 OK'
139 assert response.status == '200 OK'
140 response.mustcontain('Users administration')
140 response.mustcontain('Users administration')
141
141
142 def test_redirect_to_login_with_get_args(self):
142 def test_redirect_to_login_with_get_args(self):
143 with fixture.anon_access(False):
143 with fixture.anon_access(False):
144 kwargs = {'branch': 'stable'}
144 kwargs = {'branch': 'stable'}
145 response = self.app.get(
145 response = self.app.get(
146 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs),
146 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs),
147 status=302)
147 status=302)
148
148
149 response_query = urlparse.parse_qsl(response.location)
149 response_query = urlparse.parse_qsl(response.location)
150 assert 'branch=stable' in response_query[0][1]
150 assert 'branch=stable' in response_query[0][1]
151
151
152 def test_login_form_with_get_args(self):
152 def test_login_form_with_get_args(self):
153 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
153 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
154 response = self.app.get(_url)
154 response = self.app.get(_url)
155 assert 'branch%3Dstable' in response.form.action
155 assert 'branch%3Dstable' in response.form.action
156
156
157 @pytest.mark.parametrize("url_came_from", [
157 @pytest.mark.parametrize("url_came_from", [
158 'data:text/html,<script>window.alert("xss")</script>',
158 'data:text/html,<script>window.alert("xss")</script>',
159 'mailto:test@rhodecode.org',
159 'mailto:test@rhodecode.org',
160 'file:///etc/passwd',
160 'file:///etc/passwd',
161 'ftp://some.ftp.server',
161 'ftp://some.ftp.server',
162 'http://other.domain',
162 'http://other.domain',
163 '/\r\nX-Forwarded-Host: http://example.org',
163 '/\r\nX-Forwarded-Host: http://example.org',
164 ], ids=no_newline_id_generator)
164 ], ids=no_newline_id_generator)
165 def test_login_bad_came_froms(self, url_came_from):
165 def test_login_bad_came_froms(self, url_came_from):
166 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
166 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
167 response = self.app.post(
167 response = self.app.post(
168 _url,
168 _url,
169 {'username': 'test_admin', 'password': 'test12'})
169 {'username': 'test_admin', 'password': 'test12'})
170 assert response.status == '302 Found'
170 assert response.status == '302 Found'
171 response = response.follow()
171 response = response.follow()
172 assert response.status == '200 OK'
172 assert response.status == '200 OK'
173 assert response.request.path == '/'
173 assert response.request.path == '/'
174
174
175 def test_login_short_password(self):
175 def test_login_short_password(self):
176 response = self.app.post(route_path('login'),
176 response = self.app.post(route_path('login'),
177 {'username': 'test_admin',
177 {'username': 'test_admin',
178 'password': 'as'})
178 'password': 'as'})
179 assert response.status == '200 OK'
179 assert response.status == '200 OK'
180
180
181 response.mustcontain('Enter 3 characters or more')
181 response.mustcontain('Enter 3 characters or more')
182
182
183 def test_login_wrong_non_ascii_password(self, user_regular):
183 def test_login_wrong_non_ascii_password(self, user_regular):
184 response = self.app.post(
184 response = self.app.post(
185 route_path('login'),
185 route_path('login'),
186 {'username': user_regular.username,
186 {'username': user_regular.username,
187 'password': u'invalid-non-asci\xe4'.encode('utf8')})
187 'password': u'invalid-non-asci\xe4'.encode('utf8')})
188
188
189 response.mustcontain('invalid user name')
189 response.mustcontain('invalid user name')
190 response.mustcontain('invalid password')
190 response.mustcontain('invalid password')
191
191
192 def test_login_with_non_ascii_password(self, user_util):
192 def test_login_with_non_ascii_password(self, user_util):
193 password = u'valid-non-ascii\xe4'
193 password = u'valid-non-ascii\xe4'
194 user = user_util.create_user(password=password)
194 user = user_util.create_user(password=password)
195 response = self.app.post(
195 response = self.app.post(
196 route_path('login'),
196 route_path('login'),
197 {'username': user.username,
197 {'username': user.username,
198 'password': password.encode('utf-8')})
198 'password': password.encode('utf-8')})
199 assert response.status_code == 302
199 assert response.status_code == 302
200
200
201 def test_login_wrong_username_password(self):
201 def test_login_wrong_username_password(self):
202 response = self.app.post(route_path('login'),
202 response = self.app.post(route_path('login'),
203 {'username': 'error',
203 {'username': 'error',
204 'password': 'test12'})
204 'password': 'test12'})
205
205
206 response.mustcontain('invalid user name')
206 response.mustcontain('invalid user name')
207 response.mustcontain('invalid password')
207 response.mustcontain('invalid password')
208
208
209 def test_login_admin_ok_password_migration(self, real_crypto_backend):
209 def test_login_admin_ok_password_migration(self, real_crypto_backend):
210 from rhodecode.lib import auth
210 from rhodecode.lib import auth
211
211
212 # create new user, with sha256 password
212 # create new user, with sha256 password
213 temp_user = 'test_admin_sha256'
213 temp_user = 'test_admin_sha256'
214 user = fixture.create_user(temp_user)
214 user = fixture.create_user(temp_user)
215 user.password = auth._RhodeCodeCryptoSha256().hash_create(
215 user.password = auth._RhodeCodeCryptoSha256().hash_create(
216 b'test123')
216 b'test123')
217 Session().add(user)
217 Session().add(user)
218 Session().commit()
218 Session().commit()
219 self.destroy_users.add(temp_user)
219 self.destroy_users.add(temp_user)
220 response = self.app.post(route_path('login'),
220 response = self.app.post(route_path('login'),
221 {'username': temp_user,
221 {'username': temp_user,
222 'password': 'test123'}, status=302)
222 'password': 'test123'}, status=302)
223
223
224 response = response.follow()
224 response = response.follow()
225 session = response.get_session_from_response()
225 session = response.get_session_from_response()
226 username = session['rhodecode_user'].get('username')
226 username = session['rhodecode_user'].get('username')
227 assert username == temp_user
227 assert username == temp_user
228 response.mustcontain('/%s' % HG_REPO)
228 response.mustcontain('/%s' % HG_REPO)
229
229
230 # new password should be bcrypted, after log-in and transfer
230 # new password should be bcrypted, after log-in and transfer
231 user = User.get_by_username(temp_user)
231 user = User.get_by_username(temp_user)
232 assert user.password.startswith('$')
232 assert user.password.startswith('$')
233
233
234 # REGISTRATIONS
234 # REGISTRATIONS
235 def test_register(self):
235 def test_register(self):
236 response = self.app.get(route_path('register'))
236 response = self.app.get(route_path('register'))
237 response.mustcontain('Create an Account')
237 response.mustcontain('Create an Account')
238
238
239 def test_register_err_same_username(self):
239 def test_register_err_same_username(self):
240 uname = 'test_admin'
240 uname = 'test_admin'
241 response = self.app.post(
241 response = self.app.post(
242 route_path('register'),
242 route_path('register'),
243 {
243 {
244 'username': uname,
244 'username': uname,
245 'password': 'test12',
245 'password': 'test12',
246 'password_confirmation': 'test12',
246 'password_confirmation': 'test12',
247 'email': 'goodmail@domain.com',
247 'email': 'goodmail@domain.com',
248 'firstname': 'test',
248 'firstname': 'test',
249 'lastname': 'test'
249 'lastname': 'test'
250 }
250 }
251 )
251 )
252
252
253 assertr = response.assert_response()
253 assertr = response.assert_response()
254 msg = 'Username "%(username)s" already exists'
254 msg = 'Username "%(username)s" already exists'
255 msg = msg % {'username': uname}
255 msg = msg % {'username': uname}
256 assertr.element_contains('#username+.error-message', msg)
256 assertr.element_contains('#username+.error-message', msg)
257
257
258 def test_register_err_same_email(self):
258 def test_register_err_same_email(self):
259 response = self.app.post(
259 response = self.app.post(
260 route_path('register'),
260 route_path('register'),
261 {
261 {
262 'username': 'test_admin_0',
262 'username': 'test_admin_0',
263 'password': 'test12',
263 'password': 'test12',
264 'password_confirmation': 'test12',
264 'password_confirmation': 'test12',
265 'email': 'test_admin@mail.com',
265 'email': 'test_admin@mail.com',
266 'firstname': 'test',
266 'firstname': 'test',
267 'lastname': 'test'
267 'lastname': 'test'
268 }
268 }
269 )
269 )
270
270
271 assertr = response.assert_response()
271 assertr = response.assert_response()
272 msg = u'This e-mail address is already taken'
272 msg = u'This e-mail address is already taken'
273 assertr.element_contains('#email+.error-message', msg)
273 assertr.element_contains('#email+.error-message', msg)
274
274
275 def test_register_err_same_email_case_sensitive(self):
275 def test_register_err_same_email_case_sensitive(self):
276 response = self.app.post(
276 response = self.app.post(
277 route_path('register'),
277 route_path('register'),
278 {
278 {
279 'username': 'test_admin_1',
279 'username': 'test_admin_1',
280 'password': 'test12',
280 'password': 'test12',
281 'password_confirmation': 'test12',
281 'password_confirmation': 'test12',
282 'email': 'TesT_Admin@mail.COM',
282 'email': 'TesT_Admin@mail.COM',
283 'firstname': 'test',
283 'firstname': 'test',
284 'lastname': 'test'
284 'lastname': 'test'
285 }
285 }
286 )
286 )
287 assertr = response.assert_response()
287 assertr = response.assert_response()
288 msg = u'This e-mail address is already taken'
288 msg = u'This e-mail address is already taken'
289 assertr.element_contains('#email+.error-message', msg)
289 assertr.element_contains('#email+.error-message', msg)
290
290
291 def test_register_err_wrong_data(self):
291 def test_register_err_wrong_data(self):
292 response = self.app.post(
292 response = self.app.post(
293 route_path('register'),
293 route_path('register'),
294 {
294 {
295 'username': 'xs',
295 'username': 'xs',
296 'password': 'test',
296 'password': 'test',
297 'password_confirmation': 'test',
297 'password_confirmation': 'test',
298 'email': 'goodmailm',
298 'email': 'goodmailm',
299 'firstname': 'test',
299 'firstname': 'test',
300 'lastname': 'test'
300 'lastname': 'test'
301 }
301 }
302 )
302 )
303 assert response.status == '200 OK'
303 assert response.status == '200 OK'
304 response.mustcontain('An email address must contain a single @')
304 response.mustcontain('An email address must contain a single @')
305 response.mustcontain('Enter a value 6 characters long or more')
305 response.mustcontain('Enter a value 6 characters long or more')
306
306
307 def test_register_err_username(self):
307 def test_register_err_username(self):
308 response = self.app.post(
308 response = self.app.post(
309 route_path('register'),
309 route_path('register'),
310 {
310 {
311 'username': 'error user',
311 'username': 'error user',
312 'password': 'test12',
312 'password': 'test12',
313 'password_confirmation': 'test12',
313 'password_confirmation': 'test12',
314 'email': 'goodmailm',
314 'email': 'goodmailm',
315 'firstname': 'test',
315 'firstname': 'test',
316 'lastname': 'test'
316 'lastname': 'test'
317 }
317 }
318 )
318 )
319
319
320 response.mustcontain('An email address must contain a single @')
320 response.mustcontain('An email address must contain a single @')
321 response.mustcontain(
321 response.mustcontain(
322 'Username may only contain '
322 'Username may only contain '
323 'alphanumeric characters underscores, '
323 'alphanumeric characters underscores, '
324 'periods or dashes and must begin with '
324 'periods or dashes and must begin with '
325 'alphanumeric character')
325 'alphanumeric character')
326
326
327 def test_register_err_case_sensitive(self):
327 def test_register_err_case_sensitive(self):
328 usr = 'Test_Admin'
328 usr = 'Test_Admin'
329 response = self.app.post(
329 response = self.app.post(
330 route_path('register'),
330 route_path('register'),
331 {
331 {
332 'username': usr,
332 'username': usr,
333 'password': 'test12',
333 'password': 'test12',
334 'password_confirmation': 'test12',
334 'password_confirmation': 'test12',
335 'email': 'goodmailm',
335 'email': 'goodmailm',
336 'firstname': 'test',
336 'firstname': 'test',
337 'lastname': 'test'
337 'lastname': 'test'
338 }
338 }
339 )
339 )
340
340
341 assertr = response.assert_response()
341 assertr = response.assert_response()
342 msg = u'Username "%(username)s" already exists'
342 msg = u'Username "%(username)s" already exists'
343 msg = msg % {'username': usr}
343 msg = msg % {'username': usr}
344 assertr.element_contains('#username+.error-message', msg)
344 assertr.element_contains('#username+.error-message', msg)
345
345
346 def test_register_special_chars(self):
346 def test_register_special_chars(self):
347 response = self.app.post(
347 response = self.app.post(
348 route_path('register'),
348 route_path('register'),
349 {
349 {
350 'username': 'xxxaxn',
350 'username': 'xxxaxn',
351 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
351 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
352 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
352 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
353 'email': 'goodmailm@test.plx',
353 'email': 'goodmailm@test.plx',
354 'firstname': 'test',
354 'firstname': 'test',
355 'lastname': 'test'
355 'lastname': 'test'
356 }
356 }
357 )
357 )
358
358
359 msg = u'Invalid characters (non-ascii) in password'
359 msg = u'Invalid characters (non-ascii) in password'
360 response.mustcontain(msg)
360 response.mustcontain(msg)
361
361
362 def test_register_password_mismatch(self):
362 def test_register_password_mismatch(self):
363 response = self.app.post(
363 response = self.app.post(
364 route_path('register'),
364 route_path('register'),
365 {
365 {
366 'username': 'xs',
366 'username': 'xs',
367 'password': '123qwe',
367 'password': '123qwe',
368 'password_confirmation': 'qwe123',
368 'password_confirmation': 'qwe123',
369 'email': 'goodmailm@test.plxa',
369 'email': 'goodmailm@test.plxa',
370 'firstname': 'test',
370 'firstname': 'test',
371 'lastname': 'test'
371 'lastname': 'test'
372 }
372 }
373 )
373 )
374 msg = u'Passwords do not match'
374 msg = u'Passwords do not match'
375 response.mustcontain(msg)
375 response.mustcontain(msg)
376
376
377 def test_register_ok(self):
377 def test_register_ok(self):
378 username = 'test_regular4'
378 username = 'test_regular4'
379 password = 'qweqwe'
379 password = 'qweqwe'
380 email = 'marcin@test.com'
380 email = 'marcin@test.com'
381 name = 'testname'
381 name = 'testname'
382 lastname = 'testlastname'
382 lastname = 'testlastname'
383
383
384 # this initializes a session
384 # this initializes a session
385 response = self.app.get(route_path('register'))
385 response = self.app.get(route_path('register'))
386 response.mustcontain('Create an Account')
386 response.mustcontain('Create an Account')
387
387
388
388
389 response = self.app.post(
389 response = self.app.post(
390 route_path('register'),
390 route_path('register'),
391 {
391 {
392 'username': username,
392 'username': username,
393 'password': password,
393 'password': password,
394 'password_confirmation': password,
394 'password_confirmation': password,
395 'email': email,
395 'email': email,
396 'firstname': name,
396 'firstname': name,
397 'lastname': lastname,
397 'lastname': lastname,
398 'admin': True
398 'admin': True
399 },
399 },
400 status=302
400 status=302
401 ) # This should be overridden
401 ) # This should be overridden
402
402
403 assert_session_flash(
403 assert_session_flash(
404 response, 'You have successfully registered with RhodeCode')
404 response, 'You have successfully registered with RhodeCode. You can log-in now.')
405
405
406 ret = Session().query(User).filter(
406 ret = Session().query(User).filter(
407 User.username == 'test_regular4').one()
407 User.username == 'test_regular4').one()
408 assert ret.username == username
408 assert ret.username == username
409 assert check_password(password, ret.password)
409 assert check_password(password, ret.password)
410 assert ret.email == email
410 assert ret.email == email
411 assert ret.name == name
411 assert ret.name == name
412 assert ret.lastname == lastname
412 assert ret.lastname == lastname
413 assert ret.auth_tokens is not None
413 assert ret.auth_tokens is not None
414 assert not ret.admin
414 assert not ret.admin
415
415
416 def test_forgot_password_wrong_mail(self):
416 def test_forgot_password_wrong_mail(self):
417 bad_email = 'marcin@wrongmail.org'
417 bad_email = 'marcin@wrongmail.org'
418 # this initializes a session
418 # this initializes a session
419 self.app.get(route_path('reset_password'))
419 self.app.get(route_path('reset_password'))
420
420
421 response = self.app.post(
421 response = self.app.post(
422 route_path('reset_password'), {'email': bad_email, }
422 route_path('reset_password'), {'email': bad_email, }
423 )
423 )
424 assert_session_flash(response,
424 assert_session_flash(response,
425 '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.')
426
426
427 def test_forgot_password(self, user_util):
427 def test_forgot_password(self, user_util):
428 # this initializes a session
428 # this initializes a session
429 self.app.get(route_path('reset_password'))
429 self.app.get(route_path('reset_password'))
430
430
431 user = user_util.create_user()
431 user = user_util.create_user()
432 user_id = user.user_id
432 user_id = user.user_id
433 email = user.email
433 email = user.email
434
434
435 response = self.app.post(route_path('reset_password'), {'email': email, })
435 response = self.app.post(route_path('reset_password'), {'email': email, })
436
436
437 assert_session_flash(response,
437 assert_session_flash(response,
438 '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.')
439
439
440 # BAD KEY
440 # BAD KEY
441 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
441 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
442 response = self.app.get(confirm_url, status=302)
442 response = self.app.get(confirm_url, status=302)
443 assert response.location.endswith(route_path('reset_password'))
443 assert response.location.endswith(route_path('reset_password'))
444 assert_session_flash(response, 'Given reset token is invalid')
444 assert_session_flash(response, 'Given reset token is invalid')
445
445
446 response.follow() # cleanup flash
446 response.follow() # cleanup flash
447
447
448 # GOOD KEY
448 # GOOD KEY
449 key = UserApiKeys.query()\
449 key = UserApiKeys.query()\
450 .filter(UserApiKeys.user_id == user_id)\
450 .filter(UserApiKeys.user_id == user_id)\
451 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
451 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
452 .first()
452 .first()
453
453
454 assert key
454 assert key
455
455
456 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)
457 response = self.app.get(confirm_url)
457 response = self.app.get(confirm_url)
458 assert response.status == '302 Found'
458 assert response.status == '302 Found'
459 assert response.location.endswith(route_path('login'))
459 assert response.location.endswith(route_path('login'))
460
460
461 assert_session_flash(
461 assert_session_flash(
462 response,
462 response,
463 'Your password reset was successful, '
463 'Your password reset was successful, '
464 'a new password has been sent to your email')
464 'a new password has been sent to your email')
465
465
466 response.follow()
466 response.follow()
467
467
468 def _get_api_whitelist(self, values=None):
468 def _get_api_whitelist(self, values=None):
469 config = {'api_access_controllers_whitelist': values or []}
469 config = {'api_access_controllers_whitelist': values or []}
470 return config
470 return config
471
471
472 @pytest.mark.parametrize("test_name, auth_token", [
472 @pytest.mark.parametrize("test_name, auth_token", [
473 ('none', None),
473 ('none', None),
474 ('empty_string', ''),
474 ('empty_string', ''),
475 ('fake_number', '123456'),
475 ('fake_number', '123456'),
476 ('proper_auth_token', None)
476 ('proper_auth_token', None)
477 ])
477 ])
478 def test_access_not_whitelisted_page_via_auth_token(
478 def test_access_not_whitelisted_page_via_auth_token(
479 self, test_name, auth_token, user_admin):
479 self, test_name, auth_token, user_admin):
480
480
481 whitelist = self._get_api_whitelist([])
481 whitelist = self._get_api_whitelist([])
482 with mock.patch.dict('rhodecode.CONFIG', whitelist):
482 with mock.patch.dict('rhodecode.CONFIG', whitelist):
483 assert [] == whitelist['api_access_controllers_whitelist']
483 assert [] == whitelist['api_access_controllers_whitelist']
484 if test_name == 'proper_auth_token':
484 if test_name == 'proper_auth_token':
485 # use builtin if api_key is None
485 # use builtin if api_key is None
486 auth_token = user_admin.api_key
486 auth_token = user_admin.api_key
487
487
488 with fixture.anon_access(False):
488 with fixture.anon_access(False):
489 self.app.get(
489 self.app.get(
490 route_path('repo_commit_raw',
490 route_path('repo_commit_raw',
491 repo_name=HG_REPO, commit_id='tip',
491 repo_name=HG_REPO, commit_id='tip',
492 params=dict(api_key=auth_token)),
492 params=dict(api_key=auth_token)),
493 status=302)
493 status=302)
494
494
495 @pytest.mark.parametrize("test_name, auth_token, code", [
495 @pytest.mark.parametrize("test_name, auth_token, code", [
496 ('none', None, 302),
496 ('none', None, 302),
497 ('empty_string', '', 302),
497 ('empty_string', '', 302),
498 ('fake_number', '123456', 302),
498 ('fake_number', '123456', 302),
499 ('proper_auth_token', None, 200)
499 ('proper_auth_token', None, 200)
500 ])
500 ])
501 def test_access_whitelisted_page_via_auth_token(
501 def test_access_whitelisted_page_via_auth_token(
502 self, test_name, auth_token, code, user_admin):
502 self, test_name, auth_token, code, user_admin):
503
503
504 whitelist = self._get_api_whitelist(whitelist_view)
504 whitelist = self._get_api_whitelist(whitelist_view)
505
505
506 with mock.patch.dict('rhodecode.CONFIG', whitelist):
506 with mock.patch.dict('rhodecode.CONFIG', whitelist):
507 assert whitelist_view == whitelist['api_access_controllers_whitelist']
507 assert whitelist_view == whitelist['api_access_controllers_whitelist']
508
508
509 if test_name == 'proper_auth_token':
509 if test_name == 'proper_auth_token':
510 auth_token = user_admin.api_key
510 auth_token = user_admin.api_key
511 assert auth_token
511 assert auth_token
512
512
513 with fixture.anon_access(False):
513 with fixture.anon_access(False):
514 self.app.get(
514 self.app.get(
515 route_path('repo_commit_raw',
515 route_path('repo_commit_raw',
516 repo_name=HG_REPO, commit_id='tip',
516 repo_name=HG_REPO, commit_id='tip',
517 params=dict(api_key=auth_token)),
517 params=dict(api_key=auth_token)),
518 status=code)
518 status=code)
519
519
520 @pytest.mark.parametrize("test_name, auth_token, code", [
520 @pytest.mark.parametrize("test_name, auth_token, code", [
521 ('proper_auth_token', None, 200),
521 ('proper_auth_token', None, 200),
522 ('wrong_auth_token', '123456', 302),
522 ('wrong_auth_token', '123456', 302),
523 ])
523 ])
524 def test_access_whitelisted_page_via_auth_token_bound_to_token(
524 def test_access_whitelisted_page_via_auth_token_bound_to_token(
525 self, test_name, auth_token, code, user_admin):
525 self, test_name, auth_token, code, user_admin):
526
526
527 expected_token = auth_token
527 expected_token = auth_token
528 if test_name == 'proper_auth_token':
528 if test_name == 'proper_auth_token':
529 auth_token = user_admin.api_key
529 auth_token = user_admin.api_key
530 expected_token = auth_token
530 expected_token = auth_token
531 assert auth_token
531 assert auth_token
532
532
533 whitelist = self._get_api_whitelist([
533 whitelist = self._get_api_whitelist([
534 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
534 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
535
535
536 with mock.patch.dict('rhodecode.CONFIG', whitelist):
536 with mock.patch.dict('rhodecode.CONFIG', whitelist):
537
537
538 with fixture.anon_access(False):
538 with fixture.anon_access(False):
539 self.app.get(
539 self.app.get(
540 route_path('repo_commit_raw',
540 route_path('repo_commit_raw',
541 repo_name=HG_REPO, commit_id='tip',
541 repo_name=HG_REPO, commit_id='tip',
542 params=dict(api_key=auth_token)),
542 params=dict(api_key=auth_token)),
543 status=code)
543 status=code)
544
544
545 def test_access_page_via_extra_auth_token(self):
545 def test_access_page_via_extra_auth_token(self):
546 whitelist = self._get_api_whitelist(whitelist_view)
546 whitelist = self._get_api_whitelist(whitelist_view)
547 with mock.patch.dict('rhodecode.CONFIG', whitelist):
547 with mock.patch.dict('rhodecode.CONFIG', whitelist):
548 assert whitelist_view == \
548 assert whitelist_view == \
549 whitelist['api_access_controllers_whitelist']
549 whitelist['api_access_controllers_whitelist']
550
550
551 new_auth_token = AuthTokenModel().create(
551 new_auth_token = AuthTokenModel().create(
552 TEST_USER_ADMIN_LOGIN, 'test')
552 TEST_USER_ADMIN_LOGIN, 'test')
553 Session().commit()
553 Session().commit()
554 with fixture.anon_access(False):
554 with fixture.anon_access(False):
555 self.app.get(
555 self.app.get(
556 route_path('repo_commit_raw',
556 route_path('repo_commit_raw',
557 repo_name=HG_REPO, commit_id='tip',
557 repo_name=HG_REPO, commit_id='tip',
558 params=dict(api_key=new_auth_token.api_key)),
558 params=dict(api_key=new_auth_token.api_key)),
559 status=200)
559 status=200)
560
560
561 def test_access_page_via_expired_auth_token(self):
561 def test_access_page_via_expired_auth_token(self):
562 whitelist = self._get_api_whitelist(whitelist_view)
562 whitelist = self._get_api_whitelist(whitelist_view)
563 with mock.patch.dict('rhodecode.CONFIG', whitelist):
563 with mock.patch.dict('rhodecode.CONFIG', whitelist):
564 assert whitelist_view == \
564 assert whitelist_view == \
565 whitelist['api_access_controllers_whitelist']
565 whitelist['api_access_controllers_whitelist']
566
566
567 new_auth_token = AuthTokenModel().create(
567 new_auth_token = AuthTokenModel().create(
568 TEST_USER_ADMIN_LOGIN, 'test')
568 TEST_USER_ADMIN_LOGIN, 'test')
569 Session().commit()
569 Session().commit()
570 # patch the api key and make it expired
570 # patch the api key and make it expired
571 new_auth_token.expires = 0
571 new_auth_token.expires = 0
572 Session().add(new_auth_token)
572 Session().add(new_auth_token)
573 Session().commit()
573 Session().commit()
574 with fixture.anon_access(False):
574 with fixture.anon_access(False):
575 self.app.get(
575 self.app.get(
576 route_path('repo_commit_raw',
576 route_path('repo_commit_raw',
577 repo_name=HG_REPO, commit_id='tip',
577 repo_name=HG_REPO, commit_id='tip',
578 params=dict(api_key=new_auth_token.api_key)),
578 params=dict(api_key=new_auth_token.api_key)),
579 status=302)
579 status=302)
@@ -1,486 +1,489 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 import time
21 import time
22 import collections
22 import collections
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import logging
26 import logging
27 import urlparse
27 import urlparse
28 import requests
28 import requests
29
29
30 from pyramid.httpexceptions import HTTPFound
30 from pyramid.httpexceptions import HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import UserRegistered, trigger
36 from rhodecode.events import UserRegistered, trigger
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
41 from rhodecode.lib.base import get_ip_addr
41 from rhodecode.lib.base import get_ip_addr
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.db import User, UserApiKeys
45 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.settings import SettingsModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.translation import _
50 from rhodecode.translation import _
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55 CaptchaData = collections.namedtuple(
55 CaptchaData = collections.namedtuple(
56 'CaptchaData', 'active, private_key, public_key')
56 'CaptchaData', 'active, private_key, public_key')
57
57
58
58
59 def store_user_in_session(session, username, remember=False):
59 def store_user_in_session(session, username, remember=False):
60 user = User.get_by_username(username, case_insensitive=True)
60 user = User.get_by_username(username, case_insensitive=True)
61 auth_user = AuthUser(user.user_id)
61 auth_user = AuthUser(user.user_id)
62 auth_user.set_authenticated()
62 auth_user.set_authenticated()
63 cs = auth_user.get_cookie_store()
63 cs = auth_user.get_cookie_store()
64 session['rhodecode_user'] = cs
64 session['rhodecode_user'] = cs
65 user.update_lastlogin()
65 user.update_lastlogin()
66 Session().commit()
66 Session().commit()
67
67
68 # If they want to be remembered, update the cookie
68 # If they want to be remembered, update the cookie
69 if remember:
69 if remember:
70 _year = (datetime.datetime.now() +
70 _year = (datetime.datetime.now() +
71 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 datetime.timedelta(seconds=60 * 60 * 24 * 365))
72 session._set_cookie_expires(_year)
72 session._set_cookie_expires(_year)
73
73
74 session.save()
74 session.save()
75
75
76 safe_cs = cs.copy()
76 safe_cs = cs.copy()
77 safe_cs['password'] = '****'
77 safe_cs['password'] = '****'
78 log.info('user %s is now authenticated and stored in '
78 log.info('user %s is now authenticated and stored in '
79 'session, session attrs %s', username, safe_cs)
79 'session, session attrs %s', username, safe_cs)
80
80
81 # dumps session attrs back to cookie
81 # dumps session attrs back to cookie
82 session._update_cookie_out()
82 session._update_cookie_out()
83 # we set new cookie
83 # we set new cookie
84 headers = None
84 headers = None
85 if session.request['set_cookie']:
85 if session.request['set_cookie']:
86 # send set-cookie headers back to response to update cookie
86 # send set-cookie headers back to response to update cookie
87 headers = [('Set-Cookie', session.request['cookie_out'])]
87 headers = [('Set-Cookie', session.request['cookie_out'])]
88 return headers
88 return headers
89
89
90
90
91 def get_came_from(request):
91 def get_came_from(request):
92 came_from = safe_str(request.GET.get('came_from', ''))
92 came_from = safe_str(request.GET.get('came_from', ''))
93 parsed = urlparse.urlparse(came_from)
93 parsed = urlparse.urlparse(came_from)
94 allowed_schemes = ['http', 'https']
94 allowed_schemes = ['http', 'https']
95 default_came_from = h.route_path('home')
95 default_came_from = h.route_path('home')
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
97 log.error('Suspicious URL scheme detected %s for url %s',
97 log.error('Suspicious URL scheme detected %s for url %s',
98 parsed.scheme, parsed)
98 parsed.scheme, parsed)
99 came_from = default_came_from
99 came_from = default_came_from
100 elif parsed.netloc and request.host != parsed.netloc:
100 elif parsed.netloc and request.host != parsed.netloc:
101 log.error('Suspicious NETLOC detected %s for url %s server url '
101 log.error('Suspicious NETLOC detected %s for url %s server url '
102 'is: %s', parsed.netloc, parsed, request.host)
102 'is: %s', parsed.netloc, parsed, request.host)
103 came_from = default_came_from
103 came_from = default_came_from
104 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
105 log.error('Header injection detected `%s` for url %s server url ',
105 log.error('Header injection detected `%s` for url %s server url ',
106 parsed.path, parsed)
106 parsed.path, parsed)
107 came_from = default_came_from
107 came_from = default_came_from
108
108
109 return came_from or default_came_from
109 return came_from or default_came_from
110
110
111
111
112 class LoginView(BaseAppView):
112 class LoginView(BaseAppView):
113
113
114 def load_default_context(self):
114 def load_default_context(self):
115 c = self._get_local_tmpl_context()
115 c = self._get_local_tmpl_context()
116 c.came_from = get_came_from(self.request)
116 c.came_from = get_came_from(self.request)
117
117
118 return c
118 return c
119
119
120 def _get_captcha_data(self):
120 def _get_captcha_data(self):
121 settings = SettingsModel().get_all_settings()
121 settings = SettingsModel().get_all_settings()
122 private_key = settings.get('rhodecode_captcha_private_key')
122 private_key = settings.get('rhodecode_captcha_private_key')
123 public_key = settings.get('rhodecode_captcha_public_key')
123 public_key = settings.get('rhodecode_captcha_public_key')
124 active = bool(private_key)
124 active = bool(private_key)
125 return CaptchaData(
125 return CaptchaData(
126 active=active, private_key=private_key, public_key=public_key)
126 active=active, private_key=private_key, public_key=public_key)
127
127
128 def validate_captcha(self, private_key):
128 def validate_captcha(self, private_key):
129
129
130 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 captcha_rs = self.request.POST.get('g-recaptcha-response')
131 url = "https://www.google.com/recaptcha/api/siteverify"
131 url = "https://www.google.com/recaptcha/api/siteverify"
132 params = {
132 params = {
133 'secret': private_key,
133 'secret': private_key,
134 'response': captcha_rs,
134 'response': captcha_rs,
135 'remoteip': get_ip_addr(self.request.environ)
135 'remoteip': get_ip_addr(self.request.environ)
136 }
136 }
137 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
138 verify_rs = verify_rs.json()
138 verify_rs = verify_rs.json()
139 captcha_status = verify_rs.get('success', False)
139 captcha_status = verify_rs.get('success', False)
140 captcha_errors = verify_rs.get('error-codes', [])
140 captcha_errors = verify_rs.get('error-codes', [])
141 if not isinstance(captcha_errors, list):
141 if not isinstance(captcha_errors, list):
142 captcha_errors = [captcha_errors]
142 captcha_errors = [captcha_errors]
143 captcha_errors = ', '.join(captcha_errors)
143 captcha_errors = ', '.join(captcha_errors)
144 captcha_message = ''
144 captcha_message = ''
145 if captcha_status is False:
145 if captcha_status is False:
146 captcha_message = "Bad captcha. Errors: {}".format(
146 captcha_message = "Bad captcha. Errors: {}".format(
147 captcha_errors)
147 captcha_errors)
148
148
149 return captcha_status, captcha_message
149 return captcha_status, captcha_message
150
150
151 @view_config(
151 @view_config(
152 route_name='login', request_method='GET',
152 route_name='login', request_method='GET',
153 renderer='rhodecode:templates/login.mako')
153 renderer='rhodecode:templates/login.mako')
154 def login(self):
154 def login(self):
155 c = self.load_default_context()
155 c = self.load_default_context()
156 auth_user = self._rhodecode_user
156 auth_user = self._rhodecode_user
157
157
158 # redirect if already logged in
158 # redirect if already logged in
159 if (auth_user.is_authenticated and
159 if (auth_user.is_authenticated and
160 not auth_user.is_default and auth_user.ip_allowed):
160 not auth_user.is_default and auth_user.ip_allowed):
161 raise HTTPFound(c.came_from)
161 raise HTTPFound(c.came_from)
162
162
163 # check if we use headers plugin, and try to login using it.
163 # check if we use headers plugin, and try to login using it.
164 try:
164 try:
165 log.debug('Running PRE-AUTH for headers based authentication')
165 log.debug('Running PRE-AUTH for headers based authentication')
166 auth_info = authenticate(
166 auth_info = authenticate(
167 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
168 if auth_info:
168 if auth_info:
169 headers = store_user_in_session(
169 headers = store_user_in_session(
170 self.session, auth_info.get('username'))
170 self.session, auth_info.get('username'))
171 raise HTTPFound(c.came_from, headers=headers)
171 raise HTTPFound(c.came_from, headers=headers)
172 except UserCreationError as e:
172 except UserCreationError as e:
173 log.error(e)
173 log.error(e)
174 h.flash(e, category='error')
174 h.flash(e, category='error')
175
175
176 return self._get_template_context(c)
176 return self._get_template_context(c)
177
177
178 @view_config(
178 @view_config(
179 route_name='login', request_method='POST',
179 route_name='login', request_method='POST',
180 renderer='rhodecode:templates/login.mako')
180 renderer='rhodecode:templates/login.mako')
181 def login_post(self):
181 def login_post(self):
182 c = self.load_default_context()
182 c = self.load_default_context()
183
183
184 login_form = LoginForm(self.request.translate)()
184 login_form = LoginForm(self.request.translate)()
185
185
186 try:
186 try:
187 self.session.invalidate()
187 self.session.invalidate()
188 form_result = login_form.to_python(self.request.POST)
188 form_result = login_form.to_python(self.request.POST)
189 # form checks for username/password, now we're authenticated
189 # form checks for username/password, now we're authenticated
190 headers = store_user_in_session(
190 headers = store_user_in_session(
191 self.session,
191 self.session,
192 username=form_result['username'],
192 username=form_result['username'],
193 remember=form_result['remember'])
193 remember=form_result['remember'])
194 log.debug('Redirecting to "%s" after login.', c.came_from)
194 log.debug('Redirecting to "%s" after login.', c.came_from)
195
195
196 audit_user = audit_logger.UserWrap(
196 audit_user = audit_logger.UserWrap(
197 username=self.request.POST.get('username'),
197 username=self.request.POST.get('username'),
198 ip_addr=self.request.remote_addr)
198 ip_addr=self.request.remote_addr)
199 action_data = {'user_agent': self.request.user_agent}
199 action_data = {'user_agent': self.request.user_agent}
200 audit_logger.store_web(
200 audit_logger.store_web(
201 'user.login.success', action_data=action_data,
201 'user.login.success', action_data=action_data,
202 user=audit_user, commit=True)
202 user=audit_user, commit=True)
203
203
204 raise HTTPFound(c.came_from, headers=headers)
204 raise HTTPFound(c.came_from, headers=headers)
205 except formencode.Invalid as errors:
205 except formencode.Invalid as errors:
206 defaults = errors.value
206 defaults = errors.value
207 # remove password from filling in form again
207 # remove password from filling in form again
208 defaults.pop('password', None)
208 defaults.pop('password', None)
209 render_ctx = {
209 render_ctx = {
210 'errors': errors.error_dict,
210 'errors': errors.error_dict,
211 'defaults': defaults,
211 'defaults': defaults,
212 }
212 }
213
213
214 audit_user = audit_logger.UserWrap(
214 audit_user = audit_logger.UserWrap(
215 username=self.request.POST.get('username'),
215 username=self.request.POST.get('username'),
216 ip_addr=self.request.remote_addr)
216 ip_addr=self.request.remote_addr)
217 action_data = {'user_agent': self.request.user_agent}
217 action_data = {'user_agent': self.request.user_agent}
218 audit_logger.store_web(
218 audit_logger.store_web(
219 'user.login.failure', action_data=action_data,
219 'user.login.failure', action_data=action_data,
220 user=audit_user, commit=True)
220 user=audit_user, commit=True)
221 return self._get_template_context(c, **render_ctx)
221 return self._get_template_context(c, **render_ctx)
222
222
223 except UserCreationError as e:
223 except UserCreationError as e:
224 # headers auth or other auth functions that create users on
224 # headers auth or other auth functions that create users on
225 # the fly can throw this exception signaling that there's issue
225 # the fly can throw this exception signaling that there's issue
226 # with user creation, explanation should be provided in
226 # with user creation, explanation should be provided in
227 # Exception itself
227 # Exception itself
228 h.flash(e, category='error')
228 h.flash(e, category='error')
229 return self._get_template_context(c)
229 return self._get_template_context(c)
230
230
231 @CSRFRequired()
231 @CSRFRequired()
232 @view_config(route_name='logout', request_method='POST')
232 @view_config(route_name='logout', request_method='POST')
233 def logout(self):
233 def logout(self):
234 auth_user = self._rhodecode_user
234 auth_user = self._rhodecode_user
235 log.info('Deleting session for user: `%s`', auth_user)
235 log.info('Deleting session for user: `%s`', auth_user)
236
236
237 action_data = {'user_agent': self.request.user_agent}
237 action_data = {'user_agent': self.request.user_agent}
238 audit_logger.store_web(
238 audit_logger.store_web(
239 'user.logout', action_data=action_data,
239 'user.logout', action_data=action_data,
240 user=auth_user, commit=True)
240 user=auth_user, commit=True)
241 self.session.delete()
241 self.session.delete()
242 return HTTPFound(h.route_path('home'))
242 return HTTPFound(h.route_path('home'))
243
243
244 @HasPermissionAnyDecorator(
244 @HasPermissionAnyDecorator(
245 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
246 @view_config(
246 @view_config(
247 route_name='register', request_method='GET',
247 route_name='register', request_method='GET',
248 renderer='rhodecode:templates/register.mako',)
248 renderer='rhodecode:templates/register.mako',)
249 def register(self, defaults=None, errors=None):
249 def register(self, defaults=None, errors=None):
250 c = self.load_default_context()
250 c = self.load_default_context()
251 defaults = defaults or {}
251 defaults = defaults or {}
252 errors = errors or {}
252 errors = errors or {}
253
253
254 settings = SettingsModel().get_all_settings()
254 settings = SettingsModel().get_all_settings()
255 register_message = settings.get('rhodecode_register_message') or ''
255 register_message = settings.get('rhodecode_register_message') or ''
256 captcha = self._get_captcha_data()
256 captcha = self._get_captcha_data()
257 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
258 .AuthUser().permissions['global']
258 .AuthUser().permissions['global']
259
259
260 render_ctx = self._get_template_context(c)
260 render_ctx = self._get_template_context(c)
261 render_ctx.update({
261 render_ctx.update({
262 'defaults': defaults,
262 'defaults': defaults,
263 'errors': errors,
263 'errors': errors,
264 'auto_active': auto_active,
264 'auto_active': auto_active,
265 'captcha_active': captcha.active,
265 'captcha_active': captcha.active,
266 'captcha_public_key': captcha.public_key,
266 'captcha_public_key': captcha.public_key,
267 'register_message': register_message,
267 'register_message': register_message,
268 })
268 })
269 return render_ctx
269 return render_ctx
270
270
271 @HasPermissionAnyDecorator(
271 @HasPermissionAnyDecorator(
272 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
273 @view_config(
273 @view_config(
274 route_name='register', request_method='POST',
274 route_name='register', request_method='POST',
275 renderer='rhodecode:templates/register.mako')
275 renderer='rhodecode:templates/register.mako')
276 def register_post(self):
276 def register_post(self):
277 from rhodecode.authentication.plugins import auth_rhodecode
277 from rhodecode.authentication.plugins import auth_rhodecode
278
278
279 self.load_default_context()
279 self.load_default_context()
280 captcha = self._get_captcha_data()
280 captcha = self._get_captcha_data()
281 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
281 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
282 .AuthUser().permissions['global']
282 .AuthUser().permissions['global']
283
283
284 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
284 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
285 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
285 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
286
286
287 register_form = RegisterForm(self.request.translate)()
287 register_form = RegisterForm(self.request.translate)()
288 try:
288 try:
289
289
290 form_result = register_form.to_python(self.request.POST)
290 form_result = register_form.to_python(self.request.POST)
291 form_result['active'] = auto_active
291 form_result['active'] = auto_active
292 external_identity = self.request.POST.get('external_identity')
292 external_identity = self.request.POST.get('external_identity')
293
293
294 if external_identity:
294 if external_identity:
295 extern_name = external_identity
295 extern_name = external_identity
296 extern_type = external_identity
296 extern_type = external_identity
297
297
298 if captcha.active:
298 if captcha.active:
299 captcha_status, captcha_message = self.validate_captcha(
299 captcha_status, captcha_message = self.validate_captcha(
300 captcha.private_key)
300 captcha.private_key)
301
301
302 if not captcha_status:
302 if not captcha_status:
303 _value = form_result
303 _value = form_result
304 _msg = _('Bad captcha')
304 _msg = _('Bad captcha')
305 error_dict = {'recaptcha_field': captcha_message}
305 error_dict = {'recaptcha_field': captcha_message}
306 raise formencode.Invalid(
306 raise formencode.Invalid(
307 _msg, _value, None, error_dict=error_dict)
307 _msg, _value, None, error_dict=error_dict)
308
308
309 new_user = UserModel().create_registration(
309 new_user = UserModel().create_registration(
310 form_result, extern_name=extern_name, extern_type=extern_type)
310 form_result, extern_name=extern_name, extern_type=extern_type)
311
311
312 action_data = {'data': new_user.get_api_data(),
312 action_data = {'data': new_user.get_api_data(),
313 'user_agent': self.request.user_agent}
313 'user_agent': self.request.user_agent}
314
314
315
316
317 if external_identity:
315 if external_identity:
318 action_data['external_identity'] = external_identity
316 action_data['external_identity'] = external_identity
319
317
320 audit_user = audit_logger.UserWrap(
318 audit_user = audit_logger.UserWrap(
321 username=new_user.username,
319 username=new_user.username,
322 user_id=new_user.user_id,
320 user_id=new_user.user_id,
323 ip_addr=self.request.remote_addr)
321 ip_addr=self.request.remote_addr)
324
322
325 audit_logger.store_web(
323 audit_logger.store_web(
326 'user.register', action_data=action_data,
324 'user.register', action_data=action_data,
327 user=audit_user)
325 user=audit_user)
328
326
329 event = UserRegistered(user=new_user, session=self.session)
327 event = UserRegistered(user=new_user, session=self.session)
330 trigger(event)
328 trigger(event)
331 h.flash(
329 h.flash(
332 _('You have successfully registered with RhodeCode'),
330 _('You have successfully registered with RhodeCode. You can log-in now.'),
333 category='success')
331 category='success')
332 if external_identity:
333 h.flash(
334 _('Please use the {identity} button to log-in').format(
335 identity=external_identity),
336 category='success')
334 Session().commit()
337 Session().commit()
335
338
336 redirect_ro = self.request.route_path('login')
339 redirect_ro = self.request.route_path('login')
337 raise HTTPFound(redirect_ro)
340 raise HTTPFound(redirect_ro)
338
341
339 except formencode.Invalid as errors:
342 except formencode.Invalid as errors:
340 errors.value.pop('password', None)
343 errors.value.pop('password', None)
341 errors.value.pop('password_confirmation', None)
344 errors.value.pop('password_confirmation', None)
342 return self.register(
345 return self.register(
343 defaults=errors.value, errors=errors.error_dict)
346 defaults=errors.value, errors=errors.error_dict)
344
347
345 except UserCreationError as e:
348 except UserCreationError as e:
346 # container auth or other auth functions that create users on
349 # container auth or other auth functions that create users on
347 # the fly can throw this exception signaling that there's issue
350 # the fly can throw this exception signaling that there's issue
348 # with user creation, explanation should be provided in
351 # with user creation, explanation should be provided in
349 # Exception itself
352 # Exception itself
350 h.flash(e, category='error')
353 h.flash(e, category='error')
351 return self.register()
354 return self.register()
352
355
353 @view_config(
356 @view_config(
354 route_name='reset_password', request_method=('GET', 'POST'),
357 route_name='reset_password', request_method=('GET', 'POST'),
355 renderer='rhodecode:templates/password_reset.mako')
358 renderer='rhodecode:templates/password_reset.mako')
356 def password_reset(self):
359 def password_reset(self):
357 c = self.load_default_context()
360 c = self.load_default_context()
358 captcha = self._get_captcha_data()
361 captcha = self._get_captcha_data()
359
362
360 template_context = {
363 template_context = {
361 'captcha_active': captcha.active,
364 'captcha_active': captcha.active,
362 'captcha_public_key': captcha.public_key,
365 'captcha_public_key': captcha.public_key,
363 'defaults': {},
366 'defaults': {},
364 'errors': {},
367 'errors': {},
365 }
368 }
366
369
367 # always send implicit message to prevent from discovery of
370 # always send implicit message to prevent from discovery of
368 # matching emails
371 # matching emails
369 msg = _('If such email exists, a password reset link was sent to it.')
372 msg = _('If such email exists, a password reset link was sent to it.')
370
373
371 def default_response():
374 def default_response():
372 log.debug('faking response on invalid password reset')
375 log.debug('faking response on invalid password reset')
373 # make this take 2s, to prevent brute forcing.
376 # make this take 2s, to prevent brute forcing.
374 time.sleep(2)
377 time.sleep(2)
375 h.flash(msg, category='success')
378 h.flash(msg, category='success')
376 return HTTPFound(self.request.route_path('reset_password'))
379 return HTTPFound(self.request.route_path('reset_password'))
377
380
378 if self.request.POST:
381 if self.request.POST:
379 if h.HasPermissionAny('hg.password_reset.disabled')():
382 if h.HasPermissionAny('hg.password_reset.disabled')():
380 _email = self.request.POST.get('email', '')
383 _email = self.request.POST.get('email', '')
381 log.error('Failed attempt to reset password for `%s`.', _email)
384 log.error('Failed attempt to reset password for `%s`.', _email)
382 h.flash(_('Password reset has been disabled.'), category='error')
385 h.flash(_('Password reset has been disabled.'), category='error')
383 return HTTPFound(self.request.route_path('reset_password'))
386 return HTTPFound(self.request.route_path('reset_password'))
384
387
385 password_reset_form = PasswordResetForm(self.request.translate)()
388 password_reset_form = PasswordResetForm(self.request.translate)()
386 description = u'Generated token for password reset from {}'.format(
389 description = u'Generated token for password reset from {}'.format(
387 datetime.datetime.now().isoformat())
390 datetime.datetime.now().isoformat())
388
391
389 try:
392 try:
390 form_result = password_reset_form.to_python(
393 form_result = password_reset_form.to_python(
391 self.request.POST)
394 self.request.POST)
392 user_email = form_result['email']
395 user_email = form_result['email']
393
396
394 if captcha.active:
397 if captcha.active:
395 captcha_status, captcha_message = self.validate_captcha(
398 captcha_status, captcha_message = self.validate_captcha(
396 captcha.private_key)
399 captcha.private_key)
397
400
398 if not captcha_status:
401 if not captcha_status:
399 _value = form_result
402 _value = form_result
400 _msg = _('Bad captcha')
403 _msg = _('Bad captcha')
401 error_dict = {'recaptcha_field': captcha_message}
404 error_dict = {'recaptcha_field': captcha_message}
402 raise formencode.Invalid(
405 raise formencode.Invalid(
403 _msg, _value, None, error_dict=error_dict)
406 _msg, _value, None, error_dict=error_dict)
404
407
405 # Generate reset URL and send mail.
408 # Generate reset URL and send mail.
406 user = User.get_by_email(user_email)
409 user = User.get_by_email(user_email)
407
410
408 # only allow rhodecode based users to reset their password
411 # only allow rhodecode based users to reset their password
409 # external auth shouldn't allow password reset
412 # external auth shouldn't allow password reset
410 if user and user.extern_type != auth_rhodecode.RhodeCodeAuthPlugin.uid:
413 if user and user.extern_type != auth_rhodecode.RhodeCodeAuthPlugin.uid:
411 log.warning('User %s with external type `%s` tried a password reset. '
414 log.warning('User %s with external type `%s` tried a password reset. '
412 'This try was rejected', user, user.extern_type)
415 'This try was rejected', user, user.extern_type)
413 return default_response()
416 return default_response()
414
417
415 # generate password reset token that expires in 10 minutes
418 # generate password reset token that expires in 10 minutes
416 reset_token = UserModel().add_auth_token(
419 reset_token = UserModel().add_auth_token(
417 user=user, lifetime_minutes=10,
420 user=user, lifetime_minutes=10,
418 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
421 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
419 description=description)
422 description=description)
420 Session().commit()
423 Session().commit()
421
424
422 log.debug('Successfully created password recovery token')
425 log.debug('Successfully created password recovery token')
423 password_reset_url = self.request.route_url(
426 password_reset_url = self.request.route_url(
424 'reset_password_confirmation',
427 'reset_password_confirmation',
425 _query={'key': reset_token.api_key})
428 _query={'key': reset_token.api_key})
426 UserModel().reset_password_link(
429 UserModel().reset_password_link(
427 form_result, password_reset_url)
430 form_result, password_reset_url)
428
431
429 action_data = {'email': user_email,
432 action_data = {'email': user_email,
430 'user_agent': self.request.user_agent}
433 'user_agent': self.request.user_agent}
431 audit_logger.store_web(
434 audit_logger.store_web(
432 'user.password.reset_request', action_data=action_data,
435 'user.password.reset_request', action_data=action_data,
433 user=self._rhodecode_user, commit=True)
436 user=self._rhodecode_user, commit=True)
434
437
435 return default_response()
438 return default_response()
436
439
437 except formencode.Invalid as errors:
440 except formencode.Invalid as errors:
438 template_context.update({
441 template_context.update({
439 'defaults': errors.value,
442 'defaults': errors.value,
440 'errors': errors.error_dict,
443 'errors': errors.error_dict,
441 })
444 })
442 if not self.request.POST.get('email'):
445 if not self.request.POST.get('email'):
443 # case of empty email, we want to report that
446 # case of empty email, we want to report that
444 return self._get_template_context(c, **template_context)
447 return self._get_template_context(c, **template_context)
445
448
446 if 'recaptcha_field' in errors.error_dict:
449 if 'recaptcha_field' in errors.error_dict:
447 # case of failed captcha
450 # case of failed captcha
448 return self._get_template_context(c, **template_context)
451 return self._get_template_context(c, **template_context)
449
452
450 return default_response()
453 return default_response()
451
454
452 return self._get_template_context(c, **template_context)
455 return self._get_template_context(c, **template_context)
453
456
454 @view_config(route_name='reset_password_confirmation',
457 @view_config(route_name='reset_password_confirmation',
455 request_method='GET')
458 request_method='GET')
456 def password_reset_confirmation(self):
459 def password_reset_confirmation(self):
457 self.load_default_context()
460 self.load_default_context()
458 if self.request.GET and self.request.GET.get('key'):
461 if self.request.GET and self.request.GET.get('key'):
459 # make this take 2s, to prevent brute forcing.
462 # make this take 2s, to prevent brute forcing.
460 time.sleep(2)
463 time.sleep(2)
461
464
462 token = AuthTokenModel().get_auth_token(
465 token = AuthTokenModel().get_auth_token(
463 self.request.GET.get('key'))
466 self.request.GET.get('key'))
464
467
465 # verify token is the correct role
468 # verify token is the correct role
466 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
469 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
467 log.debug('Got token with role:%s expected is %s',
470 log.debug('Got token with role:%s expected is %s',
468 getattr(token, 'role', 'EMPTY_TOKEN'),
471 getattr(token, 'role', 'EMPTY_TOKEN'),
469 UserApiKeys.ROLE_PASSWORD_RESET)
472 UserApiKeys.ROLE_PASSWORD_RESET)
470 h.flash(
473 h.flash(
471 _('Given reset token is invalid'), category='error')
474 _('Given reset token is invalid'), category='error')
472 return HTTPFound(self.request.route_path('reset_password'))
475 return HTTPFound(self.request.route_path('reset_password'))
473
476
474 try:
477 try:
475 owner = token.user
478 owner = token.user
476 data = {'email': owner.email, 'token': token.api_key}
479 data = {'email': owner.email, 'token': token.api_key}
477 UserModel().reset_password(data)
480 UserModel().reset_password(data)
478 h.flash(
481 h.flash(
479 _('Your password reset was successful, '
482 _('Your password reset was successful, '
480 'a new password has been sent to your email'),
483 'a new password has been sent to your email'),
481 category='success')
484 category='success')
482 except Exception as e:
485 except Exception as e:
483 log.error(e)
486 log.error(e)
484 return HTTPFound(self.request.route_path('reset_password'))
487 return HTTPFound(self.request.route_path('reset_password'))
485
488
486 return HTTPFound(self.request.route_path('login'))
489 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now