src/pyams_security/views/login.py
changeset 0 f04e1d0a0723
child 25 31ad4c01e99e
--- /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 <tflorac AT ulthar.net>
+# 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