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

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

Add HB to copyright line.

File size: 10.1 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 & Henrik Bettermann
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.state == 'disabled':
231            return None
232        if ac.state == 'used' and appl_ac != ac.representation:
233            return None
234        if appl_ac is not None and appl_ac != ac.representation:
235            # This should never happen. It means a catalog error.
236            return None
237        return ApplicantPrincipalInfo(accesscode)
238
239    def principalInfo(self, id):
240        """Returns an IPrincipalInfo object for the specified principal id.
241
242        This method is used by the stadard PAU to lookup for instance
243        groups. If a principal belongs to a group, the group is looked
244        up by the id.  Currently we always return ``None``,
245        indicating, that the principal could not be found. This also
246        means, that is has no effect if applicant users belong to a
247        certain group. They can not gain extra-permissions this way.
248        """
249        return None
250
251class ApplicantsAuthUtility(grok.GlobalUtility):
252    """A global utility that sets up any PAU passed.
253
254    The methods of this utility are called during setup of a new site
255    (`University`) instance and after the regular authentication
256    systems (regular users, officers, etc.) were set up.
257    """
258    grok.provides(IAuthPluginUtility)
259    grok.name('applicants_auth_setup')
260
261    def register(self, pau):
262        """Register our local applicants specific PAU components.
263
264        Applicants provide their own authentication system resulting
265        in a specialized credentials plugin and a specialized
266        authenticator plugin.
267
268        Here we tell a given PAU that these plugins exist and should
269        be consulted when trying to authenticate a user.
270
271        We stack our local plugins at end of the plugin list, so that
272        other authentication mechanisms (the normal user
273        authentication for instance) have precedence and to avoid
274        "account-shadowing".
275        """
276        # The local credentials plugin is registered under the name
277        # 'applicant_credentials' (see above).
278        pau.credentialsPlugins.append('applicant_credentials')
279        # The local authenticator plugin is registered under the name
280        # 'applicants' (subject to change?)
281        pau.authenticatorPlugins.append('applicants')
282        return pau
283
284    def unregister(self, pau):
285        return pau
286
287
288def principal_id(access_code):
289    """Get a principal ID for applicants.
290
291    We need unique principal ids for appliants. As access codes must
292    be unique we simply return them.
293    """
294    return access_code
Note: See TracBrowser for help on using the repository browser.