diff -r 000000000000 -r f04e1d0a0723 src/pyams_security/views/login.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_security/views/login.py Thu Feb 19 10:53:29 2015 +0100 @@ -0,0 +1,201 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_security.interfaces import ILoginView, ISecurityManager, LOGIN_REFERER_KEY +from pyams_skin.interfaces import IModalFullPage +from pyams_skin.layer import IPyAMSLayer +from z3c.form.interfaces import IDataExtractedEvent + +# import packages +from pyams_form.form import AddForm, AJAXAddForm, DialogAddForm +from pyams_form.schema import ResetButton, CloseButton +from pyams_pagelet.pagelet import pagelet_config +from pyams_security.credential import Credentials +from pyams_utils.registry import query_utility +from pyramid.events import subscriber +from pyramid.httpexceptions import HTTPFound +from pyramid.response import Response +from pyramid.security import remember, forget +from pyramid.view import view_config, forbidden_view_config +from z3c.form import field, button +from zope.interface import implementer, Interface, Invalid +from zope.schema import TextLine, Password + +from pyams_security import _ + + +# +# Login views +# + +@forbidden_view_config(request_type=IPyAMSLayer) +def ForbiddenView(request): + """Default forbidden view""" + request.session[LOGIN_REFERER_KEY] = request.view_name + return HTTPFound('login.html') + + +class ILoginFormFields(Interface): + """Login form fields""" + + login = TextLine(title=_("Login")) + password = Password(title=_("Password")) + + +class ILoginFormButtons(Interface): + """Login form buttons""" + + reset = ResetButton(name='reset', title=_("Reset")) + login = button.Button(name='login', title=_("Connect")) + + +@subscriber(IDataExtractedEvent, form_selector=ILoginView) +def handle_login_form_data(event): + """Check credentials after data extraction""" + data = event.data + if 'principal_id' in data: + del data['principal_id'] + manager = query_utility(ISecurityManager) + if manager is not None: + credentials = Credentials('form', id=data['login'], **data) + principal_id = manager.authenticate(credentials, event.form.request) + if principal_id is None: + event.form.widgets.errors += (Invalid(_("Invalid credentials!")),) + else: + data['principal_id'] = principal_id + else: + event.form.widgets.errors += (Invalid(_("Missing security manager utility. Please contact administrator!")),) + + +@pagelet_config(name='login.html', layer=IPyAMSLayer) +@implementer(IModalFullPage, ILoginView) +class LoginForm(AddForm): + """Login form""" + + legend = _("Please enter valid credentials to log-in") + fields = field.Fields(ILoginFormFields) + buttons = button.Buttons(ILoginFormButtons) + ajax_handler = 'login.json' + edit_permission = None + + def updateActions(self): + super(LoginForm, self).updateActions() + if 'login' in self.actions: + self.actions['login'].addClass('btn-primary') + + def createAndAdd(self, data): + principal_id = data.get('principal_id') + if principal_id is not None: + headers = remember(self.request, principal_id) + response = self.request.response + response.headerlist.extend(headers) + if not self.request.is_xhr: + response.status_code = 302 + session = self.request.session + if LOGIN_REFERER_KEY in session: + response.location = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + else: + response.location = '/' + + +@view_config(name='login.json', request_type=IPyAMSLayer, renderer='json', xhr=True) +class LoginAJAXForm(AJAXAddForm, LoginForm): + """Login form, AJAX view""" + + def get_ajax_output(self, changes): + status = {'status': 'redirect'} + session = self.request.session + if LOGIN_REFERER_KEY in session: + status['location'] = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + return status + + +@forbidden_view_config(request_type=IPyAMSLayer, renderer='json', xhr=True) +def ForbiddenAJAXView(request): + """AJAX call forbidden view""" + return {'status': 'modal', + 'location': 'login-dialog.html'} + + +class ILoginDialogFormButtons(Interface): + """Login dialog form buttons""" + + close = CloseButton(name='close', title=_("Cancel")) + login = button.Button(name='login', title=_("Connect")) + + +@pagelet_config(name='login-dialog.html', layer=IPyAMSLayer) +@implementer(ILoginView) +class LoginDialogForm(DialogAddForm): + """Login dialog form""" + + title = _("Please enter valid credentials to log-in") + legend = None + fields = field.Fields(ILoginFormFields) + buttons = button.Buttons(ILoginDialogFormButtons) + ajax_handler = 'login-dialog.json' + edit_permission = None + + def update(self): + super(LoginDialogForm, self).update() + self.request.session[LOGIN_REFERER_KEY] = self.request.referer + + def updateActions(self): + super(LoginDialogForm, self).updateActions() + if 'login' in self.actions: + self.actions['login'].addClass('btn-primary') + + def createAndAdd(self, data): + credentials = Credentials('form', id=data['login'], **data) + manager = query_utility(ISecurityManager) + if manager is not None: + principal_id = manager.authenticate(credentials, self.request) + if principal_id is not None: + headers = remember(self.request, principal_id) + response = self.request.response + response.headerlist.extend(headers) + + +@view_config(name='login-dialog.json', request_type=IPyAMSLayer, renderer='json', xhr=True) +class LoginDialogAJAXForm(AJAXAddForm, LoginDialogForm): + """Login dialog form, AJAX view""" + + def get_ajax_output(self, changes): + status = {'status': 'redirect'} + session = self.request.session + if LOGIN_REFERER_KEY in session: + status['location'] = session[LOGIN_REFERER_KEY] + del session[LOGIN_REFERER_KEY] + return status + + +# +# Logout view +# + +@view_config(name='logout.html', request_type=IPyAMSLayer) +def logout(request): + """Logout view""" + headers = forget(request) + response = Response() + response.headerlist.extend(headers) + response.status_code = 302 + response.location = request.referer or '/' + return response