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

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

Adjust copyright statement and svn keyword in applicants.

  • Property svn:keywords set to Id
File size: 10.5 KB
Line 
1## $Id: authentication.py 7192 2011-11-25 07:15:50Z 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
247class ApplicantsAuthUtility(grok.GlobalUtility):
248    """A global utility that sets up any PAU passed.
249
250    The methods of this utility are called during setup of a new site
251    (`University`) instance and after the regular authentication
252    systems (regular users, officers, etc.) were set up.
253    """
254    grok.implements(IAuthPluginUtility)
255    grok.name('applicants_auth_setup')
256
257    def register(self, pau):
258        """Register our local applicants specific PAU components.
259
260        Applicants provide their own authentication system resulting
261        in a specialized credentials plugin and a specialized
262        authenticator plugin.
263
264        Here we tell a given PAU that these plugins exist and should
265        be consulted when trying to authenticate a user.
266
267        We stack our local plugins at end of the plugin list, so that
268        other authentication mechanisms (the normal user
269        authentication for instance) have precedence and to avoid
270        "account-shadowing".
271        """
272        # The local credentials plugin is registered under the name
273        # 'applicant_credentials' (see above).
274        plugins = list(pau.credentialsPlugins) + ['applicant_credentials']
275        pau.credentialsPlugins = tuple(plugins)
276        # The local authenticator plugin is registered under the name
277        # 'applicants' (subject to change?)
278        plugins = list(pau.authenticatorPlugins) + ['applicants']
279        pau.authenticatorPlugins = tuple(plugins)
280        return pau
281
282    def unregister(self, pau):
283        """Unregister applicant specific authentication components from PAU.
284        """
285        pau.credentialsPlugins = tuple(
286            [x for x in list(pau.credentialsPlugins)
287             if x != 'applicant_credentials'])
288        pau.authenticatorPlugins = tuple(
289            [x for x in list(pau.authenticatorPlugins)
290             if x != 'applicants'])
291        return pau
292
293
294def principal_id(access_code):
295    """Get a principal ID for applicants.
296
297    We need unique principal ids for appliants. As access codes must
298    be unique we simply return them.
299    """
300    return access_code
Note: See TracBrowser for help on using the repository browser.