source: main/waeup.kofa/trunk/src/waeup/kofa/students/authentication.py @ 15344

Last change on this file since 15344 was 13394, checked in by Henrik Bettermann, 9 years ago

Implement portal maintenance mode.

  • Property svn:keywords set to Id
File size: 10.9 KB
RevLine 
[6669]1## $Id: authentication.py 13394 2015-11-06 05:43:37Z henrik $
2##
[7190]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[6669]4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Authenticate students.
20"""
21import grok
[10055]22import time
[6669]23from zope.component import getUtility
24from zope.password.interfaces import IPasswordManager
[6756]25from zope.pluggableauth.interfaces import (
26    IAuthenticatorPlugin, ICredentialsPlugin)
27from zope.pluggableauth.plugins.session import (
28    SessionCredentialsPlugin, SessionCredentials)
29from zope.publisher.interfaces.http import IHTTPRequest
30from zope.session.interfaces import ISession
[7811]31from waeup.kofa.authentication import (
[10055]32    KofaPrincipalInfo, get_principal_role_manager, FailedLoginInfo)
[7811]33from waeup.kofa.interfaces import (
[7147]34    IAuthPluginUtility, IUserAccount, IPasswordValidator)
[7811]35from waeup.kofa.students.interfaces import IStudent
[6669]36
37class StudentAccount(grok.Adapter):
38    """An adapter to turn student objects into accounts on-the-fly.
39    """
40    grok.context(IStudent)
41    grok.implements(IUserAccount)
42
[8756]43    public_name = None
44
[6669]45    @property
46    def name(self):
47        return self.context.student_id
48
49    @property
50    def password(self):
51        return getattr(self.context, 'password', None)
52
53    @property
54    def title(self):
[7364]55        return self.context.display_fullname
[6669]56
57    @property
[7221]58    def email(self):
59        return self.context.email
60
61    @property
[7233]62    def phone(self):
63        return self.context.phone
64
65    @property
[7240]66    def user_type(self):
67        return u'student'
68
69    @property
[6669]70    def description(self):
71        return self.title
72
[12926]73    def suspended(self):
74        return self.context.suspended
75
[10055]76    @property
77    def failed_logins(self):
78        if not hasattr(self.context, 'failed_logins'):
79            self.context.failed_logins = FailedLoginInfo()
80        return self.context.failed_logins
81
[6680]82    def _get_roles(self):
83        prm = get_principal_role_manager()
84        roles = [x[0] for x in prm.getRolesForPrincipal(self.name)
85                 if x[0].startswith('waeup.')]
86        return roles
[6669]87
[6680]88    def _set_roles(self, roles):
89        """Set roles for principal denoted by this account.
90        """
91        prm = get_principal_role_manager()
92        old_roles = self.roles
93        for role in old_roles:
94            # Remove old roles, not to be set now...
95            if role.startswith('waeup.') and role not in roles:
96                prm.unsetRoleForPrincipal(role, self.name)
97        for role in roles:
98            prm.assignRoleToPrincipal(role, self.name)
99        return
100
101    roles = property(_get_roles, _set_roles)
102
[6669]103    def setPassword(self, password):
104        """Set a password (LDAP-compatible) SSHA encoded.
105
[6680]106        We do not store passwords in plaintext. Encrypted password is
107        stored as unicode string.
[6669]108        """
109        passwordmanager = getUtility(IPasswordManager, 'SSHA')
[8350]110        self.context.password = passwordmanager.encodePassword(password)
[6669]111
112    def checkPassword(self, password):
[9334]113        """Check whether the given `password` matches the one stored by
114        students or the temporary password set by officers.
[13394]115        We additionally check if student account has been suspended
116        or if the portal is blocked.
[6669]117        """
[13394]118        try:
119            blocker = grok.getSite()['configuration'].maintmode_enabled_by
120            if blocker:
121                return False
122        except TypeError:  # in unit tests
123            pass
[6669]124        if not isinstance(password, basestring):
125            return False
[9334]126        passwordmanager = getUtility(IPasswordManager, 'SSHA')
127        temp_password = self.context.getTempPassword()
128        if temp_password:
129            return passwordmanager.checkPassword(temp_password, password)
[7139]130        if not getattr(self.context, 'password', None):
131            # unset/empty passwords do never match
132            return False
[8983]133        if self.context.suspended == True:
134            return False
[9334]135        return passwordmanager.checkPassword(self.context.password, password)
[6669]136
137class StudentsAuthenticatorPlugin(grok.GlobalUtility):
138    grok.implements(IAuthenticatorPlugin)
139    grok.provides(IAuthenticatorPlugin)
140    grok.name('students')
141
142    def authenticateCredentials(self, credentials):
143        """Authenticate `credentials`.
144
145        `credentials` is a tuple (login, password).
146
147        We look up students to find out whether a respective student
148        exists, then check the password and return the resulting
149        `PrincipalInfo` or ``None`` if no such student can be found.
150        """
151        if not isinstance(credentials, dict):
152            return None
153        if not ('login' in credentials and 'password' in credentials):
154            return None
155        account = self.getAccount(credentials['login'])
156        if account is None:
157            return None
158        if not account.checkPassword(credentials['password']):
159            return None
[7819]160        return KofaPrincipalInfo(id=account.name,
[6669]161                             title=account.title,
[7233]162                             description=account.description,
163                             email=account.email,
[7240]164                             phone=account.phone,
[8757]165                             public_name=account.public_name,
[7240]166                             user_type=account.user_type)
[6669]167
168    def principalInfo(self, id):
169        """Get a principal identified by `id`.
170
[7233]171        This one is required by IAuthenticatorPlugin but not needed here
172        (see respective docstring in applicants package).
[6669]173        """
[7233]174        return None
[6669]175
176    def getAccount(self, login):
177        """Look up a student identified by `login`. Returns an account.
178
179        Currently, we simply look up the key under which the student
180        is stored in the portal. That means we hit if login name and
181        name under which the student is stored match.
182
183        Returns not a student but an account object adapted from any
184        student found.
185
186        If no such student exists, ``None`` is returned.
187        """
188        site = grok.getSite()
189        if site is None:
190            return None
191        studentscontainer = site.get('students', None)
192        if studentscontainer is None:
193            return None
194        student = studentscontainer.get(login, None)
195        if student is None:
196            return None
197        return IUserAccount(student)
198
[6756]199class PasswordChangeCredentialsPlugin(grok.GlobalUtility,
200                                      SessionCredentialsPlugin):
201    """A session credentials plugin that handles the case of a user
[7592]202    changing his/her own password.
[6756]203
204    When users change their own password they might find themselves
205    logged out on next request.
206
207    To avoid this, we support to use a 'change password' page a bit
208    like a regular login page. That means, on each request we lookup
209    the sent data for a login field called 'student_id' and a
210    password.
211
212    If both exist, this means someone sent new credentials.
213
214    We then look for the old credentials stored in the user session.
215    If the new credentials' login (the student_id) matches the old
216    one's, we set the new credentials in session _but_ we return the
217    old credentials for the authentication plugins to check as for the
218    current request (and for the last time) the old credentials apply.
219
220    No valid credentials are returned by this plugin if one of the
221    follwing circumstances is true
222
223    - the sent request is not a regular IHTTPRequest
224
225    - the credentials to set do not match the old ones
226
227    - no credentials are sent with the request
228
229    - no credentials were set before (i.e. the user has no session
230      with credentials set before)
231
[6768]232    - no session exists already
233
234    - password and repeated password do not match
235
[6756]236    Therefore it is mandatory to put this plugin in the line of all
237    credentials plugins _before_ other plugins, so that the regular
238    credentials plugins can drop in as a 'fallback'.
239
240    This plugin was designed for students to change their passwords,
241    but might be used to allow password resets for other types of
242    accounts as well.
243    """
244    grok.provides(ICredentialsPlugin)
245    grok.name('student_pw_change')
246
247    loginpagename = 'login'
248    loginfield = 'student_id'
[7144]249    passwordfield = 'change_password'
250    repeatfield = 'change_password_repeat'
[6756]251
252    def extractCredentials(self, request):
253        if not IHTTPRequest.providedBy(request):
254            return None
255        login = request.get(self.loginfield, None)
256        password = request.get(self.passwordfield, None)
[6768]257        password_repeat = request.get(self.repeatfield, None)
258
[6756]259        if not login or not password:
260            return None
[6768]261
[7147]262        validator = getUtility(IPasswordValidator)
263        errors = validator.validate_password(password, password_repeat)
264        if errors:
[6768]265            return None
266
[6756]267        session = ISession(request)
268        sessionData = session.get(
269            'zope.pluggableauth.browserplugins')
[6768]270        if not sessionData:
271            return None
272
[6756]273        old_credentials = sessionData.get('credentials', None)
274        if old_credentials is None:
275            # Password changes for already authenticated users only!
276            return None
277        if old_credentials.getLogin() != login:
278            # Special treatment only for users that change their own pw.
279            return None
280        old_credentials = {
281            'login': old_credentials.getLogin(),
282            'password': old_credentials.getPassword()}
283
284        # Set new credentials in session. These will be active on next request
285        new_credentials = SessionCredentials(login, password)
286        sessionData['credentials'] = new_credentials
287
288        # Return old credentials for this one request only
289        return old_credentials
290
[6669]291class StudentsAuthenticatorSetup(grok.GlobalUtility):
292    """Register or unregister student authentication for a PAU.
293
294    This piece is called when a new site is created.
295    """
296    grok.implements(IAuthPluginUtility)
[7240]297    grok.name('students_auth_setup')
[6669]298
299    def register(self, pau):
[6756]300        plugins = list(pau.credentialsPlugins)
301        # this plugin must come before the regular credentials plugins
302        plugins.insert(0, 'student_pw_change')
303        pau.credentialsPlugins = tuple(plugins)
[6669]304        plugins = list(pau.authenticatorPlugins)
305        plugins.append('students')
306        pau.authenticatorPlugins = tuple(plugins)
307        return pau
308
309    def unregister(self, pau):
310        plugins = [x for x in pau.authenticatorPlugins
311                   if x != 'students']
312        pau.authenticatorPlugins = tuple(plugins)
313        return pau
Note: See TracBrowser for help on using the repository browser.