source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/authentication.py @ 7229

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

Implement contact form to send messages to students.

helpers.py: Let get_user_account look for the right IAuthenticatorPlugin (works well with students and users but not with applicants).
applicants/authentication.py: Add getAccount method which returns None at the moment (to let the tests pass).

  • Property svn:keywords set to Id
File size: 10.5 KB
Line 
1## $Id: authentication.py 7229 2011-11-28 10:35:47Z 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"""Special authentication for applicants.
19
20   XXX: This is work in progress, experimental code! Don't do that at home!
21"""
22import grok
23from zope.event import notify
24from zope.pluggableauth.factories import Principal
25from zope.pluggableauth.interfaces import (
26    ICredentialsPlugin, IAuthenticatorPlugin,
27    IAuthenticatedPrincipalFactory, AuthenticatedPrincipalCreated)
28from zope.pluggableauth.plugins.session import SessionCredentialsPlugin
29from zope.publisher.interfaces import IRequest
30from zope.publisher.interfaces.http import IHTTPRequest
31from zope.session.interfaces import ISession
32from waeup.sirp.accesscodes import get_access_code
33from waeup.sirp.applicants.interfaces import (
34    IApplicantPrincipalInfo, IApplicantPrincipal, IApplicantSessionCredentials,
35    )
36from waeup.sirp.applicants import get_applicant_data
37from waeup.sirp.interfaces import IAuthPluginUtility
38
39
40class ApplicantPrincipalInfo(object):
41    """Infos about an applicant principal.
42    """
43    grok.implements(IApplicantPrincipalInfo)
44
45    def __init__(self, access_code):
46        self.id = principal_id(access_code)
47        self.title = u'Applicant'
48        self.description = u'An Applicant'
49        self.credentialsPlugin = None
50        self.authenticatorPlugin = None
51        self.access_code = access_code
52
53class ApplicantPrincipal(Principal):
54    """An applicant principal.
55
56    Applicant principals provide an extra `access_code` and `reg_no`
57    attribute extending ordinary principals.
58    """
59
60    grok.implements(IApplicantPrincipal)
61
62    def __init__(self, access_code, prefix=None):
63        self.id = principal_id(access_code)
64        if prefix is not None:
65            self.id = '%s.%s' % (prefix, self.id)
66        self.title = u'Applicant'
67        self.description = u'An applicant'
68        self.groups = []
69        self.access_code = access_code
70
71    def __repr__(self):
72        return 'ApplicantPrincipal(%r)' % self.id
73
74class AuthenticatedApplicantPrincipalFactory(grok.MultiAdapter):
75    """Creates 'authenticated' applicant principals.
76
77    Adapts (principal info, request) to an ApplicantPrincipal instance.
78
79    This adapter is used by the standard PAU to transform
80    PrincipalInfos into Principal instances.
81    """
82    grok.adapts(IApplicantPrincipalInfo, IRequest)
83    grok.implements(IAuthenticatedPrincipalFactory)
84
85    def __init__(self, info, request):
86        self.info = info
87        self.request = request
88
89    def __call__(self, authentication):
90        principal = ApplicantPrincipal(
91            self.info.access_code,
92            authentication.prefix,
93            )
94        notify(
95            AuthenticatedPrincipalCreated(
96                authentication, principal, self.info, self.request))
97        return principal
98
99
100#
101# Credentials plugins and related....
102#
103
104class ApplicantCredentials(object):
105    """Credentials class for ordinary applicants.
106    """
107    grok.implements(IApplicantSessionCredentials)
108
109    def __init__(self, access_code):
110        self.access_code = access_code
111
112    def getAccessCode(self):
113        """Get the access code.
114        """
115        return self.access_code
116
117    def getLogin(self):
118        """Stay compatible with non-applicant authenticators.
119        """
120        return None
121
122    def getPassword(self):
123        """Stay compatible with non-applicant authenticators.
124        """
125        return None
126
127class WAeUPApplicantCredentialsPlugin(grok.GlobalUtility,
128                                      SessionCredentialsPlugin):
129    """A credentials plugin that scans requests for applicant credentials.
130    """
131    grok.provides(ICredentialsPlugin)
132    grok.name('applicant_credentials')
133
134    loginpagename = 'login'
135    accesscode_prefix_field = 'form.ac_prefix'
136    accesscode_series_field = 'form.ac_series'
137    accesscode_number_field = 'form.ac_number'
138
139    def extractCredentials(self, request):
140        """Extracts credentials from a session if they exist.
141        """
142        if not IHTTPRequest.providedBy(request):
143            return None
144        session = ISession(request)
145        sessionData = session.get(
146            'zope.pluggableauth.browserplugins')
147        access_code_prefix = request.get(self.accesscode_prefix_field, None)
148        access_code_series = request.get(self.accesscode_series_field, None)
149        access_code_no = request.get(self.accesscode_number_field, None)
150        access_code = '%s-%s-%s' % (
151            access_code_prefix, access_code_series, access_code_no)
152        if None in [access_code_prefix, access_code_series, access_code_no]:
153            access_code = None
154        credentials = None
155
156        if access_code:
157            credentials = ApplicantCredentials(access_code)
158        elif not sessionData:
159            return None
160        sessionData = session[
161            'zope.pluggableauth.browserplugins']
162        if credentials:
163            sessionData['credentials'] = credentials
164        else:
165            credentials = sessionData.get('credentials', None)
166        if not credentials:
167            return None
168        if not IApplicantSessionCredentials.providedBy(credentials):
169            # If credentials were stored in session from another
170            # credentials plugin then we cannot make assumptions about
171            # its structure.
172            return None
173        return {'accesscode': credentials.getAccessCode()}
174
175
176
177class ApplicantsAuthenticatorPlugin(grok.GlobalUtility):
178    """Authenticate applicants.
179    """
180    grok.provides(IAuthenticatorPlugin)
181    grok.name('applicants')
182
183    def authenticateCredentials(self, credentials):
184        """Validate the given `credentials`
185
186        Credentials for applicants have to be passed as a regular
187        dictionary with a key ``accesscode``. This access code is the
188        password and username of an applicant.
189
190        Returns a :class:`ApplicantPrincipalInfo` in case of
191        successful validation, ``None`` else.
192
193        Credentials are not valid if:
194
195        - The passed accesscode does not exist (i.e. was not generated
196          by the :mod:`waeup.sirp.accesscode` module).
197
198        or
199
200        - the accesscode was disabled
201
202        or
203
204        - the accesscode was already used and a dataset for this
205          applicant was already generated with a different accesscode
206          (currently impossible, as applicant datasets are indexed by
207          accesscode)
208
209        or
210
211        - a dataset for the applicant already exists with an
212          accesscode set and this accesscode does not match the given
213          one.
214
215        """
216        if not isinstance(credentials, dict):
217            return None
218        accesscode = credentials.get('accesscode', None)
219        if accesscode is None:
220            return None
221        applicant_data = get_applicant_data(accesscode)
222        ac = get_access_code(accesscode) # Get the real access code object
223        appl_ac = getattr(applicant_data, 'access_code', None)
224        if ac is None:
225            return None
226        if ac.state == 'disabled':
227            return None
228        if ac.state == 'used' and appl_ac != ac.representation:
229            return None
230        # If the following fails we have a catalog error. Bad enough
231        # to pull emergency break.
232        assert appl_ac is None or appl_ac == ac.representation
233        return ApplicantPrincipalInfo(accesscode)
234
235    def principalInfo(self, id):
236        """Returns an IPrincipalInfo object for the specified principal id.
237
238        This method is used by the stadard PAU to lookup for instance
239        groups. If a principal belongs to a group, the group is looked
240        up by the id.  Currently we always return ``None``,
241        indicating, that the principal could not be found. This also
242        means, that is has no effect if applicant users belong to a
243        certain group. They can not gain extra-permissions this way.
244        """
245        return None
246
247    def getAccount(self, login):
248        return None
249
250class ApplicantsAuthUtility(grok.GlobalUtility):
251    """A global utility that sets up any PAU passed.
252
253    The methods of this utility are called during setup of a new site
254    (`University`) instance and after the regular authentication
255    systems (regular users, officers, etc.) were set up.
256    """
257    grok.implements(IAuthPluginUtility)
258    grok.name('applicants_auth_setup')
259
260    def register(self, pau):
261        """Register our local applicants specific PAU components.
262
263        Applicants provide their own authentication system resulting
264        in a specialized credentials plugin and a specialized
265        authenticator plugin.
266
267        Here we tell a given PAU that these plugins exist and should
268        be consulted when trying to authenticate a user.
269
270        We stack our local plugins at end of the plugin list, so that
271        other authentication mechanisms (the normal user
272        authentication for instance) have precedence and to avoid
273        "account-shadowing".
274        """
275        # The local credentials plugin is registered under the name
276        # 'applicant_credentials' (see above).
277        plugins = list(pau.credentialsPlugins) + ['applicant_credentials']
278        pau.credentialsPlugins = tuple(plugins)
279        # The local authenticator plugin is registered under the name
280        # 'applicants' (subject to change?)
281        plugins = list(pau.authenticatorPlugins) + ['applicants']
282        pau.authenticatorPlugins = tuple(plugins)
283        return pau
284
285    def unregister(self, pau):
286        """Unregister applicant specific authentication components from PAU.
287        """
288        pau.credentialsPlugins = tuple(
289            [x for x in list(pau.credentialsPlugins)
290             if x != 'applicant_credentials'])
291        pau.authenticatorPlugins = tuple(
292            [x for x in list(pau.authenticatorPlugins)
293             if x != 'applicants'])
294        return pau
295
296
297def principal_id(access_code):
298    """Get a principal ID for applicants.
299
300    We need unique principal ids for appliants. As access codes must
301    be unique we simply return them.
302    """
303    return access_code
Note: See TracBrowser for help on using the repository browser.