##// END OF EJS Templates
password-reset: strengthten security on password reset logic....
marcink -
r1471:9ea7077d default
parent child Browse files
Show More
@@ -0,0 +1,106 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.tests import (
25 TestController, clear_all_caches, url,
26 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
27 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.utils import AssertResponse
29
30 fixture = Fixture()
31
32 # Hardcode URLs because we don't have a request object to use
33 # pyramids URL generation methods.
34 index_url = '/'
35 login_url = ADMIN_PREFIX + '/login'
36 logut_url = ADMIN_PREFIX + '/logout'
37 register_url = ADMIN_PREFIX + '/register'
38 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
39 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
40
41
42 class TestPasswordReset(TestController):
43
44 @pytest.mark.parametrize(
45 'pwd_reset_setting, show_link, show_reset', [
46 ('hg.password_reset.enabled', True, True),
47 ('hg.password_reset.hidden', False, True),
48 ('hg.password_reset.disabled', False, False),
49 ])
50 def test_password_reset_settings(
51 self, pwd_reset_setting, show_link, show_reset):
52 clear_all_caches()
53 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
54 params = {
55 'csrf_token': self.csrf_token,
56 'anonymous': 'True',
57 'default_register': 'hg.register.auto_activate',
58 'default_register_message': '',
59 'default_password_reset': pwd_reset_setting,
60 'default_extern_activate': 'hg.extern_activate.auto',
61 }
62 resp = self.app.post(url('admin_permissions_application'), params=params)
63 self.logout_user()
64
65 login_page = self.app.get(login_url)
66 asr_login = AssertResponse(login_page)
67 index_page = self.app.get(index_url)
68 asr_index = AssertResponse(index_page)
69
70 if show_link:
71 asr_login.one_element_exists('a.pwd_reset')
72 asr_index.one_element_exists('a.pwd_reset')
73 else:
74 asr_login.no_element_exists('a.pwd_reset')
75 asr_index.no_element_exists('a.pwd_reset')
76
77 response = self.app.get(pwd_reset_url)
78
79 assert_response = AssertResponse(response)
80 if show_reset:
81 response.mustcontain('Send password reset email')
82 assert_response.one_element_exists('#email')
83 assert_response.one_element_exists('#send')
84 else:
85 response.mustcontain('Password reset is disabled.')
86 assert_response.no_element_exists('#email')
87 assert_response.no_element_exists('#send')
88
89 def test_password_form_disabled(self):
90 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
91 params = {
92 'csrf_token': self.csrf_token,
93 'anonymous': 'True',
94 'default_register': 'hg.register.auto_activate',
95 'default_register_message': '',
96 'default_password_reset': 'hg.password_reset.disabled',
97 'default_extern_activate': 'hg.extern_activate.auto',
98 }
99 self.app.post(url('admin_permissions_application'), params=params)
100 self.logout_user()
101
102 response = self.app.post(
103 pwd_reset_url, {'email': 'lisa@rhodecode.com',}
104 )
105 response = response.follow()
106 response.mustcontain('Password reset is disabled.')
@@ -18,6 +18,7 b''
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 collections
22 import collections
22 import datetime
23 import datetime
23 import formencode
24 import formencode
@@ -37,9 +38,10 b' from rhodecode.lib.auth import ('
37 from rhodecode.lib.base import get_ip_addr
38 from rhodecode.lib.base import get_ip_addr
38 from rhodecode.lib.exceptions import UserCreationError
39 from rhodecode.lib.exceptions import UserCreationError
39 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.model.db import User
41 from rhodecode.model.db import User, UserApiKeys
41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.user import UserModel
46 from rhodecode.model.user import UserModel
45 from rhodecode.translation import _
47 from rhodecode.translation import _
@@ -289,17 +291,24 b' class LoginView(object):'
289 'errors': {},
291 'errors': {},
290 }
292 }
291
293
294 # always send implicit message to prevent from discovery of
295 # matching emails
296 msg = _('If such email exists, a password reset link was sent to it.')
297
292 if self.request.POST:
298 if self.request.POST:
299 if h.HasPermissionAny('hg.password_reset.disabled')():
300 _email = self.request.POST.get('email', '')
301 log.error('Failed attempt to reset password for `%s`.', _email)
302 self.session.flash(_('Password reset has been disabled.'),
303 queue='error')
304 return HTTPFound(self.request.route_path('reset_password'))
305
293 password_reset_form = PasswordResetForm()()
306 password_reset_form = PasswordResetForm()()
294 try:
307 try:
295 form_result = password_reset_form.to_python(
308 form_result = password_reset_form.to_python(
296 self.request.params)
309 self.request.params)
297 if h.HasPermissionAny('hg.password_reset.disabled')():
310 user_email = form_result['email']
298 log.error('Failed attempt to reset password for %s.', form_result['email'] )
311
299 self.session.flash(
300 _('Password reset has been disabled.'),
301 queue='error')
302 return HTTPFound(self.request.route_path('reset_password'))
303 if captcha.active:
312 if captcha.active:
304 response = submit(
313 response = submit(
305 self.request.params.get('recaptcha_challenge_field'),
314 self.request.params.get('recaptcha_challenge_field'),
@@ -310,43 +319,66 b' class LoginView(object):'
310 _value = form_result
319 _value = form_result
311 _msg = _('Bad captcha')
320 _msg = _('Bad captcha')
312 error_dict = {'recaptcha_field': _msg}
321 error_dict = {'recaptcha_field': _msg}
313 raise formencode.Invalid(_msg, _value, None,
322 raise formencode.Invalid(
314 error_dict=error_dict)
323 _msg, _value, None, error_dict=error_dict)
324 # Generate reset URL and send mail.
325 user = User.get_by_email(user_email)
315
326
316 # Generate reset URL and send mail.
327 # generate password reset token that expires in 10minutes
317 user_email = form_result['email']
328 desc = 'Generated token for password reset from {}'.format(
318 user = User.get_by_email(user_email)
329 datetime.datetime.now().isoformat())
330 reset_token = AuthTokenModel().create(
331 user, lifetime=10,
332 description=desc,
333 role=UserApiKeys.ROLE_PASSWORD_RESET)
334 Session().commit()
335
336 log.debug('Successfully created password recovery token')
319 password_reset_url = self.request.route_url(
337 password_reset_url = self.request.route_url(
320 'reset_password_confirmation',
338 'reset_password_confirmation',
321 _query={'key': user.api_key})
339 _query={'key': reset_token.api_key})
322 UserModel().reset_password_link(
340 UserModel().reset_password_link(
323 form_result, password_reset_url)
341 form_result, password_reset_url)
324
325 # Display success message and redirect.
342 # Display success message and redirect.
326 self.session.flash(
343 self.session.flash(msg, queue='success')
327 _('Your password reset link was sent'),
344 return HTTPFound(self.request.route_path('reset_password'))
328 queue='success')
329 return HTTPFound(self.request.route_path('login'))
330
345
331 except formencode.Invalid as errors:
346 except formencode.Invalid as errors:
332 render_ctx.update({
347 render_ctx.update({
333 'defaults': errors.value,
348 'defaults': errors.value,
334 'errors': errors.error_dict,
335 })
349 })
350 log.debug('faking response on invalid password reset')
351 # make this take 2s, to prevent brute forcing.
352 time.sleep(2)
353 self.session.flash(msg, queue='success')
354 return HTTPFound(self.request.route_path('reset_password'))
336
355
337 return render_ctx
356 return render_ctx
338
357
339 @view_config(route_name='reset_password_confirmation',
358 @view_config(route_name='reset_password_confirmation',
340 request_method='GET')
359 request_method='GET')
341 def password_reset_confirmation(self):
360 def password_reset_confirmation(self):
361
342 if self.request.GET and self.request.GET.get('key'):
362 if self.request.GET and self.request.GET.get('key'):
363 # make this take 2s, to prevent brute forcing.
364 time.sleep(2)
365
366 token = AuthTokenModel().get_auth_token(
367 self.request.GET.get('key'))
368
369 # verify token is the correct role
370 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
371 log.debug('Got token with role:%s expected is %s',
372 getattr(token, 'role', 'EMPTY_TOKEN'),
373 UserApiKeys.ROLE_PASSWORD_RESET)
374 self.session.flash(
375 _('Given reset token is invalid'), queue='error')
376 return HTTPFound(self.request.route_path('reset_password'))
377
343 try:
378 try:
344 user = User.get_by_auth_token(self.request.GET.get('key'))
379 owner = token.user
345 password_reset_url = self.request.route_url(
380 data = {'email': owner.email, 'token': token.api_key}
346 'reset_password_confirmation',
381 UserModel().reset_password(data)
347 _query={'key': user.api_key})
348 data = {'email': user.email}
349 UserModel().reset_password(data, password_reset_url)
350 self.session.flash(
382 self.session.flash(
351 _('Your password reset was successful, '
383 _('Your password reset was successful, '
352 'a new password has been sent to your email'),
384 'a new password has been sent to your email'),
@@ -41,7 +41,7 b' class AuthTokenModel(BaseModel):'
41 """
41 """
42 :param user: user or user_id
42 :param user: user or user_id
43 :param description: description of ApiKey
43 :param description: description of ApiKey
44 :param lifetime: expiration time in seconds
44 :param lifetime: expiration time in minutes
45 :param role: role for the apikey
45 :param role: role for the apikey
46 """
46 """
47 from rhodecode.lib.auth import generate_auth_token
47 from rhodecode.lib.auth import generate_auth_token
@@ -85,3 +85,13 b' class AuthTokenModel(BaseModel):'
85 .filter(or_(UserApiKeys.expires == -1,
85 .filter(or_(UserApiKeys.expires == -1,
86 UserApiKeys.expires >= time.time()))
86 UserApiKeys.expires >= time.time()))
87 return user_auth_tokens
87 return user_auth_tokens
88
89 def get_auth_token(self, auth_token):
90 auth_token = UserApiKeys.query().filter(
91 UserApiKeys.api_key == auth_token)
92 auth_token = auth_token \
93 .filter(or_(UserApiKeys.expires == -1,
94 UserApiKeys.expires >= time.time()))\
95 .first()
96
97 return auth_token
@@ -943,6 +943,8 b' class UserApiKeys(Base, BaseModel):'
943 ROLE_VCS = 'token_role_vcs'
943 ROLE_VCS = 'token_role_vcs'
944 ROLE_API = 'token_role_api'
944 ROLE_API = 'token_role_api'
945 ROLE_FEED = 'token_role_feed'
945 ROLE_FEED = 'token_role_feed'
946 ROLE_PASSWORD_RESET = 'token_password_reset'
947
946 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
948 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
947
949
948 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -538,7 +538,7 b' class UserModel(BaseModel):'
538
538
539 return True
539 return True
540
540
541 def reset_password(self, data, pwd_reset_url):
541 def reset_password(self, data):
542 from rhodecode.lib.celerylib import tasks, run_task
542 from rhodecode.lib.celerylib import tasks, run_task
543 from rhodecode.model.notification import EmailNotificationModel
543 from rhodecode.model.notification import EmailNotificationModel
544 from rhodecode.lib import auth
544 from rhodecode.lib import auth
@@ -554,8 +554,15 b' class UserModel(BaseModel):'
554 user.update_userdata(force_password_change=True)
554 user.update_userdata(force_password_change=True)
555
555
556 Session().add(user)
556 Session().add(user)
557
558 # now delete the token in question
559 UserApiKeys = AuthTokenModel.cls
560 UserApiKeys().query().filter(
561 UserApiKeys.api_key == data['token']).delete()
562
557 Session().commit()
563 Session().commit()
558 log.info('successfully reset password for `%s`', user_email)
564 log.info('successfully reset password for `%s`', user_email)
565
559 if new_passwd is None:
566 if new_passwd is None:
560 raise Exception('unable to generate new password')
567 raise Exception('unable to generate new password')
561
568
@@ -563,7 +570,6 b' class UserModel(BaseModel):'
563
570
564 email_kwargs = {
571 email_kwargs = {
565 'new_password': new_passwd,
572 'new_password': new_passwd,
566 'password_reset_url': pwd_reset_url,
567 'user': user,
573 'user': user,
568 'email': user_email,
574 'email': user_email,
569 'date': datetime.datetime.now()
575 'date': datetime.datetime.now()
@@ -571,7 +577,8 b' class UserModel(BaseModel):'
571
577
572 (subject, headers, email_body,
578 (subject, headers, email_body,
573 email_body_plaintext) = EmailNotificationModel().render_email(
579 email_body_plaintext) = EmailNotificationModel().render_email(
574 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
580 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
581 **email_kwargs)
575
582
576 recipients = [user_email]
583 recipients = [user_email]
577
584
@@ -16,6 +16,7 b' There was a request to reset your passwo'
16 You can continue, and generate new password by clicking following URL:
16 You can continue, and generate new password by clicking following URL:
17 ${password_reset_url}
17 ${password_reset_url}
18
18
19 This link will be active for 10 minutes.
19 ${self.plaintext_footer()}
20 ${self.plaintext_footer()}
20 </%def>
21 </%def>
21
22
@@ -28,4 +29,5 b' There was a request to reset your passwo'
28 <strong>If you did not request a password reset, please contact your RhodeCode administrator.</strong>
29 <strong>If you did not request a password reset, please contact your RhodeCode administrator.</strong>
29 </p><p>
30 </p><p>
30 <a href="${password_reset_url}">${_('Generate new password here')}.</a>
31 <a href="${password_reset_url}">${_('Generate new password here')}.</a>
32 This link will be active for 10 minutes.
31 </p>
33 </p>
@@ -9,12 +9,11 b' Your new RhodeCode password'
9 <%def name="body_plaintext()" filter="n,trim">
9 <%def name="body_plaintext()" filter="n,trim">
10 Hi ${user.username},
10 Hi ${user.username},
11
11
12 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
12 Below is your new access password for RhodeCode.
13
13
14 *If you didn't do this, please contact your RhodeCode administrator.*
14 *If you didn't do this, please contact your RhodeCode administrator.*
15
15
16 You can continue, and generate new password by clicking following URL:
16 password: ${new_password}
17 ${password_reset_url}
18
17
19 ${self.plaintext_footer()}
18 ${self.plaintext_footer()}
20 </%def>
19 </%def>
@@ -27,4 +26,4 b' Below is your new access password for Rh'
27 <br/>
26 <br/>
28 <strong>If you didn't request a new password, please contact your RhodeCode administrator.</strong>
27 <strong>If you didn't request a new password, please contact your RhodeCode administrator.</strong>
29 </p>
28 </p>
30 <p>password: <input value='${new_password}'/></p>
29 <p>password: <pre>${new_password}</pre>
@@ -25,15 +25,13 b' import pytest'
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 TestController, assert_session_flash, clear_all_caches, url,
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
29 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
30 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.utils import AssertResponse, get_session_from_response
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
32 from rhodecode.lib.auth import check_password, generate_auth_token
31 from rhodecode.lib.auth import check_password
33 from rhodecode.lib import helpers as h
34 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
35 from rhodecode.model import validators
33 from rhodecode.model import validators
36 from rhodecode.model.db import User, Notification
34 from rhodecode.model.db import User, Notification, UserApiKeys
37 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
38
36
39 fixture = Fixture()
37 fixture = Fixture()
@@ -49,7 +47,7 b" pwd_reset_confirm_url = ADMIN_PREFIX + '"
49
47
50
48
51 @pytest.mark.usefixtures('app')
49 @pytest.mark.usefixtures('app')
52 class TestLoginController:
50 class TestLoginController(object):
53 destroy_users = set()
51 destroy_users = set()
54
52
55 @classmethod
53 @classmethod
@@ -374,54 +372,42 b' class TestLoginController:'
374 def test_forgot_password_wrong_mail(self):
372 def test_forgot_password_wrong_mail(self):
375 bad_email = 'marcin@wrongmail.org'
373 bad_email = 'marcin@wrongmail.org'
376 response = self.app.post(
374 response = self.app.post(
377 pwd_reset_url,
375 pwd_reset_url, {'email': bad_email, }
378 {'email': bad_email, }
379 )
376 )
377 assert_session_flash(response,
378 'If such email exists, a password reset link was sent to it.')
380
379
381 msg = validators.ValidSystemEmail()._messages['non_existing_email']
380 def test_forgot_password(self, user_util):
382 msg = h.html_escape(msg % {'email': bad_email})
383 response.mustcontain()
384
385 def test_forgot_password(self):
386 response = self.app.get(pwd_reset_url)
381 response = self.app.get(pwd_reset_url)
387 assert response.status == '200 OK'
382 assert response.status == '200 OK'
388
383
389 username = 'test_password_reset_1'
384 user = user_util.create_user()
390 password = 'qweqwe'
385 user_id = user.user_id
391 email = 'marcin@python-works.com'
386 email = user.email
392 name = 'passwd'
393 lastname = 'reset'
394
387
395 new = User()
388 response = self.app.post(pwd_reset_url, {'email': email, })
396 new.username = username
397 new.password = password
398 new.email = email
399 new.name = name
400 new.lastname = lastname
401 new.api_key = generate_auth_token(username)
402 Session().add(new)
403 Session().commit()
404
389
405 response = self.app.post(pwd_reset_url,
390 assert_session_flash(response,
406 {'email': email, })
391 'If such email exists, a password reset link was sent to it.')
407
408 assert_session_flash(
409 response, 'Your password reset link was sent')
410
411 response = response.follow()
412
392
413 # BAD KEY
393 # BAD KEY
414
394 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, 'badkey')
415 key = "bad"
416 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
417 response = self.app.get(confirm_url)
395 response = self.app.get(confirm_url)
418 assert response.status == '302 Found'
396 assert response.status == '302 Found'
419 assert response.location.endswith(pwd_reset_url)
397 assert response.location.endswith(pwd_reset_url)
398 assert_session_flash(response, 'Given reset token is invalid')
399
400 response.follow() # cleanup flash
420
401
421 # GOOD KEY
402 # GOOD KEY
403 key = UserApiKeys.query()\
404 .filter(UserApiKeys.user_id == user_id)\
405 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
406 .first()
422
407
423 key = User.get_by_username(username).api_key
408 assert key
424 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
409
410 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key.api_key)
425 response = self.app.get(confirm_url)
411 response = self.app.get(confirm_url)
426 assert response.status == '302 Found'
412 assert response.status == '302 Found'
427 assert response.location.endswith(login_url)
413 assert response.location.endswith(login_url)
@@ -431,7 +417,7 b' class TestLoginController:'
431 'Your password reset was successful, '
417 'Your password reset was successful, '
432 'a new password has been sent to your email')
418 'a new password has been sent to your email')
433
419
434 response = response.follow()
420 response.follow()
435
421
436 def _get_api_whitelist(self, values=None):
422 def _get_api_whitelist(self, values=None):
437 config = {'api_access_controllers_whitelist': values or []}
423 config = {'api_access_controllers_whitelist': values or []}
@@ -522,70 +508,3 b' class TestLoginController:'
522 repo_name=HG_REPO, revision='tip',
508 repo_name=HG_REPO, revision='tip',
523 api_key=new_auth_token.api_key),
509 api_key=new_auth_token.api_key),
524 status=302)
510 status=302)
525
526
527 class TestPasswordReset(TestController):
528
529 @pytest.mark.parametrize(
530 'pwd_reset_setting, show_link, show_reset', [
531 ('hg.password_reset.enabled', True, True),
532 ('hg.password_reset.hidden', False, True),
533 ('hg.password_reset.disabled', False, False),
534 ])
535 def test_password_reset_settings(
536 self, pwd_reset_setting, show_link, show_reset):
537 clear_all_caches()
538 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
539 params = {
540 'csrf_token': self.csrf_token,
541 'anonymous': 'True',
542 'default_register': 'hg.register.auto_activate',
543 'default_register_message': '',
544 'default_password_reset': pwd_reset_setting,
545 'default_extern_activate': 'hg.extern_activate.auto',
546 }
547 resp = self.app.post(url('admin_permissions_application'), params=params)
548 self.logout_user()
549
550 login_page = self.app.get(login_url)
551 asr_login = AssertResponse(login_page)
552 index_page = self.app.get(index_url)
553 asr_index = AssertResponse(index_page)
554
555 if show_link:
556 asr_login.one_element_exists('a.pwd_reset')
557 asr_index.one_element_exists('a.pwd_reset')
558 else:
559 asr_login.no_element_exists('a.pwd_reset')
560 asr_index.no_element_exists('a.pwd_reset')
561
562 pwdreset_page = self.app.get(pwd_reset_url)
563
564 asr_reset = AssertResponse(pwdreset_page)
565 if show_reset:
566 assert 'Send password reset email' in pwdreset_page
567 asr_reset.one_element_exists('#email')
568 asr_reset.one_element_exists('#send')
569 else:
570 assert 'Password reset is disabled.' in pwdreset_page
571 asr_reset.no_element_exists('#email')
572 asr_reset.no_element_exists('#send')
573
574 def test_password_form_disabled(self):
575 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
576 params = {
577 'csrf_token': self.csrf_token,
578 'anonymous': 'True',
579 'default_register': 'hg.register.auto_activate',
580 'default_register_message': '',
581 'default_password_reset': 'hg.password_reset.disabled',
582 'default_extern_activate': 'hg.extern_activate.auto',
583 }
584 self.app.post(url('admin_permissions_application'), params=params)
585 self.logout_user()
586
587 pwdreset_page = self.app.post(
588 pwd_reset_url,
589 {'email': 'lisa@rhodecode.com',}
590 )
591 assert 'Password reset is disabled.' in pwdreset_page
General Comments 0
You need to be logged in to leave comments. Login now