source: main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py @ 7232

Last change on this file since 7232 was 7221, checked in by Henrik Bettermann, 13 years ago

Now we have a configuration object and can provide ContactAdminForm? with proper credentials for a smtp server.

Add Email address to IAccount objects so that 'From' fields in emails, sent by users, can be automatically filled.

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