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

Last change on this file since 6036 was 5909, checked in by uli, 14 years ago

Extend docs and respect possibly set disabled attribute of
accesscodes.

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