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

Last change on this file since 6178 was 6178, checked in by uli, 13 years ago

* empty log message *

File size: 10.1 KB
RevLine 
[5431]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 (
[5818]30    ICredentialsPlugin, IAuthenticatorPlugin,
[5431]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
[5818]36from waeup.sirp.accesscodes import get_access_code
[5762]37from waeup.sirp.applicants.interfaces import (
[5440]38    IApplicantPrincipalInfo, IApplicantPrincipal, IApplicantSessionCredentials,
[5907]39    )
[5818]40from waeup.sirp.applicants import get_applicant_data
[5902]41from waeup.sirp.interfaces import IAuthPluginUtility
[5431]42
[5818]43
[5431]44class ApplicantPrincipalInfo(object):
[5441]45    """Infos about an applicant principal.
46    """
[5431]47    grok.implements(IApplicantPrincipalInfo)
48
[5818]49    def __init__(self, access_code):
50        self.id = principal_id(access_code)
[5431]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):
[5441]58    """An applicant principal.
[5431]59
[5441]60    Applicant principals provide an extra `access_code` and `reg_no`
61    attribute extending ordinary principals.
62    """
63
[5431]64    grok.implements(IApplicantPrincipal)
65
[5818]66    def __init__(self, access_code, prefix=None):
67        self.id = principal_id(access_code)
[5464]68        if prefix is not None:
69            self.id = '%s.%s' % (prefix, self.id)
[5431]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.
[5441]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.
[5431]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(
[5464]95            self.info.access_code,
96            authentication.prefix,
[5431]97            )
98        notify(
99            AuthenticatedPrincipalCreated(
100                authentication, principal, self.info, self.request))
101        return principal
102
[5440]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
[5444]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
[6122]130
[5431]131class WAeUPApplicantCredentialsPlugin(grok.GlobalUtility,
132                                      SessionCredentialsPlugin):
[5440]133    """A credentials plugin that scans requests for applicant credentials.
134    """
[5431]135    grok.provides(ICredentialsPlugin)
136    grok.name('applicant_credentials')
137
138    loginpagename = 'login'
[6110]139    accesscode_prefix_field = 'form.ac_prefix'
[5444]140    accesscode_series_field = 'form.ac_series'
141    accesscode_number_field = 'form.ac_number'
[5431]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')
[5444]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
[5431]158        credentials = None
159
[5818]160        if access_code:
[5440]161            credentials = ApplicantCredentials(access_code)
[5431]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
[5497]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
[5818]177        return {'accesscode': credentials.getAccessCode()}
[5431]178
[5452]179
[5818]180
[5431]181class ApplicantsAuthenticatorPlugin(grok.GlobalUtility):
182    """Authenticate applicants.
183    """
184    grok.provides(IAuthenticatorPlugin)
185    grok.name('applicants')
186
187    def authenticateCredentials(self, credentials):
[5909]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
[6122]207
[5909]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.
[6122]218
[5909]219        """
[5431]220        if not isinstance(credentials, dict):
221            return None
[5460]222        accesscode = credentials.get('accesscode', None)
223        if accesscode is None:
[5431]224            return None
[5818]225        applicant_data = get_applicant_data(accesscode)
226        ac = get_access_code(accesscode) # Get the real access code object
[5460]227        appl_ac = getattr(applicant_data, 'access_code', None)
[5446]228        if ac is None:
229            return None
[5909]230        if ac.disabled is not False:
231            return None
[5460]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:
[6178]235            # This should never happen. It means a catalog error.
[5460]236            return None
[5818]237        return ApplicantPrincipalInfo(accesscode)
[5431]238
239    def principalInfo(self, id):
240        """Returns an IPrincipalInfo object for the specified principal id.
241
[5441]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.
[5431]248        """
249        return None
[5435]250
[5902]251class ApplicantsAuthUtility(grok.GlobalUtility):
252    """A global utility that sets up any PAU passed.
[5904]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.
[5902]257    """
258    grok.provides(IAuthPluginUtility)
259    grok.name('applicants_auth_setup')
260
261    def register(self, pau):
[5903]262        """Register our local applicants specific PAU components.
[5902]263
[5904]264        Applicants provide their own authentication system resulting
[5903]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
[5904]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".
[5903]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
[5902]284    def unregister(self, pau):
[5903]285        return pau
[5904]286
287
[5818]288def principal_id(access_code):
[5435]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.