Ignore:
Timestamp:
30 Nov 2011, 23:13:26 (13 years ago)
Author:
Henrik Bettermann
Message:

Rebuild applicants package (1st part). Applicants now have an applicant_id and a password and can use the regular login page to enter the portal.

Add user_type attribute to SIRPPrincipal objects.

Add some permissions in students package.

Some tests are still missing and will be re-added soon.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/authentication.py

    r7235 r7240  
    11## $Id$
    2 ##
     2## 
    33## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
    44## This program is free software; you can redistribute it and/or modify
     
    66## the Free Software Foundation; either version 2 of the License, or
    77## (at your option) any later version.
    8 ##
     8## 
    99## This program is distributed in the hope that it will be useful,
    1010## but WITHOUT ANY WARRANTY; without even the implied warranty of
    1111## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1212## GNU General Public License for more details.
    13 ##
     13## 
    1414## You should have received a copy of the GNU General Public License
    1515## along with this program; if not, write to the Free Software
    1616## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    1717##
    18 """Special authentication for applicants.
    19 
    20    XXX: This is work in progress, experimental code! Don't do that at home!
     18"""
     19Authenticate applicants.
    2120"""
    2221import grok
    23 from zope.event import notify
    24 from zope.pluggableauth.factories import Principal
     22from zope.component import getUtility
     23from zope.password.interfaces import IPasswordManager
    2524from zope.pluggableauth.interfaces import (
    26     ICredentialsPlugin, IAuthenticatorPlugin,
    27     IAuthenticatedPrincipalFactory, AuthenticatedPrincipalCreated)
    28 from zope.pluggableauth.plugins.session import SessionCredentialsPlugin
    29 from zope.publisher.interfaces import IRequest
     25    IAuthenticatorPlugin, ICredentialsPlugin)
     26from zope.pluggableauth.plugins.session import (
     27    SessionCredentialsPlugin, SessionCredentials)
    3028from zope.publisher.interfaces.http import IHTTPRequest
    3129from zope.session.interfaces import ISession
    32 from waeup.sirp.accesscodes import get_access_code
    33 from waeup.sirp.applicants.interfaces import (
    34     IApplicantPrincipalInfo, IApplicantPrincipal, IApplicantSessionCredentials,
    35     )
    36 from waeup.sirp.applicants import get_applicant_data
    37 from waeup.sirp.interfaces import IAuthPluginUtility
     30from waeup.sirp.authentication import SIRPPrincipalInfo, get_principal_role_manager
     31from waeup.sirp.interfaces import (
     32    IAuthPluginUtility, IUserAccount, IPasswordValidator)
     33from waeup.sirp.applicants.interfaces import IApplicant
     34from waeup.sirp.students.authentication import (
     35    StudentAccount, StudentsAuthenticatorPlugin)
    3836
     37class ApplicantAccount(StudentAccount):
     38    """An adapter to turn applicant objects into accounts on-the-fly.
     39    """
     40    grok.context(IApplicant)
     41    grok.implements(IUserAccount)
    3942
    40 class ApplicantPrincipalInfo(object):
    41     """Infos about an applicant principal.
    42     """
    43     grok.implements(IApplicantPrincipalInfo)
     43    @property
     44    def name(self):
     45        return self.context.applicant_id
    4446
    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
     47    @property
     48    def title(self):
     49        return self.context.fullname
    5250
    53 class ApplicantPrincipal(Principal):
    54     """An applicant principal.
     51    @property
     52    def user_type(self):
     53        return u'applicant'
    5554
    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 
    74 class 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 
    104 class 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 
    127 class 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 
    177 class ApplicantsAuthenticatorPlugin(grok.GlobalUtility):
    178     """Authenticate applicants.
    179     """
     55class ApplicantsAuthenticatorPlugin(StudentsAuthenticatorPlugin):
     56    grok.implements(IAuthenticatorPlugin)
    18057    grok.provides(IAuthenticatorPlugin)
    18158    grok.name('applicants')
    18259
    183     def authenticateCredentials(self, credentials):
    184         """Validate the given `credentials`
     60    def getAccount(self, login):
     61        """Look up a applicant identified by `login`. Returns an account.
    18562
    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.
     63        First we split the login name into the container part and
     64        the application number part. Then we simply look up the key under which
     65        the applicant is stored in the respective applicants cointainer of
     66        the portal.
    18967
    190         Returns a :class:`ApplicantPrincipalInfo` in case of
    191         successful validation, ``None`` else.
     68        Returns not an applicant but an account object adapted from any
     69        applicant found.
    19270
    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 
     71        If no such applicant exists, ``None`` is returned.
    21572        """
    216         if not isinstance(credentials, dict):
     73        site = grok.getSite()
     74        if site is None:
    21775            return None
    218         accesscode = credentials.get('accesscode', None)
    219         if accesscode is None:
     76        applicantsroot = site.get('applicants', None)
     77        if applicantsroot is None:
    22078            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:
     79        try:
     80            container, application_number = login.split('_')
     81        except ValueError:
    22582            return None
    226         if ac.state == 'disabled':
     83        applicantscontainer = applicantsroot.get(container,None)
     84        if applicantscontainer is None:
    22785            return None
    228         if ac.state == 'used' and appl_ac != ac.representation:
     86        applicant = applicantscontainer.get(application_number, None)
     87        if applicant is None:
    22988            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
     89        return IUserAccount(applicant)
    24690
    24791class ApplicantsAuthenticatorSetup(grok.GlobalUtility):
    248     """A global utility that sets up any PAU passed.
     92    """Register or unregister applicant authentication for a PAU.
    24993
    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.
     94    This piece is called when a new site is created.
    25395    """
    25496    grok.implements(IAuthPluginUtility)
     
    25698
    25799    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']
     100        plugins = list(pau.authenticatorPlugins)
     101        plugins.append('applicants')
    279102        pau.authenticatorPlugins = tuple(plugins)
    280103        return pau
    281104
    282105    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'])
     106        plugins = [x for x in pau.authenticatorPlugins
     107                   if x != 'applicants']
     108        pau.authenticatorPlugins = tuple(plugins)
    291109        return pau
    292 
    293 
    294 def 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 TracChangeset for help on using the changeset viewer.