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

Last change on this file since 6128 was 6122, checked in by uli, 14 years ago

Remove trailing whitespaces (yep, I do them too ;).

File size: 10.0 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 ApplicantPrincipalInfo(object):
45    """Infos about an applicant principal.
46    """
47    grok.implements(IApplicantPrincipalInfo)
48
49    def __init__(self, access_code):
50        self.id = principal_id(access_code)
51        self.title = u'Applicant'
52        self.description = u'An Applicant'
53        self.credentialsPlugin = None
54        self.authenticatorPlugin = None
55        self.access_code = access_code
56
57class ApplicantPrincipal(Principal):
58    """An applicant principal.
59
60    Applicant principals provide an extra `access_code` and `reg_no`
61    attribute extending ordinary principals.
62    """
63
64    grok.implements(IApplicantPrincipal)
65
66    def __init__(self, access_code, prefix=None):
67        self.id = principal_id(access_code)
68        if prefix is not None:
69            self.id = '%s.%s' % (prefix, self.id)
70        self.title = u'Applicant'
71        self.description = u'An applicant'
72        self.groups = []
73        self.access_code = access_code
74
75    def __repr__(self):
76        return 'ApplicantPrincipal(%r)' % self.id
77
78class AuthenticatedApplicantPrincipalFactory(grok.MultiAdapter):
79    """Creates 'authenticated' applicant principals.
80
81    Adapts (principal info, request) to an ApplicantPrincipal instance.
82
83    This adapter is used by the standard PAU to transform
84    PrincipalInfos into Principal instances.
85    """
86    grok.adapts(IApplicantPrincipalInfo, IRequest)
87    grok.implements(IAuthenticatedPrincipalFactory)
88
89    def __init__(self, info, request):
90        self.info = info
91        self.request = request
92
93    def __call__(self, authentication):
94        principal = ApplicantPrincipal(
95            self.info.access_code,
96            authentication.prefix,
97            )
98        notify(
99            AuthenticatedPrincipalCreated(
100                authentication, principal, self.info, self.request))
101        return principal
102
103
104#
105# Credentials plugins and related....
106#
107
108class ApplicantCredentials(object):
109    """Credentials class for ordinary applicants.
110    """
111    grok.implements(IApplicantSessionCredentials)
112
113    def __init__(self, access_code):
114        self.access_code = access_code
115
116    def getAccessCode(self):
117        """Get the access code.
118        """
119        return self.access_code
120
121    def getLogin(self):
122        """Stay compatible with non-applicant authenticators.
123        """
124        return None
125
126    def getPassword(self):
127        """Stay compatible with non-applicant authenticators.
128        """
129        return None
130
131class WAeUPApplicantCredentialsPlugin(grok.GlobalUtility,
132                                      SessionCredentialsPlugin):
133    """A credentials plugin that scans requests for applicant credentials.
134    """
135    grok.provides(ICredentialsPlugin)
136    grok.name('applicant_credentials')
137
138    loginpagename = 'login'
139    accesscode_prefix_field = 'form.ac_prefix'
140    accesscode_series_field = 'form.ac_series'
141    accesscode_number_field = 'form.ac_number'
142
143    def extractCredentials(self, request):
144        """Extracts credentials from a session if they exist.
145        """
146        if not IHTTPRequest.providedBy(request):
147            return None
148        session = ISession(request)
149        sessionData = session.get(
150            'zope.pluggableauth.browserplugins')
151        access_code_prefix = request.get(self.accesscode_prefix_field, None)
152        access_code_series = request.get(self.accesscode_series_field, None)
153        access_code_no = request.get(self.accesscode_number_field, None)
154        access_code = '%s-%s-%s' % (
155            access_code_prefix, access_code_series, access_code_no)
156        if None in [access_code_prefix, access_code_series, access_code_no]:
157            access_code = None
158        credentials = None
159
160        if access_code:
161            credentials = ApplicantCredentials(access_code)
162        elif not sessionData:
163            return None
164        sessionData = session[
165            'zope.pluggableauth.browserplugins']
166        if credentials:
167            sessionData['credentials'] = credentials
168        else:
169            credentials = sessionData.get('credentials', None)
170        if not credentials:
171            return None
172        if not IApplicantSessionCredentials.providedBy(credentials):
173            # If credentials were stored in session from another
174            # credentials plugin then we cannot make assumptions about
175            # its structure.
176            return None
177        return {'accesscode': credentials.getAccessCode()}
178
179
180
181class ApplicantsAuthenticatorPlugin(grok.GlobalUtility):
182    """Authenticate applicants.
183    """
184    grok.provides(IAuthenticatorPlugin)
185    grok.name('applicants')
186
187    def authenticateCredentials(self, credentials):
188        """Validate the given `credentials`
189
190        Credentials for applicants have to be passed as a regular
191        dictionary with a key ``accesscode``. This access code is the
192        password and username of an applicant.
193
194        Returns a :class:`ApplicantPrincipalInfo` in case of
195        successful validation, ``None`` else.
196
197        Credentials are not valid if:
198
199        - The passed accesscode does not exist (i.e. was not generated
200          by the :mod:`waeup.sirp.accesscode` module).
201
202        or
203
204        - the accesscode was disabled
205
206        or
207
208        - the accesscode was already used and a dataset for this
209          applicant was already generated with a different accesscode
210          (currently impossible, as applicant datasets are indexed by
211          accesscode)
212
213        or
214
215        - a dataset for the applicant already exists with an
216          accesscode set and this accesscode does not match the given
217          one.
218
219        """
220        if not isinstance(credentials, dict):
221            return None
222        accesscode = credentials.get('accesscode', None)
223        if accesscode is None:
224            return None
225        applicant_data = get_applicant_data(accesscode)
226        ac = get_access_code(accesscode) # Get the real access code object
227        appl_ac = getattr(applicant_data, 'access_code', None)
228        if ac is None:
229            return None
230        if ac.disabled is not False:
231            return None
232        if ac.invalidation_date is not None and appl_ac != ac.representation:
233            return None
234        if appl_ac is not None and appl_ac != ac.representation:
235            return None
236        return ApplicantPrincipalInfo(accesscode)
237
238    def principalInfo(self, id):
239        """Returns an IPrincipalInfo object for the specified principal id.
240
241        This method is used by the stadard PAU to lookup for instance
242        groups. If a principal belongs to a group, the group is looked
243        up by the id.  Currently we always return ``None``,
244        indicating, that the principal could not be found. This also
245        means, that is has no effect if applicant users belong to a
246        certain group. They can not gain extra-permissions this way.
247        """
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.provides(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        pau.credentialsPlugins.append('applicant_credentials')
278        # The local authenticator plugin is registered under the name
279        # 'applicants' (subject to change?)
280        pau.authenticatorPlugins.append('applicants')
281        return pau
282
283    def unregister(self, pau):
284        return pau
285
286
287def principal_id(access_code):
288    """Get a principal ID for applicants.
289
290    We need unique principal ids for appliants. As access codes must
291    be unique we simply return them.
292    """
293    return access_code
Note: See TracBrowser for help on using the repository browser.