|
1 # |
|
2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net> |
|
3 # All Rights Reserved. |
|
4 # |
|
5 # This software is subject to the provisions of the Zope Public License, |
|
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. |
|
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED |
|
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
|
10 # FOR A PARTICULAR PURPOSE. |
|
11 # |
|
12 |
|
13 __docformat__ = 'restructuredtext' |
|
14 |
|
15 |
|
16 # import standard library |
|
17 |
|
18 # import interfaces |
|
19 from pyams_security.interfaces import ILoginView, ISecurityManager, LOGIN_REFERER_KEY |
|
20 from pyams_skin.interfaces import IModalFullPage |
|
21 from pyams_skin.layer import IPyAMSLayer |
|
22 from z3c.form.interfaces import IDataExtractedEvent |
|
23 |
|
24 # import packages |
|
25 from pyams_form.form import AddForm, AJAXAddForm, DialogAddForm |
|
26 from pyams_form.schema import ResetButton, CloseButton |
|
27 from pyams_pagelet.pagelet import pagelet_config |
|
28 from pyams_security.credential import Credentials |
|
29 from pyams_utils.registry import query_utility |
|
30 from pyramid.events import subscriber |
|
31 from pyramid.httpexceptions import HTTPFound |
|
32 from pyramid.response import Response |
|
33 from pyramid.security import remember, forget |
|
34 from pyramid.view import view_config, forbidden_view_config |
|
35 from z3c.form import field, button |
|
36 from zope.interface import implementer, Interface, Invalid |
|
37 from zope.schema import TextLine, Password |
|
38 |
|
39 from pyams_security import _ |
|
40 |
|
41 |
|
42 # |
|
43 # Login views |
|
44 # |
|
45 |
|
46 @forbidden_view_config(request_type=IPyAMSLayer) |
|
47 def ForbiddenView(request): |
|
48 """Default forbidden view""" |
|
49 request.session[LOGIN_REFERER_KEY] = request.view_name |
|
50 return HTTPFound('login.html') |
|
51 |
|
52 |
|
53 class ILoginFormFields(Interface): |
|
54 """Login form fields""" |
|
55 |
|
56 login = TextLine(title=_("Login")) |
|
57 password = Password(title=_("Password")) |
|
58 |
|
59 |
|
60 class ILoginFormButtons(Interface): |
|
61 """Login form buttons""" |
|
62 |
|
63 reset = ResetButton(name='reset', title=_("Reset")) |
|
64 login = button.Button(name='login', title=_("Connect")) |
|
65 |
|
66 |
|
67 @subscriber(IDataExtractedEvent, form_selector=ILoginView) |
|
68 def handle_login_form_data(event): |
|
69 """Check credentials after data extraction""" |
|
70 data = event.data |
|
71 if 'principal_id' in data: |
|
72 del data['principal_id'] |
|
73 manager = query_utility(ISecurityManager) |
|
74 if manager is not None: |
|
75 credentials = Credentials('form', id=data['login'], **data) |
|
76 principal_id = manager.authenticate(credentials, event.form.request) |
|
77 if principal_id is None: |
|
78 event.form.widgets.errors += (Invalid(_("Invalid credentials!")),) |
|
79 else: |
|
80 data['principal_id'] = principal_id |
|
81 else: |
|
82 event.form.widgets.errors += (Invalid(_("Missing security manager utility. Please contact administrator!")),) |
|
83 |
|
84 |
|
85 @pagelet_config(name='login.html', layer=IPyAMSLayer) |
|
86 @implementer(IModalFullPage, ILoginView) |
|
87 class LoginForm(AddForm): |
|
88 """Login form""" |
|
89 |
|
90 legend = _("Please enter valid credentials to log-in") |
|
91 fields = field.Fields(ILoginFormFields) |
|
92 buttons = button.Buttons(ILoginFormButtons) |
|
93 ajax_handler = 'login.json' |
|
94 edit_permission = None |
|
95 |
|
96 def updateActions(self): |
|
97 super(LoginForm, self).updateActions() |
|
98 if 'login' in self.actions: |
|
99 self.actions['login'].addClass('btn-primary') |
|
100 |
|
101 def createAndAdd(self, data): |
|
102 principal_id = data.get('principal_id') |
|
103 if principal_id is not None: |
|
104 headers = remember(self.request, principal_id) |
|
105 response = self.request.response |
|
106 response.headerlist.extend(headers) |
|
107 if not self.request.is_xhr: |
|
108 response.status_code = 302 |
|
109 session = self.request.session |
|
110 if LOGIN_REFERER_KEY in session: |
|
111 response.location = session[LOGIN_REFERER_KEY] |
|
112 del session[LOGIN_REFERER_KEY] |
|
113 else: |
|
114 response.location = '/' |
|
115 |
|
116 |
|
117 @view_config(name='login.json', request_type=IPyAMSLayer, renderer='json', xhr=True) |
|
118 class LoginAJAXForm(AJAXAddForm, LoginForm): |
|
119 """Login form, AJAX view""" |
|
120 |
|
121 def get_ajax_output(self, changes): |
|
122 status = {'status': 'redirect'} |
|
123 session = self.request.session |
|
124 if LOGIN_REFERER_KEY in session: |
|
125 status['location'] = session[LOGIN_REFERER_KEY] |
|
126 del session[LOGIN_REFERER_KEY] |
|
127 return status |
|
128 |
|
129 |
|
130 @forbidden_view_config(request_type=IPyAMSLayer, renderer='json', xhr=True) |
|
131 def ForbiddenAJAXView(request): |
|
132 """AJAX call forbidden view""" |
|
133 return {'status': 'modal', |
|
134 'location': 'login-dialog.html'} |
|
135 |
|
136 |
|
137 class ILoginDialogFormButtons(Interface): |
|
138 """Login dialog form buttons""" |
|
139 |
|
140 close = CloseButton(name='close', title=_("Cancel")) |
|
141 login = button.Button(name='login', title=_("Connect")) |
|
142 |
|
143 |
|
144 @pagelet_config(name='login-dialog.html', layer=IPyAMSLayer) |
|
145 @implementer(ILoginView) |
|
146 class LoginDialogForm(DialogAddForm): |
|
147 """Login dialog form""" |
|
148 |
|
149 title = _("Please enter valid credentials to log-in") |
|
150 legend = None |
|
151 fields = field.Fields(ILoginFormFields) |
|
152 buttons = button.Buttons(ILoginDialogFormButtons) |
|
153 ajax_handler = 'login-dialog.json' |
|
154 edit_permission = None |
|
155 |
|
156 def update(self): |
|
157 super(LoginDialogForm, self).update() |
|
158 self.request.session[LOGIN_REFERER_KEY] = self.request.referer |
|
159 |
|
160 def updateActions(self): |
|
161 super(LoginDialogForm, self).updateActions() |
|
162 if 'login' in self.actions: |
|
163 self.actions['login'].addClass('btn-primary') |
|
164 |
|
165 def createAndAdd(self, data): |
|
166 credentials = Credentials('form', id=data['login'], **data) |
|
167 manager = query_utility(ISecurityManager) |
|
168 if manager is not None: |
|
169 principal_id = manager.authenticate(credentials, self.request) |
|
170 if principal_id is not None: |
|
171 headers = remember(self.request, principal_id) |
|
172 response = self.request.response |
|
173 response.headerlist.extend(headers) |
|
174 |
|
175 |
|
176 @view_config(name='login-dialog.json', request_type=IPyAMSLayer, renderer='json', xhr=True) |
|
177 class LoginDialogAJAXForm(AJAXAddForm, LoginDialogForm): |
|
178 """Login dialog form, AJAX view""" |
|
179 |
|
180 def get_ajax_output(self, changes): |
|
181 status = {'status': 'redirect'} |
|
182 session = self.request.session |
|
183 if LOGIN_REFERER_KEY in session: |
|
184 status['location'] = session[LOGIN_REFERER_KEY] |
|
185 del session[LOGIN_REFERER_KEY] |
|
186 return status |
|
187 |
|
188 |
|
189 # |
|
190 # Logout view |
|
191 # |
|
192 |
|
193 @view_config(name='logout.html', request_type=IPyAMSLayer) |
|
194 def logout(request): |
|
195 """Logout view""" |
|
196 headers = forget(request) |
|
197 response = Response() |
|
198 response.headerlist.extend(headers) |
|
199 response.status_code = 302 |
|
200 response.location = request.referer or '/' |
|
201 return response |