From 30df8627dbc6d6e25b3130cc893c13a56eaffd76 Mon Sep 17 00:00:00 2001 From: Holger Frey Date: Mon, 2 Oct 2017 13:53:38 +0200 Subject: [PATCH] passwort reset links are working --- ordr2/resources/__init__.py | 2 +- ordr2/resources/account.py | 38 ++++++++++++- ordr2/schemas/account.py | 27 ++++++--- ordr2/templates/account/password_reset.jinja2 | 24 ++++++++ ordr2/views/account.py | 55 ++++++++++++++++++- 5 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 ordr2/templates/account/password_reset.jinja2 diff --git a/ordr2/resources/__init__.py b/ordr2/resources/__init__.py index 00bf576..8a3fd06 100644 --- a/ordr2/resources/__init__.py +++ b/ordr2/resources/__init__.py @@ -1,6 +1,6 @@ from pyramid.security import Allow, Everyone -from .account import Account +from .account import Account, PasswordResetAccount from .admin import Admin, UserList, UserAccount from .base import BaseResource diff --git a/ordr2/resources/account.py b/ordr2/resources/account.py index ced05c0..718d6e4 100644 --- a/ordr2/resources/account.py +++ b/ordr2/resources/account.py @@ -1,10 +1,44 @@ -from pyramid.security import Allow, Authenticated, Deny, Everyone +from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone + +from ordr2.models import User from .base import BaseResource + +class PasswordResetAccount(BaseResource): + + def __acl__(self): + return [ + (Allow, Everyone, 'reset'), + DENY_ALL + ] + + +class PasswordReset(BaseResource): + + def __acl__(self): + return [ + (Allow, Everyone, 'reset'), + DENY_ALL + ] + + def __getitem__(self, key): + key = key.strip() + if key: + account = self.request.dbsession.\ + query(User).\ + filter_by(password_reset=key).\ + first() + if account: + return PasswordResetAccount(key, self, account) + raise KeyError + + class Account(BaseResource): + nodes = {'reset': PasswordReset} + def __init__(self, name, parent): super().__init__(name, parent) self.model = self.request.user @@ -17,4 +51,6 @@ class Account(BaseResource): (Deny, Authenticated, 'register'), (Allow, Everyone, 'register'), (Allow, Authenticated, 'settings'), + (Allow, Everyone, 'reset'), + DENY_ALL ] diff --git a/ordr2/schemas/account.py b/ordr2/schemas/account.py index 0f06534..818552a 100644 --- a/ordr2/schemas/account.py +++ b/ordr2/schemas/account.py @@ -105,15 +105,6 @@ class ChangePasswordSchema(CSRFSchema): missing='' ) - @classmethod - def as_form(cls, request, **override): - settings = { - 'buttons': ('Change Password', 'Cancel'), - 'css_class': 'form-horizontal' - } - settings.update(override) - return super().as_form(request, **settings) - class ConfirmSettingsSchema(CSRFSchema): ''' confirm changes with current password ''' @@ -148,3 +139,21 @@ class SettingsSchema(CSRFSchema): template='textinput_disabled.pt' ) return form + + +class ResetPasswordSchema(CSRFSchema): + ''' reset password of an account ''' + + new_password = colander.SchemaNode( + colander.String(), + widget=deform.widget.CheckedPasswordWidget() + ) + + @classmethod + def as_form(cls, request, **override): + settings = { + 'buttons': ('Change Password', 'Cancel'), + 'css_class': 'form-horizontal' + } + settings.update(override) + return super().as_form(request, **settings) diff --git a/ordr2/templates/account/password_reset.jinja2 b/ordr2/templates/account/password_reset.jinja2 new file mode 100644 index 0000000..694874d --- /dev/null +++ b/ordr2/templates/account/password_reset.jinja2 @@ -0,0 +1,24 @@ +{% extends "ordr2:templates/layout.jinja2" %} +{% import 'ordr2:templates/macros.jinja2' as macros with context %} + +{% block subtitle %} Account | Reset Password {% endblock subtitle %} + +{% block content %} +
+ +
+
+
+

Reset your Password

+
+
+
+
+ {{ macros.flash_messages() }} + {{form.render()|safe}} +
+
+
+ +
+{% endblock content %} diff --git a/ordr2/views/account.py b/ordr2/views/account.py index f8a284a..8a3c862 100644 --- a/ordr2/views/account.py +++ b/ordr2/views/account.py @@ -7,7 +7,11 @@ from pyramid.view import view_config from ordr2.events import UserLogIn from ordr2.models import User, Role -from ordr2.schemas.account import RegistrationSchema, SettingsSchema +from ordr2.schemas.account import ( + ResetPasswordSchema, + RegistrationSchema, + SettingsSchema + ) # user log in and log out @@ -185,6 +189,9 @@ def settings_form(context, request): def settings_form_processing(context, request): ''' display the user settings form ''' + if 'Cancel' in request.POST: + return HTTPFound(request.resource_url(request.root)) + form = SettingsSchema.as_form(request) data = request.POST.items() try: @@ -207,3 +214,49 @@ def settings_form_processing(context, request): request.flash('success', 'Your account information has been updated.') return {'form': form} + + +# passwort reset links + +@view_config( + context='ordr2:resources.PasswordResetAccount', + permission='reset', + request_method='GET', + renderer='ordr2:templates/account/password_reset.jinja2' + ) +def reset_password_form(context, request): + ''' display the password reset form ''' + form = ResetPasswordSchema.as_form(request) + return {'form': form} + + +@view_config( + context='ordr2:resources.PasswordResetAccount', + permission='reset', + request_method='POST', + renderer='ordr2:templates/account/password_reset.jinja2' + ) +def reset_password_form_processing(context, request): + ''' process the password reset form ''' + + if 'Cancel' in request.POST: + return HTTPFound(request.resource_url(request.root)) + + form = ResetPasswordSchema.as_form(request) + data = request.POST.items() + try: + appstruct = form.validate(data) + except deform.ValidationFailure as e: + return {'form': form} + + context.model.set_password(appstruct['new_password']) + context.model.password_reset = '' + + request.flash( + 'success', + 'Password reset successful', + 'Please Log In with your new password', + ) + return HTTPFound(request.resource_url(request.root, 'account', 'login')) + +