source: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/authentication.py @ 9091

Last change on this file since 9091 was 8757, checked in by Henrik Bettermann, 12 years ago

Add public_name to IKofaPrincipalInfo and IKofaPrincipal.

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