From eb32a8a47430fad1df32f04b1af8e073d4b6749a Mon Sep 17 00:00:00 2001 From: Holger Frey Date: Mon, 23 Oct 2017 12:11:15 +0200 Subject: [PATCH] added forgotten password form reset password form is missing --- .../account/forgot_password_email.jinja2 | 16 ++++ .../account/forgot_password_form.jinja2 | 23 ++++++ ordr2/views/account.py | 71 +++++++++++++++++- tests/views/account.py | 73 ++++++++++++++++++- 4 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 ordr2/templates/account/forgot_password_email.jinja2 create mode 100644 ordr2/templates/account/forgot_password_form.jinja2 diff --git a/ordr2/templates/account/forgot_password_email.jinja2 b/ordr2/templates/account/forgot_password_email.jinja2 new file mode 100644 index 0000000..82e2a59 --- /dev/null +++ b/ordr2/templates/account/forgot_password_email.jinja2 @@ -0,0 +1,16 @@ +{% extends "ordr2:templates/layout.jinja2" %} + +{% block title %} Ordr | Forgotten Password {% endblock title %} + +{% block content %} +
+
+
+ +

Password Reset Link

+

To reset your password, an email has been sent to you.

+

Please follow the link in the email to set a new password.

+ +
+
+{% endblock content %} diff --git a/ordr2/templates/account/forgot_password_form.jinja2 b/ordr2/templates/account/forgot_password_form.jinja2 new file mode 100644 index 0000000..e6e4303 --- /dev/null +++ b/ordr2/templates/account/forgot_password_form.jinja2 @@ -0,0 +1,23 @@ +{% extends "ordr2:templates/layout.jinja2" %} + +{% block title %} Ordr | Forgotten Password {% endblock title %} + +{% block content %} +
+
+ +

Forgotten password?

+
+ +
+ + +
+
+ + +
+
+
+
+{% endblock content %} diff --git a/ordr2/views/account.py b/ordr2/views/account.py index 554a91a..71f6aff 100644 --- a/ordr2/views/account.py +++ b/ordr2/views/account.py @@ -5,8 +5,9 @@ import deform from pyramid.httpexceptions import HTTPFound from pyramid.security import remember, forget from pyramid.view import view_config +from sqlalchemy import or_ -from ordr2.events import CompleteRegistration +from ordr2.events import CompleteRegistration, PasswordReset from ordr2.models.account import User, Role, TokenSubject from ordr2.schemas.account import RegistrationSchema @@ -14,6 +15,8 @@ from ordr2.schemas.account import RegistrationSchema PROPOSED_PASSWORD_LENGTH = 12 +# login and logout + @view_config( context='ordr2:resources.account.AccountResource', name='login', @@ -33,7 +36,7 @@ def login(context, request): if user.is_active and user.check_password(password): headers = remember(request, user.id) return HTTPFound( - request.resource_path(request.root, 'orders'), + request.resource_url(request.root, 'orders'), headers=headers ) return {} @@ -50,6 +53,8 @@ def logout(context, request): return HTTPFound(request.resource_url(request.root), headers=headers) +# account registration + @view_config( context='ordr2:resources.account.AccountResource', name='register', @@ -116,6 +121,7 @@ def registration_form_processing(context, request): renderer='ordr2:templates/account/registration_confirmation.jinja2' ) def registration_confirmation(context, request): + ''' email to verify registration was sent ''' return {} @@ -125,6 +131,67 @@ def registration_confirmation(context, request): renderer='ordr2:templates/account/registration_completed.jinja2' ) def registration_completed(context, request): + ''' registration was verified by mail link ''' context.model.owner.role = Role.NEW request.dbsession.delete(context.model) return {} + + +# password recovery + +@view_config( + context='ordr2:resources.account.AccountResource', + name='forgot-password', + request_method='GET', + permission='reset password', + renderer='ordr2:templates/account/forgot_password_form.jinja2' + ) +def forgot_password_form(context, request): + ''' forgot password form ''' + return {} + + +@view_config( + context='ordr2:resources.account.AccountResource', + name='forgot-password', + request_method='POST', + permission='reset password', + renderer='ordr2:templates/account/forgot_password_form.jinja2' + ) +def forgot_password_form_processing(context, request): + ''' forgot password form processing ''' + if 'cancel' in request.POST: + return HTTPFound(request.resource_url(request.root)) + + identifier = request.POST.get('username_or_email') + account = ( + request.dbsession + .query(User) + .filter(or_(User.username == identifier, User.email == identifier)) + .first() + ) + if not account: + request.session.flash( + 'warning', + 'Username or email address unknown' + ) + return {} + + # create a forgot-my-password token and send email + token = account.issue_token(request, TokenSubject.RESET_PASSWORD) + notification = PasswordReset(request, account, {'token': token}) + request.registry.notify(notification) + + return HTTPFound(request.resource_url(context, 'forgot-password-email')) + + +@view_config( + context='ordr2:resources.account.AccountResource', + name='forgot-password-email', + request_method='GET', + permission='reset password', + renderer='ordr2:templates/account/forgot_password_email.jinja2' + ) +def forgot_password_email_sent(context, request): + ''' password reset link was sent ''' + return {} diff --git a/tests/views/account.py b/tests/views/account.py index 6cd0273..17b4b6e 100644 --- a/tests/views/account.py +++ b/tests/views/account.py @@ -40,7 +40,7 @@ def test_account_login_active_users(dbsession, rolename): result = login(None, request) assert isinstance(result, HTTPFound) - assert result.location == '//orders' + assert result.location == 'http://example.com//orders' @pytest.mark.parametrize('rolename', ['unvalidated', 'new', 'inactive']) @@ -99,6 +99,8 @@ def test_logout(app_config): assert result.location == 'http://example.com//' +# tests for the registration form + def test_registration_form(app_config): ''' registration form ''' from ordr2.views.account import registration_form @@ -214,3 +216,72 @@ def test_registration_completed(dbsession): assert user.role == Role.NEW assert dbsession.query(Token).count() == 0 assert dbsession.query(User).count() == 1 + + +# tests for password reset + +def test_forgot_password_form(): + ''' display the forgot password form ''' + from ordr2.views.account import forgot_password_form + + result = forgot_password_form(None, None) + + assert result == {} + + +@pytest.mark.parametrize('who', ['TerryGilliam', 'gilliam@example.com']) +def test_forgot_password_form_processing_ok(dbsession, who): + ''' process the forgot password form ''' + from ordr2.views.account import forgot_password_form_processing + + dbsession.add(get_user('user')) + request = DummyRequest( + dbsession=dbsession, + POST={'username_or_email': who} + ) + result = forgot_password_form_processing(None, request) + + assert isinstance(result, HTTPFound) + assert result.location == 'http://example.com//forgot-password-email' + + +def test_forgot_password_form_processing_cancel(dbsession): + ''' forgot password form was canceled ''' + from ordr2.views.account import forgot_password_form_processing + + dbsession.add(get_user('user')) + request = DummyRequest( + dbsession=dbsession, + POST={'username_or_email': 'gilliam@example.com', 'cancel': 'Cancel'} + ) + result = forgot_password_form_processing(None, request) + + assert isinstance(result, HTTPFound) + assert result.location == 'http://example.com//' + + +@pytest.mark.parametrize( + 'who', + ['unknown user', 'unknown@example.com', ''] + ) +def test_forgot_password_form_processing_error(dbsession, who): + ''' process the forgot password form ''' + from ordr2.views.account import forgot_password_form_processing + + dbsession.add(get_user('user')) + request = DummyRequest( + dbsession=dbsession, + POST = {'username_or_email': who} + ) + result = forgot_password_form_processing(None, request) + + assert result == {} + + +def test_forgot_password_email_sent(): + ''' message that a password reset link was sent ''' + from ordr2.views.account import forgot_password_email_sent + + result = forgot_password_email_sent(None, None) + + assert result == {}