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.

Location:
main/waeup.sirp/trunk/src/waeup/sirp/applicants
Files:
2 deleted
13 edited

Legend:

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

    r7159 r7240  
    33# Make this a package.
    44from waeup.sirp.applicants.applicant import (
    5     ResultEntry, Applicant, ApplicantFactory, ApplicantImageStoreHandler,
    6     get_regno_or_ac, ApplicantImageNameChooser,
     5    Applicant, ApplicantFactory, ApplicantImageStoreHandler,
     6    ApplicantImageNameChooser,
    77    )
    88from waeup.sirp.applicants.container import ApplicantsContainer
     
    1414
    1515__all__ = [
    16     'ResultEntry',
    1716    'Applicant',
    1817    'ApplicantFactory',
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py

    r7192 r7240  
    1717##
    1818import os
     19from random import SystemRandom as r
    1920import grok
    2021from grok import index
    2122from zope.component.interfaces import IFactory
     23from zope.securitypolicy.interfaces import IPrincipalRoleManager
    2224from zope.interface import implementedBy
    2325from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
     
    2729from waeup.sirp.interfaces import (
    2830    IObjectHistory, IFileStoreHandler, IFileStoreNameChooser)
    29 from waeup.sirp.utils.helpers import attrs_to_fields
     31from waeup.sirp.utils.helpers import attrs_to_fields, get_current_principal
    3032from waeup.sirp.applicants.interfaces import (
    31     IResultEntry, IApplicant, IApplicantEdit,
     33    IApplicant, IApplicantEdit,
    3234    )
    33 
    34 
    35 def get_regno_or_ac(context):
    36     reg_no = getattr(context, 'reg_no', None)
    37     if reg_no is None:
    38         return getattr(context, 'access_code', None)
    39     return reg_no
    40 
    41 class ResultEntry(grok.Context):
    42     grok.implements(IResultEntry)
    43 
    44     def __init__(self, subject=None, score=None):
    45         self.subject = subject
    46         self.score = score
     35from waeup.sirp.applicants.workflow import INITIALIZED
     36
     37def generate_applicant_id(container=None):
     38    if container is not None:
     39        aid = u"%s_%d" % (container.code, r().randint(99999,1000000))
     40        while aid in container.keys():
     41            aid = u"%s_%d" % (container.code, r().randint(99999,1000000))
     42        return aid
     43    else:
     44        # In some tests we don't use containers
     45        return u"xxx_1234"
    4746
    4847class Applicant(grok.Model):
     
    5049    grok.provides(IApplicant)
    5150
    52     def __init__(self):
     51    def __init__(self, container=None):
    5352        super(Applicant, self).__init__()
     53        self.applicant_id = generate_applicant_id(container)
     54        self.password = None
    5455        IWorkflowInfo(self).fireTransition('init')
    5556        self.application_date = None
     
    6970        history = IObjectHistory(self)
    7071        return history
     72
     73    @property
     74    def application_number(self):
     75        return self.applicant_id.split('_')[1]
     76
     77    @property
     78    def fullname(self):
     79        # We do not necessarily have the middlenames attribute
     80        middlenames = getattr(self, 'middlenames', None)
     81        if middlenames:
     82            return '%s %s %s' % (self.firstname,
     83                middlenames, self.lastname)
     84        else:
     85            return '%s %s' % (self.firstname, self.lastname)
    7186
    7287# Set all attributes of Applicant required in IApplicant as field
     
    8499
    85100    access_code = index.Field(attribute='access_code')
     101    applicant_id = index.Field(attribute='applicant_id')
     102    reg_no = index.Field(attribute='reg_no')
    86103
    87104class ApplicantFactory(grok.GlobalUtility):
     
    94111
    95112    def __call__(self, *args, **kw):
    96         return Applicant()
     113        return Applicant(kw['container'])
    97114
    98115    def getInterfaces(self):
     
    147164        *Example:*
    148165
    149         For an applicant with registration no. ``'My_reg_no_1234'``
     166        For an applicant with applicant_id. ``'app2001_1234'``
    150167        and stored in an applicants container called
    151168        ``'mycontainer'``, this chooser would create:
    152169
    153           ``'__img-applicant__mycontainer/My_reg_no_1234.jpg'``
     170          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
    154171
    155172        meaning that the passport image of this applicant would be
    156173        stored in the site-wide file storage in path:
    157174
    158           ``mycontainer/My_reg_no_1234.jpg``
     175          ``mycontainer/app2001_1234.jpg``
    159176
    160177        If the context applicant has no parent, ``'_default'`` is used
     
    171188        marked_filename = '__%s__%s/%s.jpg' % (
    172189            APPLICANT_IMAGE_STORE_NAME,
    173             parent_name, get_regno_or_ac(self.context))
     190            parent_name, self.context.applicant_id)
    174191        return marked_filename
    175192
     
    214231        return file, path, WAeUPImageFile(
    215232            file_obj.filename, file_obj.data)
     233
     234@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
     235def handle_applicant_added(applicant, event):
     236    """If an applicant is added local and site roles are assigned.
     237    """
     238    role_manager = IPrincipalRoleManager(applicant)
     239    role_manager.assignRoleToPrincipal(
     240        'waeup.local.ApplicationOwner', applicant.applicant_id)
     241    # Assign current principal the global Applicant role
     242    role_manager = IPrincipalRoleManager(grok.getSite())
     243    role_manager.assignRoleToPrincipal(
     244        'waeup.Applicant', applicant.applicant_id)
     245
     246    # Assign global applicant role for new applicant (alternative way)
     247    #account = IUserAccount(applicant)
     248    #account.roles = ['waeup.Applicant']
     249
     250    return
     251
     252@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
     253def handle_applicant_removed(applicant, event):
     254    """If an applicant is removed a message is logged.
     255    """
     256    comment = 'Applicant record removed'
     257    target = applicant.applicant_id
     258    # In some tests we don't have a principal
     259    try:
     260        user = get_current_principal().id
     261    except (TypeError, AttributeError):
     262        return
     263    try:
     264        grok.getSite()['applicants'].logger.info('%s - %s - %s' % (
     265            user, target, comment))
     266    except KeyError:
     267        # If we delete an entire university instance there won't be
     268        # an applicants subcontainer
     269        return
     270    return
  • 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
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py

    r7237 r7240  
    2424from datetime import datetime, date
    2525from zope.authentication.interfaces import ILogout, IAuthentication
    26 from zope.component import getUtility
     26from zope.component import getUtility, createObject
    2727from zope.formlib.widget import CustomWidgetFactory
    2828from zope.formlib.form import setUpEditWidgets
     
    5151    )
    5252from waeup.sirp.interfaces import (
    53     IWAeUPObject, ILocalRolesAssignable, IExtFileStore, IFileStoreNameChooser)
     53    IWAeUPObject, ILocalRolesAssignable, IExtFileStore,
     54    IFileStoreNameChooser, IPasswordValidator, IUserAccount)
    5455from waeup.sirp.permissions import get_users_with_local_roles
    5556from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
     
    6162from waeup.sirp.widgets.objectwidget import (
    6263    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
    63 from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
     64from waeup.sirp.applicants import Applicant, get_applicant_data
    6465from waeup.sirp.applicants.interfaces import (
    65     IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
     66    IApplicant, IApplicantEdit, IApplicantsRoot,
    6667    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
    6768    MAX_UPLOAD_SIZE,
    6869    )
    6970from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
    70 
    71 results_widget = CustomWidgetFactory(
    72     WAeUPObjectWidget, ResultEntry)
    73 
    74 results_display_widget = CustomWidgetFactory(
    75     WAeUPObjectDisplayWidget, ResultEntry)
     71from waeup.sirp.students.viewlets import PrimaryStudentNavTab
     72
     73grok.context(IWAeUPObject) # Make IWAeUPObject the default context
    7674
    7775class ApplicantsRootPage(WAeUPPage):
     
    228226        """Get a title for a context.
    229227        """
    230         return self.context.access_code
     228        return self.context.application_number
    231229
    232230class ApplicantsAuthTab(PrimaryNavTab):
     
    235233    grok.context(IWAeUPObject)
    236234    grok.order(3)
    237     grok.require('waeup.viewApplication')
     235    grok.require('waeup.viewApplicationsTab')
    238236    grok.template('primarynavtab')
    239237    pnav = 3
     
    263261    #    return tt
    264262
     263class MyApplicationDataTab(PrimaryStudentNavTab):
     264    """MyData-tab in primary navigation.
     265    """
     266    grok.order(3)
     267    grok.require('waeup.viewMyApplicationDataTab')
     268    grok.template('primarynavtab')
     269    pnav = 3
     270    tab_title = u'My Data'
     271
     272    @property
     273    def link_target(self):
     274        try:
     275            container, application_number = self.request.principal.id.split('_')
     276        except ValueError:
     277            return
     278        rel_link = '/applicants/%s/%s' % (container, application_number)
     279        return self.view.application_url() + rel_link
     280
    265281class ApplicantsContainerPage(WAeUPDisplayFormPage):
    266282    """The standard view for regular applicant containers.
     
    292308    text = 'Manage applicants container'
    293309
    294 class ApplicantLoginActionButton(ManageActionButton):
    295     grok.order(2)
    296     grok.context(IApplicantsContainer)
    297     grok.view(ApplicantsContainerPage)
    298     grok.require('waeup.Anonymous')
    299     icon = 'login.png'
    300     text = 'Login for applicants'
    301     target = 'login'
     310#class ApplicantLoginActionButton(ManageActionButton):
     311#    grok.order(2)
     312#    grok.context(IApplicantsContainer)
     313#    grok.view(ApplicantsContainerPage)
     314#    grok.require('waeup.Anonymous')
     315#    icon = 'login.png'
     316#    text = 'Login for applicants'
     317#    target = 'login'
    302318
    303319class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
     
    394410        return del_local_roles(self,3,**data)
    395411
     412# Not used anymore
    396413class ApplicantLoginPage(WAeUPPage):
    397414    grok.context(IApplicantsContainer)
     
    421438            self.flash('Entered credentials are invalid.')
    422439            return
    423         if not IApplicantPrincipal.providedBy(self.request.principal):
    424             # Don't care if user is already authenticated as non-applicant
     440#        if not IApplicantPrincipal.providedBy(self.request.principal):
     441#            # Don't care if user is already authenticated as non-applicant
    425442            return
    426443
     
    470487
    471488        # Mark application as started
    472         if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
    473             IWorkflowInfo(self.context[pin]).fireTransition('start')
     489        #if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
     490        #    IWorkflowInfo(self.context[pin]).fireTransition('start')
    474491
    475492        self.redirect(self.url(self.context[pin], 'edit'))
     493        return
     494
     495    def render(self):
    476496        return
    477497
     
    482502    grok.require('waeup.manageApplication')
    483503    grok.name('addapplicant')
    484     grok.template('applicantaddpage')
     504    #grok.template('applicantaddpage')
     505    form_fields = grok.AutoFields(IApplicant).select(
     506        'firstname', 'middlenames', 'lastname',
     507        'email', 'phone')
    485508    title = 'Applicants'
    486509    label = 'Add applicant'
     
    491514        return "Applicants Container: %s" % self.context.title
    492515
    493     @property
    494     def ac_prefix(self):
    495         return self.context.ac_prefix
    496 
    497516    @grok.action('Create application record')
    498517    def addApplicant(self, **data):
    499         ac_series = self.request.form.get('form.ac_series', None)
    500         ac_number = self.request.form.get('form.ac_number', None)
    501         pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
    502         if not invalidate_accesscode(pin, comment=u"Invalidated by system"):
    503             self.flash('%s is not a valid access code.' % pin)
    504             self.redirect(self.url(self.context, '@@manage')+'#tab-2')
    505             return
    506         else:
    507             # Create applicant record
    508             applicant = Applicant()
    509             applicant.access_code = pin
    510             self.context[pin] = applicant
    511         self.redirect(self.url(self.context[pin], 'manage'))
    512         return
    513 
    514 class AccessCodeViewLink(LeftSidebarLink):
    515     grok.order(1)
    516     grok.require('waeup.Public')
    517     icon = 'actionicon_view.png'
    518     title = 'View Record'
    519     target = '/@@index'
    520 
    521     @property
    522     def url(self):
    523         if not IApplicantPrincipal.providedBy(self.request.principal):
    524             return ''
    525         access_code = getattr(self.request.principal,'access_code',None)
    526         if access_code:
    527             applicant_object = get_applicant_data(access_code)
    528             return absoluteURL(applicant_object, self.request) + self.target
    529         return ''
    530 
    531 class AccessCodeEditLink(AccessCodeViewLink):
    532     grok.order(2)
    533     grok.require('waeup.Public')
    534     icon = 'actionicon_modify.png'
    535     title = 'Edit Record'
    536     target = '/@@edit'
    537 
    538     @property
    539     def url(self):
    540         if not IApplicantPrincipal.providedBy(self.request.principal):
    541             return ''
    542         access_code = getattr(self.request.principal,'access_code',None)
    543         if access_code:
    544             applicant_object = get_applicant_data(access_code)
    545             if applicant_object.locked:
    546                 return ''
    547             return absoluteURL(applicant_object, self.request) + self.target
    548         return ''
    549 
    550 class AccessCodeSlipLink(AccessCodeViewLink):
    551     grok.order(3)
    552     grok.require('waeup.Public')
    553     icon = 'actionicon_pdf.png'
    554     title = 'Download Slip'
    555     target = '/application_slip.pdf'
     518        applicant = createObject(u'waeup.Applicant', container = self.context)
     519        self.applyData(applicant, **data)
     520        self.context.addApplicant(applicant)
     521        self.flash('Applicant record created.')
     522        self.redirect(self.url(self.context[applicant.application_number], 'index'))
     523        return
    556524
    557525class ApplicantDisplayFormPage(WAeUPDisplayFormPage):
     
    561529    grok.template('applicantdisplaypage')
    562530    form_fields = grok.AutoFields(IApplicant).omit(
    563         'locked').omit('course_admitted')
     531        'locked', 'course_admitted', 'password')
    564532    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    565533    label = 'Applicant'
     
    568536    def update(self):
    569537        self.passport_url = self.url(self.context, 'passport.jpg')
    570         return
     538        # Mark application as started if applicant logs in for the first time
     539        if IWorkflowState(self.context).getState() == INITIALIZED:
     540            IWorkflowInfo(self.context).fireTransition('start')
     541        return
     542
     543    @property
     544    def hasPassword(self):
     545        if self.context.password:
     546            return 'set'
     547        return 'unset'
    571548
    572549    @property
    573550    def title(self):
    574         if self.request.principal.title == 'Applicant':
    575             return u'Your Application Record'
    576         return '%s' % self.context.access_code
     551        return 'Application Record %s' % self.context.application_number
    577552
    578553    @property
    579554    def label(self):
    580555        container_title = self.context.__parent__.title
    581         return '%s Application Record' % container_title
     556        return '%s Application Record %s' % (
     557            container_title, self.context.application_number)
    582558
    583559    def getCourseAdmitted(self):
     
    607583    grok.require('waeup.viewApplication')
    608584    form_fields = grok.AutoFields(IApplicant).omit(
    609         'locked').omit('course_admitted')
     585        'locked', 'course_admitted')
    610586    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    611587    prefix = 'form'
     
    614590    def label(self):
    615591        container_title = self.context.__parent__.title
    616         return '%s Application Record' % container_title
     592        return '%s Application Record %s' % (
     593            container_title, self.context.application_number)
    617594
    618595    def getCourseAdmitted(self):
     
    710687    target = 'manage'
    711688
     689class ApplicantEditActionButton(ManageActionButton):
     690    grok.context(IApplicant)
     691    grok.view(ApplicantDisplayFormPage)
     692    grok.require('waeup.handleApplication')
     693    text = 'Edit application record'
     694    target ='edit'
     695
     696    @property
     697    def target_url(self):
     698        """Get a URL to the target...
     699        """
     700        if self.context.locked:
     701            return
     702        return self.view.url(self.view.context, self.target)
    712703
    713704def handle_img_upload(upload, context, view):
     
    756747    @property
    757748    def title(self):
    758         return self.context.access_code
     749        return 'Application Record %s' % self.context.application_number
    759750
    760751    @property
    761752    def label(self):
    762753        container_title = self.context.__parent__.title
    763         return '%s Application Form' % container_title
     754        return '%s Application Form %s' % (
     755            container_title, self.context.application_number)
    764756
    765757    def getTransitions(self):
     
    776768    @grok.action('Save')
    777769    def save(self, **data):
     770        form = self.request.form
     771        password = form.get('password', None)
     772        password_ctl = form.get('control_password', None)
     773        if password:
     774            validator = getUtility(IPasswordValidator)
     775            errors = validator.validate_password(password, password_ctl)
     776            if errors:
     777                self.flash( ' '.join(errors))
     778                return
    778779        if self.passport_changed is False:  # False is not None!
    779780            return # error during image upload. Ignore other values
     
    782783        if changed_fields:
    783784            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
     785        else:
     786            changed_fields = []
     787        if self.passport_changed:
     788            changed_fields.append('passport')
     789        if password:
     790            # Now we know that the form has no errors and can set password ...
     791            IUserAccount(self.context).setPassword(password)
     792            changed_fields.append('password')
    784793        fields_string = ' + '.join(changed_fields)
    785         if self.passport_changed:
    786             fields_string += ' + passport'
    787         #self.context._p_changed = True
    788         form = self.request.form
    789794        trans_id = form.get('transition', None)
    790795        if trans_id:
     
    804809    form_fields = grok.AutoFields(IApplicantEdit).omit(
    805810        'locked', 'course_admitted', 'student_id',
    806         'screening_score',
     811        'screening_score', 'applicant_id'
    807812        )
    808813    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
     
    863868    grok.context(IApplicant)
    864869    grok.view(ApplicantManageFormPage)
    865     grok.require('waeup.manageApplication')
     870    grok.require('waeup.viewApplication')
    866871    icon = 'actionicon_view.png'
    867872    text = 'View application record'
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantdisplaypage.pt

    r7200 r7240  
    1111</div>
    1212
     13<img src="" tal:attributes="src view/passport_url" />
     14
    1315<table class="zebra">
    1416  <tbody>
    15     <tr>
    16     <td><img src="" tal:attributes="src view/passport_url" /></td>
    17     </tr>
    1817    <tal:block repeat="widget view/widgets">
    1918      <tr>
     
    3433      </td>
    3534    </tr>
     35    <tr>
     36      <td class="fieldname">
     37          Password:
     38      </td>
     39      <td>
     40          <tal:password replace="view/hasPassword" />
     41      </td>
     42    <tr>
    3643  </tbody>
    3744</table>
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicanteditpage.pt

    r7200 r7240  
    2323  </div>
    2424
    25   <table class="form-fields zebra">
     25  <table class="zebra">
    2626    <tbody>
    2727      <tal:block repeat="widget view/widgets">
     
    6262      </tr>
    6363      <tr tal:condition="view/manage_applications">
     64        <td class="label"><label>Password:</label></td>
     65        <td>
     66          <input name="password" type="password"  />
     67        </td>
     68      </tr>
     69      <tr tal:condition="view/manage_applications">
     70        <td class="label"><label>Retype password:</label></td>
     71        <td>
     72          <input name="control_password" type="password" />
     73        </td>
     74      </tr>
     75      <tr tal:condition="view/manage_applications">
    6476        <td class="label"><label>Application Transition:</label></td>
    6577        <td>
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/container.py

    r7192 r7240  
    2424from waeup.sirp.applicants.interfaces import (
    2525    IApplicantsContainer, IApplicantsContainerAdd,
    26     IApplicantsContainerProvider,
     26    IApplicantsContainerProvider, IApplicant
    2727    )
    2828from waeup.sirp.utils.helpers import attrs_to_fields
     
    8383        raise NotImplementedError()
    8484
     85    def addApplicant(self, applicant):
     86        """Add an applicant.
     87        """
     88        if not IApplicant.providedBy(applicant):
     89            raise TypeError(
     90                'ApplicantsContainers contain only IApplicant instances')
     91        self[applicant.application_number] = applicant
     92        return
     93
    8594ApplicantsContainer = attrs_to_fields(ApplicantsContainer)
    8695
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py

    r7192 r7240  
    2929from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
    3030from zc.sourcefactory.basic import BasicSourceFactory
    31 from waeup.sirp.interfaces import IWAeUPObject, year_range
     31from waeup.sirp.interfaces import IWAeUPObject, year_range, validate_email
    3232from waeup.sirp.university.vocabularies import application_categories
    3333from waeup.sirp.students.vocabularies import (
     
    4242MAX_UPLOAD_SIZE = 1024 * 20
    4343
    44 # Define a valiation method for email addresses
    45 class NotAnEmailAddress(schema.ValidationError):
    46     __doc__ = u"Invalid email address"
    47 
    48 check_email = re.compile(
    49     r"[a-zA-Z0-9._%-]+@([a-zA-Z0-9-]+.)*[a-zA-Z]{2,4}").match
    50 def validate_email(value):
    51     if not check_email(value):
    52         raise NotAnEmailAddress(value)
    53     return True
    54 
    5544class ApplicantContainerProviderSource(BasicSourceFactory):
    5645    """A source offering all available applicants container types.
     
    9079        return "%s - %s" % (
    9180            factory.container_title, factory.container_description)
    92 
    93 class IResultEntry(IWAeUPObject):
    94     subject = schema.TextLine(
    95         title = u'Subject',
    96         description = u'The subject',
    97         required=False,
    98         )
    99     score = schema.TextLine(
    100         title = u'Score',
    101         description = u'The score',
    102         required=False,
    103         )
    10481
    10582class IApplicantsRoot(IWAeUPObject, IContainer):
     
    263240    """The data for an applicant.
    264241
    265     This is a base interface with no field (except ``reg_no``)
     242    This is a base interface with no field
    266243    required. For use with importers, forms, etc., please use one of
    267244    the derived interfaces below, which set more fields to required
     
    269246    """
    270247    history = Attribute('Object history, a list of messages.')
    271     state = Attribute('Returns the application state of an applicant')
     248    state = Attribute('The application state of an applicant')
     249    fullname = Attribute('The fullname of an applicant')
    272250    application_date = Attribute('Date of submission, used for export only')
    273 
    274     #def getApplicantsRootLogger():
    275     #    """Returns the logger from the applicants root object
    276     #    """
     251    password = Attribute('Encrypted password of a applicant')
     252    application_number = Attribute('The key under which the record is stored')
    277253
    278254    def loggerInfo(ob_class, comment):
     
    280256        """
    281257
     258    applicant_id = schema.TextLine(
     259        title = u'Applicant Id',
     260        required = False,
     261        readonly = True,
     262        )
     263
    282264    reg_no = schema.TextLine(
    283265        title = u'JAMB Registration Number',
    284266        readonly = True,
     267        required = False,
    285268        )
    286269    access_code = schema.TextLine(
     
    329312    email = schema.ASCIILine(
    330313        title = u'Email',
    331         required = False,
     314        required = True,
    332315        constraint=validate_email,
    333316        )
     
    416399        )
    417400
    418 class IApplicantPrincipalInfo(IPrincipalInfo):
    419     """Infos about principals that are applicants.
    420     """
    421     access_code = Attribute("The Access Code the user purchased")
    422 
    423 class IApplicantPrincipal(IPrincipal):
    424     """A principal that is an applicant.
    425 
    426     This interface extends zope.security.interfaces.IPrincipal and
    427     requires also an `id` and other attributes defined there.
    428     """
    429     access_code = schema.TextLine(
    430         title = u'Access Code',
    431         description = u'The access code purchased by the user.',
    432         required = True,
    433         readonly = True)
    434 
    435 class IApplicantsFormChallenger(Interface):
    436     """A challenger that uses a browser form to collect applicant
    437        credentials.
    438     """
    439     loginpagename = schema.TextLine(
    440         title = u'Loginpagename',
    441         description = u"""Name of the login form used by challenger.
    442 
    443         The form must provide an ``access_code`` input field.
    444         """)
    445 
    446     accesscode_field = schema.TextLine(
    447         title = u'Access code field',
    448         description = u'''Field of the login page which is looked up for
    449                           access_code''',
    450         default = u'access_code',
    451         )
    452 
    453 
    454 class IApplicantSessionCredentials(Interface):
    455     """Interface for storing and accessing applicant credentials in a
    456        session.
    457     """
    458 
    459     def __init__(access_code):
    460         """Create applicant session credentials."""
    461 
    462     def getAccessCode():
    463         """Return the access code."""
    464 
    465 
    466401class IApplicantsContainerProvider(Interface):
    467402    """A provider for applicants containers.
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/permissions.py

    r7192 r7240  
    2929    grok.name('waeup.viewApplication')
    3030
     31class ViewApplicationsTab(grok.Permission):
     32    grok.name('waeup.viewApplicationsTab')
     33
     34class ViewMyApplicationDataTab(grok.Permission):
     35    grok.name('waeup.viewMyApplicationDataTab')
     36
    3137class ManageApplication(grok.Permission):
    3238    grok.name('waeup.manageApplication')
     
    3642    grok.name('waeup.local.ApplicationOwner')
    3743    grok.title(u'Application Owner')
    38     grok.permissions('waeup.handleApplication', 'waeup.viewApplication')
     44    grok.permissions('waeup.handleApplication', 'waeup.viewApplication',
     45                     'waeup.Authenticated')
    3946
    4047# Site role
     
    4249class ApplicantRole(grok.Role):
    4350    grok.name('waeup.Applicant')
    44     grok.permissions('waeup.viewAcademics')
     51    grok.permissions('waeup.viewAcademics', 'waeup.viewMyApplicationDataTab')
    4552
    4653class ApplicationsOfficer(grok.Role):
    4754    grok.name('waeup.ApplicationsOfficer')
    4855    grok.title(u'Applications Officer')
    49     grok.permissions('waeup.manageApplication', 'waeup.viewApplication')
     56    grok.permissions('waeup.manageApplication', 'waeup.viewApplication'
     57                     'waeup.viewApplicationsTab')
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_applicant.py

    r7193 r7240  
    3232from waeup.sirp.interfaces import IFileStoreHandler, IFileStoreNameChooser
    3333from waeup.sirp.applicants import (
    34     ResultEntry, Applicant, ApplicantFactory, get_regno_or_ac,
     34    Applicant, ApplicantFactory, ApplicantsContainer,
    3535    ApplicantImageStoreHandler, ApplicantImageNameChooser,
    3636    )
    37 from waeup.sirp.applicants.interfaces import IResultEntry, IApplicant
     37from waeup.sirp.applicants.interfaces import IApplicant
    3838from waeup.sirp.testing import FunctionalTestCase, FunctionalLayer
    3939
     
    5656        super(HelperTests, self).tearDown()
    5757        shutil.rmtree(self.workdir)
    58         return
    59 
    60     def test_get_regno_or_ac(self):
    61         # we can get reg_no or access_code of an applicants if it is set
    62         appl1 = Applicant()
    63         appl2 = Applicant()
    64         appl2.reg_no = u'foo'
    65         appl3 = Applicant()
    66         appl3.access_code = u'bar'
    67         appl4 = Applicant()
    68         appl4.reg_no = u'foo'
    69         appl4.access_code = u'bar'
    70         self.assertTrue(
    71             get_regno_or_ac(appl1) is None)
    72         self.assertEqual(
    73             get_regno_or_ac(appl2), u'foo')
    74         self.assertEqual(
    75             get_regno_or_ac(appl3), u'bar')
    76         self.assertEqual(
    77             get_regno_or_ac(appl4), u'foo')
    7858        return
    7959
     
    124104        return
    125105
    126 class ApplicantImageNameChooserTests(FunctionalTestCase):
    127 
    128     layer = FunctionalLayer
    129 
    130     def test_iface(self):
    131         # make sure we implement promised interfaces
    132         obj = ApplicantImageNameChooser(None) # needs a context normally
    133         verify.verifyClass(IFileStoreNameChooser, ApplicantImageNameChooser)
    134         verify.verifyObject(IFileStoreNameChooser, obj)
    135         return
    136 
    137     def test_name_chooser_available(self):
    138         # we can get a name chooser for applicant objects as adapter
    139         appl = Applicant()
    140         chooser = IFileStoreNameChooser(appl)
    141         self.assertTrue(chooser is not None)
    142         return
    143 
    144     def test_name_chooser_applicant_wo_container(self):
    145         # we can get an image filename for applicants not in a container
    146         appl = Applicant()
    147         appl.reg_no = u'MY_REG_NO'
    148         chooser = IFileStoreNameChooser(appl)
    149         result = chooser.chooseName()
    150         # the file would be stored in a ``_default`` directory.
    151         self.assertEqual(
    152             result, '__img-applicant___default/MY_REG_NO.jpg')
    153         return
    154 
    155     def test_name_chooser_applicant_w_container(self):
    156         appl = Applicant()
    157         appl.reg_no = u'MY_REG_NO'
    158         fake_container = grok.Container()
    159         fake_container.__name__ = 'folder'
    160         fake_container['appl'] = appl
    161         appl.__parent__ = fake_container
    162         chooser = IFileStoreNameChooser(appl)
    163         result = chooser.chooseName()
    164         self.assertEqual(
    165             result, '__img-applicant__folder/MY_REG_NO.jpg')
    166         return
    167 
    168     def test_name_chooser_check_name(self):
    169         # we can check file ids for applicants
    170         appl = Applicant()
    171         appl.reg_no = u'MY_REG_NO'
    172         fake_container = grok.Container()
    173         fake_container.__name__ = 'folder'
    174         fake_container['appl'] = appl
    175         appl.__parent__ = fake_container
    176         chooser = IFileStoreNameChooser(appl)
    177         result1 = chooser.checkName('foo')
    178         result2 = chooser.checkName('__img-applicant__folder/MY_REG_NO.jpg')
    179         self.assertEqual(result1, False)
    180         self.assertEqual(result2, True)
    181         return
    182 
    183 class ResultEntryTest(unittest.TestCase):
    184 
    185     def setUp(self):
    186         self.result_entry = ResultEntry()
    187         return
    188 
    189     def tearDown(self):
    190         pass
    191 
    192     def test_interfaces(self):
    193         verify.verifyClass(IResultEntry, ResultEntry)
    194         verify.verifyObject(IResultEntry, self.result_entry)
    195 
    196     def test_resultentry(self):
    197         entry = ResultEntry('Some subject', 3.7)
    198         assert entry.subject == 'Some subject'
    199         assert entry.score == 3.7
    200 
    201106class ApplicantTest(FunctionalTestCase):
    202107
     
    235140
    236141    def test_factory(self):
    237         obj = self.factory()
     142        obj = self.factory(container=None)
    238143        assert isinstance(obj, Applicant)
    239144
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_authentication.py

    r7238 r7240  
    1616## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    1717##
    18 import shutil
    19 import tempfile
    2018import unittest
    2119from zope.authentication.interfaces import IAuthentication
    22 from zope.component import provideAdapter, getUtility, queryUtility
    23 from zope.component.hooks import setSite
    24 from zope.interface import verify
    25 from zope.pluggableauth.interfaces import  IAuthenticatedPrincipalFactory
    26 from zope.publisher.browser import TestRequest
    27 from zope.publisher.interfaces import IRequest
    28 from zope.session.interfaces import ISession
    29 from zope.testbrowser.testing import Browser
    30 from zope.testing import cleanup
    31 from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
    32 from waeup.sirp.app import University
    33 from waeup.sirp.interfaces import IAuthPluginUtility
    34 from waeup.sirp.applicants import  ApplicantsContainer, Applicant
     20from zope.component import provideUtility, queryUtility, getGlobalSiteManager
     21from zope.interface.verify import verifyClass, verifyObject
     22from zope.password.password import SSHAPasswordManager
     23from zope.password.interfaces import IPasswordManager
     24from zope.pluggableauth import PluggableAuthentication
     25from zope.security.interfaces import Unauthorized
     26from zope.securitypolicy.role import Role
     27from zope.securitypolicy.interfaces import IRole, Allow
     28from waeup.sirp.authentication import get_principal_role_manager
     29from waeup.sirp.interfaces import IAuthPluginUtility, IUserAccount
    3530from waeup.sirp.applicants.authentication import (
    36     ApplicantsAuthenticatorPlugin, WAeUPApplicantCredentialsPlugin,
    37     ApplicantCredentials, AuthenticatedApplicantPrincipalFactory,
    38     ApplicantPrincipalInfo, ApplicantPrincipal, ApplicantsAuthenticatorSetup)
     31    ApplicantsAuthenticatorSetup, ApplicantAccount)
     32from waeup.sirp.applicants.tests.test_browser import ApplicantsFullSetup
     33from waeup.sirp.testing import FunctionalLayer
     34
     35class ApplicantsAuthenticatorSetupTests(unittest.TestCase):
     36
     37    def test_iface(self):
     38        obj = ApplicantsAuthenticatorSetup()
     39        verifyClass(IAuthPluginUtility, ApplicantsAuthenticatorSetup)
     40        verifyObject(IAuthPluginUtility, obj)
     41        return
     42
     43    def test_register(self):
     44        # Make sure registration works.
     45        setup = ApplicantsAuthenticatorSetup()
     46        pau = PluggableAuthentication()
     47        setup.register(pau)
     48        self.assertTrue('applicants' in pau.authenticatorPlugins)
     49        return
     50
     51    def test_unregister(self):
     52        # Make sure deregistration works.
     53        setup = ApplicantsAuthenticatorSetup()
     54        pau = PluggableAuthentication()
     55        pau.authenticatorPlugins = ('applicants')
     56        setup.unregister(pau)
     57        self.assertTrue('applicants' not in pau.authenticatorPlugins)
     58        return
    3959
    4060
    41 class FakeBatch(dict):
    42     def getAccessCode(self, id):
    43         return self.get(id)
     61class FakeApplicant(object):
     62    applicant_id = 'test_appl'
     63    fullname = 'Tilman Gause'
     64    password = None
     65    email = None
     66    phone = None
    4467
    45 class FakeAccessCode(object):
    46     def __init__(self, repr, state = 'initialized'):
    47         self.representation = repr
    48         self.state = state
    4968
    50 class FakeApplication(object):
    51     def __init__(self, ac=None):
    52         self.access_code = ac
     69class MinimalPAU(PluggableAuthentication):
     70    def getPrincipal(self, id):
     71        return 'faked principal'
    5372
    54 def FakeSite():
    55     return {
    56         'applicants': {
    57             'APP': {
    58                 'APP-12345': FakeApplication(u'APP-12345'),
    59                 'APP-54321': FakeApplication(u'APP-54321'),
    60                 'APP-22222': FakeApplication(u'APP-OTHER'),
    61                 'APP-44444': FakeApplication(),
    62                 'APP-55555': FakeApplication(u'APP-OTHER'),
    63                 'APP-77777': FakeApplication(u'APP-77777'),
    64                 },
    65             },
    66         'accesscodes': {
    67             'APP': FakeBatch({
    68                     'APP-12345': FakeAccessCode('APP-12345'),
    69                     'APP-54321': FakeAccessCode('APP-54321', 'used'),
    70                     'APP-11111': FakeAccessCode('APP-11111'),
    71                     'APP-22222': FakeAccessCode('APP-22222'),
    72                     'APP-33333': FakeAccessCode('APP-33333', 'used'),
    73                     'APP-44444': FakeAccessCode('APP-44444', 'used'),
    74                     'APP-55555': FakeAccessCode('APP-55555', 'used'),
    75                     'APP-66666': FakeAccessCode('APP-66666', 'disabled'),
    76                     'APP-77777': FakeAccessCode('APP-77777', 'disabled'),
    77                     })
    78             }
    79         }
    80 
    81 class AuthenticatorPluginTest(FunctionalTestCase):
    82 
    83     layer = FunctionalLayer
    84 
    85     def create_applicant(self, cname, aname, ac=None):
    86         # Create an applicant in an applicants container
    87         setSite(self.app)
    88         if not cname in self.app['applicants'].keys():
    89             container = ApplicantsContainer()
    90             self.app['applicants'][cname] = container
    91         applicant = Applicant()
    92         if ac is not None:
    93             applicant.access_code = ac
    94         self.app['applicants'][cname][aname] = applicant
    95         return
     73class ApplicantAccountTests(unittest.TestCase):
    9674
    9775    def setUp(self):
    98         super(AuthenticatorPluginTest, self).setUp()
     76        self.fake_stud = FakeApplicant()
     77        self.account = ApplicantAccount(self.fake_stud)
    9978
    100         # Setup a sample site for each test
    101         app = University()
    102         self.dc_root = tempfile.mkdtemp()
    103         app['datacenter'].setStoragePath(self.dc_root)
     79        # We provide a minimal PAU
     80        pau = MinimalPAU()
     81        provideUtility(pau, IAuthentication)
    10482
    105         # Prepopulate the ZODB...
    106         self.getRootFolder()['app'] = app
    107         self.app = self.getRootFolder()['app']
     83        # We register a role
     84        test_role = Role('waeup.test.Role', 'Testing Role')
     85        provideUtility(test_role, IRole, name='waeup.test.Role')
    10886
    109         fake_site = FakeSite()
    110         for ckey, fake_container in fake_site['applicants'].items():
    111             for akey, fake_appl in fake_container.items():
    112                 self.create_applicant(ckey, akey, fake_appl.access_code)
    113         del self.app['accesscodes']
    114         self.app['accesscodes'] = fake_site['accesscodes']
    115 
    116         self.plugin = ApplicantsAuthenticatorPlugin()
     87        # We have to setup a password manager utility manually as we
     88        # have no functional test. In functional tests this would
     89        # happen automatically, but it would take a lot more time to
     90        # run the tests.
     91        provideUtility(
     92            SSHAPasswordManager(), IPasswordManager, 'SSHA')
    11793        return
    11894
    11995    def tearDown(self):
    120         super(AuthenticatorPluginTest, self).tearDown()
    121         shutil.rmtree(self.dc_root)
     96        self.account.roles = [] # make sure roles are reset
     97        gsm = getGlobalSiteManager()
     98        to_clean = []
     99        # Clear up utilities registered in setUp
     100        to_clean.append(
     101            (IPasswordManager, queryUtility(
     102                    IPasswordManager, name='SSHA', default=None)))
     103        to_clean.append(
     104            (IAuthentication, queryUtility(IAuthentication, default=None)))
     105        to_clean.append(
     106            (IRole, queryUtility(IRole, name='test.Role', default=None)))
     107        for iface, elem in to_clean:
     108            if elem is not None:
     109                gsm.unregisterUtility(elem, iface)
    122110        return
    123111
    124     def test_invalid_credentials(self):
    125         result = self.plugin.authenticateCredentials('not-a-dict')
    126         assert result is None
    127 
    128         result = self.plugin.authenticateCredentials(
    129             dict(accesscode=None, foo='blah'))
    130         assert result is None
    131 
    132         result = self.plugin.authenticateCredentials(
    133             dict(accesscode='Nonsense',))
    134         assert result is None
    135 
    136         # Possible cases, where formal correct authentication
    137         # data is not valid:
    138         result = self.plugin.authenticateCredentials(
    139             dict(accesscode='APP-33333'))
    140         assert result is None
    141 
    142         result = self.plugin.authenticateCredentials(
    143             dict(accesscode='APP-55555'))
    144         assert result is None
    145 
    146         result = self.plugin.authenticateCredentials(
    147             dict(accesscode='APP-66666'))
    148         assert result is None
    149 
    150         result = self.plugin.authenticateCredentials(
    151             dict(accesscode='APP-77777'))
    152         assert result is None
    153 
     112    def test_iface(self):
     113        verifyClass(IUserAccount, ApplicantAccount)
     114        verifyObject(IUserAccount, self.account)
    154115        return
    155116
    156     def test_valid_credentials(self):
    157         """The six different cases where we allow login.
    158 
    159         All other combinations should be forbidden.
    160         """
    161         result = self.plugin.authenticateCredentials(
    162             dict(accesscode='APP-11111'))
    163         assert result is not None
    164 
    165         result = self.plugin.authenticateCredentials(
    166             dict(accesscode='APP-12345'))
    167         assert result is not None
    168 
    169         result = self.plugin.authenticateCredentials(
    170             dict(accesscode='APP-54321'))
    171         assert result is not None
    172 
    173         # check the `principalInfo` method of authenticator
    174         # plugin. This is only here to satisfy the coverage report.
    175         assert self.plugin.principalInfo('not-an-id') is None
     117    def test_set_password(self):
     118        # make sure we can set a password.
     119        self.account.setPassword('secret')
     120        self.assertTrue(self.fake_stud.password is not None)
     121        # we do not store plaintext passwords
     122        self.assertTrue(self.fake_stud.password != 'secret')
     123        # passwords are stored as unicode
     124        self.assertTrue(isinstance(self.fake_stud.password, unicode))
    176125        return
    177126
    178 session_data = {
    179     'zope.pluggableauth.browserplugins': {}
    180     }
    181 
    182 class FakeSession(dict):
    183     def __init__(self, request):
    184         pass
    185 
    186     def get(self, key, default=None):
    187         return self.__getitem__(key, default)
    188 
    189     def __getitem__(self, key, default=None):
    190         return session_data.get(key, default)
    191 
    192     def __setitem__(self, key, value):
    193         session_data[key] = value
     127    def test_check_password(self):
     128        # make sure we can check a password.
     129        self.account.setPassword('secret')
     130        result1 = self.account.checkPassword(None)
     131        result2 = self.account.checkPassword('nonsense')
     132        result3 = self.account.checkPassword('secret')
     133        self.assertEqual(result1, False)
     134        self.assertEqual(result2, False)
     135        self.assertEqual(result3, True)
    194136        return
    195137
    196 class CredentialsPluginTest(unittest.TestCase):
    197 
    198     def setUp(self):
    199         self.request = TestRequest()
    200         provideAdapter(FakeSession, (IRequest,), ISession)
    201         self.plugin = WAeUPApplicantCredentialsPlugin()
    202         self.full_request = TestRequest()
    203         session_data['zope.pluggableauth.browserplugins'] = {}
     138    def test_check_unset_password(self):
     139        # empty and unset passwords do not match anything
     140        self.fake_stud.password = None
     141        result1 = self.account.checkPassword('')
     142        self.fake_stud.password = ''
     143        result2 = self.account.checkPassword('')
     144        self.assertEqual(result1, False)
     145        self.assertEqual(result2, False)
    204146        return
    205147
    206     def tearDown(self):
    207         cleanup.tearDown()
     148    def test_check_password_no_string(self):
     149        # if passed in password is not a string, we gain no access
     150        self.fake_stud.password = 'secret'
     151        result1 = self.account.checkPassword(None)
     152        result2 = self.account.checkPassword(object())
     153        self.assertEqual(result1, False)
     154        self.assertEqual(result2, False)
    208155        return
    209156
    210     def filled_request(self, form_dict):
    211         request = TestRequest()
    212         for key, value in form_dict.items():
    213             request.form[key] = value
    214         return request
    215 
    216     def test_extractCredentials_invalid(self):
    217         result = self.plugin.extractCredentials('not-a-request')
    218         assert result is None
     157    def test_role_set(self):
     158        # make sure we can set roles for principals denoted by account
     159        prm = get_principal_role_manager()
     160        self.assertEqual(prm.getPrincipalsAndRoles(), [])
     161        self.account.roles = ['waeup.test.Role']
     162        self.assertEqual(
     163            prm.getPrincipalsAndRoles(),
     164            [('waeup.test.Role', 'test_appl', Allow)])
    219165        return
    220166
    221     def test_extractCredentials_empty(self):
    222         result = self.plugin.extractCredentials(self.request)
    223         assert result is None
     167    def test_role_get(self):
     168        # make sure we can get roles set for an account
     169        self.assertEqual(self.account.roles, [])
     170        self.account.roles = ['waeup.test.Role',] # set a role
     171        self.assertEqual(self.account.roles, ['waeup.test.Role'])
    224172        return
    225173
    226     def test_extractCredentials_full_set(self):
    227         request = self.filled_request({
    228                 'form.ac_prefix': 'APP',
    229                 'form.ac_series': '1',
    230                 'form.ac_number': '1234567890',
    231                 #'form.jamb_reg_no': 'JAMB_NUMBER',
    232                 })
    233         result = self.plugin.extractCredentials(request)
    234         self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
    235         return
    236 
    237     def test_extractCredentials_accesscode_only(self):
    238         request = self.filled_request({
    239                 'form.ac_prefix': 'APP',
    240                 'form.ac_series': '1',
    241                 'form.ac_number': '1234567890',
    242                 })
    243         result = self.plugin.extractCredentials(request)
    244         self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
    245         return
    246 
    247     def test_extractCredentials_from_empty_session(self):
    248         session_data['zope.pluggableauth.browserplugins']['credentials'] = None
    249         result = self.plugin.extractCredentials(self.request)
    250         assert result is None
    251         return
    252 
    253     def test_extractCredentials_from_nonempty_session(self):
    254         credentials = ApplicantCredentials('APP-1-12345')
    255         session_data['zope.pluggableauth.browserplugins'][
    256             'credentials'] = credentials
    257         result = self.plugin.extractCredentials(self.request)
    258         self.assertEqual(result, {'accesscode': 'APP-1-12345'})
    259         return
    260 
    261 
    262 class ApplicantCredentialsTest(unittest.TestCase):
    263 
    264     def setUp(self):
    265         self.credentials = ApplicantCredentials('SOME_ACCESSCODE')
    266         return
    267 
    268     def tearDown(self):
    269         return
    270 
    271     def test_methods(self):
    272         self.assertEqual(self.credentials.getAccessCode(), 'SOME_ACCESSCODE')
    273         assert self.credentials.getLogin() is None
    274         assert self.credentials.getPassword() is None
    275         return
    276 
    277 class FakePluggableAuth(object):
    278     prefix = 'foo'
    279 
    280 class PrincipalFactoryTest(unittest.TestCase):
    281 
    282     def setUp(self):
    283         self.info = ApplicantPrincipalInfo('APP-1-1234567890')
    284         return
    285 
    286     def tearDown(self):
    287         pass
    288 
    289     def test_principalFactory_interface(self):
    290         verify.verifyClass(IAuthenticatedPrincipalFactory,
    291                            AuthenticatedApplicantPrincipalFactory
    292                            )
    293         return
    294 
    295     def test_principalFactory_create(self):
    296         factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
    297 
    298         assert factory.info is self.info
    299         assert factory.request is None
    300         return
    301 
    302     def test_principalFactory_call_w_prefix(self):
    303         factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
    304         principal = factory(FakePluggableAuth())
    305 
    306         assert isinstance(principal, ApplicantPrincipal)
    307         self.assertEqual(principal.__repr__(),
    308                          "ApplicantPrincipal('foo.APP-1-1234567890')")
    309         self.assertEqual(principal.id, 'foo.APP-1-1234567890')
    310         return
    311 
    312     def test_principalFactory_call_wo_prefix(self):
    313         factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
    314         fake_auth = FakePluggableAuth()
    315         fake_auth.prefix = None
    316         principal = factory(fake_auth)
    317         self.assertEqual(principal.id, 'APP-1-1234567890')
    318         return
    319 
    320 class PAUSetupTest(FunctionalTestCase):
    321     # Check correct setup of authentication components in the
    322     # applicants subpackage.
    323 
    324     # When a university is created, we want by default have our local
    325     # authentication components (an authenticator plugin and a
    326     # credentials plugin) to be registered with the local PAU. Admins
    327     # can remove these components on the fly later-on if they wish.
     174class FunctionalApplicantAuthTests(ApplicantsFullSetup):
    328175
    329176    layer = FunctionalLayer
    330177
    331178    def setUp(self):
    332         super(PAUSetupTest, self).setUp()
    333 
    334         # Setup a sample site for each test
    335         app = University()
    336         self.dc_root = tempfile.mkdtemp()
    337         app['datacenter'].setStoragePath(self.dc_root)
    338 
    339         # Prepopulate the ZODB...
    340         self.getRootFolder()['app'] = app
    341         self.app = self.getRootFolder()['app']
    342         self.browser = Browser()
    343         self.browser.handleErrors = False
     179        super(FunctionalApplicantAuthTests, self).setUp()
     180        return
    344181
    345182    def tearDown(self):
    346         super(PAUSetupTest, self).tearDown()
    347         shutil.rmtree(self.dc_root)
    348 
    349     def test_extra_auth_plugins_installed(self):
    350         # Check whether the auth plugins defined in here are setup
    351         # automatically when a university is created
    352 
    353         # Get the PAU responsible for the local site ('app')
    354         pau = getUtility(IAuthentication, context=self.app)
    355         cred_plugins = pau.getCredentialsPlugins()
    356         auth_plugins = pau.getAuthenticatorPlugins()
    357         cred_names = [name for name, plugin in cred_plugins]
    358         auth_names = [name for name, plugin in auth_plugins]
    359 
    360         # Make sure our local ApplicantsAuthenticatorPlugin is registered...
    361         self.assertTrue('applicants' in auth_names)
    362         # Make sure our local WAeUPApplicantCredentialsPlugin is registered...
    363         self.assertTrue('applicant_credentials' in cred_names)
     183        super(FunctionalApplicantAuthTests, self).tearDown()
    364184        return
    365 
    366 class FakePAU(object):
    367     credentialsPlugins = ()
    368     authenticatorPlugins = ()
    369 
    370 class ApplicantsAuthenticatorSetupTests(FunctionalTestCase):
    371 
    372     layer = FunctionalLayer
    373 
    374     def test_ifaces(self):
    375         # make sure we fullfill the interface promises
    376         obj = ApplicantsAuthenticatorSetup()
    377         verify.verifyClass(IAuthPluginUtility, ApplicantsAuthenticatorSetup)
    378         verify.verifyObject(IAuthPluginUtility, obj)
    379         return
    380 
    381     def test_utility_available(self):
    382         # we can get an applicant auth utility by lookup
    383         util = queryUtility(IAuthPluginUtility,
    384                             name='applicants_auth_setup')
    385         self.assertTrue(util is not None)
    386         return
    387 
    388     def test_register(self):
    389         # make sure we can register additional components
    390         pau = FakePAU()
    391         result = ApplicantsAuthenticatorSetup().register(pau)
    392         self.assertEqual(
    393             result.credentialsPlugins, ('applicant_credentials',))
    394         self.assertEqual(
    395             result.authenticatorPlugins, ('applicants',))
    396         return
    397 
    398     def test_unregister(self):
    399         # make sure we can unregister applicant auth components
    400         pau = FakePAU()
    401         util = ApplicantsAuthenticatorSetup()
    402         pau = util.register(pau)
    403         result = util.unregister(pau)
    404         self.assertEqual(
    405             result.credentialsPlugins, ())
    406         self.assertEqual(
    407             result.authenticatorPlugins, ())
    408         return
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py

    r7226 r7240  
    3333from waeup.sirp.applicants.container import ApplicantsContainer
    3434from waeup.sirp.applicants.applicant import Applicant
    35 from waeup.sirp.interfaces import IExtFileStore, IFileStoreNameChooser
     35from waeup.sirp.interfaces import (
     36    IExtFileStore, IFileStoreNameChooser, IUserAccount)
    3637from waeup.sirp.university.faculty import Faculty
    3738from waeup.sirp.university.department import Department
     
    6869        setSite(app)
    6970
     71        self.login_path = 'http://localhost/app/login'
    7072        self.root_path = 'http://localhost/app/applicants'
    7173        self.manage_root_path = self.root_path + '/@@manage'
     
    7779        applicantscontainer = ApplicantsContainer()
    7880        applicantscontainer.ac_prefix = 'APP'
     81        applicantscontainer.code = u'app2009'
    7982        applicantscontainer.prefix = 'app'
    8083        applicantscontainer.year = 2009
     
    8487        applicantscontainer.enddate = date.today() + delta
    8588        self.app['applicants']['app2009'] = applicantscontainer
     89        self.applicantscontainer = self.app['applicants']['app2009']
    8690
    8791        # Populate university
     
    112116
    113117        # Add an applicant
    114         self.applicant = Applicant()
    115         self.pin_applicant = unicode(self.pins[1])
    116         self.applicant.access_code = self.pin_applicant
    117         app['applicants']['app2009'][self.pin_applicant] = self.applicant
     118        self.applicant = Applicant(container=applicantscontainer)
     119        app['applicants']['app2009'][
     120            self.applicant.application_number] = self.applicant
     121        IUserAccount(
     122            self.app['applicants']['app2009'][
     123            self.applicant.application_number]).setPassword('apwd')
     124        self.manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
     125            'app2009', self.applicant.application_number, 'manage')
     126        self.edit_path = 'http://localhost/app/applicants/%s/%s/%s' % (
     127            'app2009', self.applicant.application_number, 'edit')
     128        self.view_path = 'http://localhost/app/applicants/%s/%s' % (
     129            'app2009', self.applicant.application_number)
     130
     131    def login(self):
     132        # Perform an applicant login. This creates an applicant record.
     133        #
     134        # This helper also sets `self.applicant`, which is the
     135        # applicant object created.
     136        self.browser.open(self.login_path)
     137        self.browser.getControl(name="form.login").value = self.applicant.applicant_id
     138        self.browser.getControl(name="form.password").value = 'apwd'
     139        self.browser.getControl("Login").click()
     140
     141    def fill_correct_values(self):
     142        # Fill the edit form with suitable values
     143        self.browser.getControl(name="form.firstname").value = 'John'
     144        self.browser.getControl(name="form.lastname").value = 'Tester'
     145        self.browser.getControl(name="form.course1").value = ['CERT1']
     146        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
     147        self.browser.getControl(name="form.lga").value = ['foreigner']
     148        self.browser.getControl(name="form.sex").value = ['m']
     149        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    118150
    119151    def tearDown(self):
     
    253285        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    254286        self.assertEqual(self.browser.url, self.add_applicant_path)
    255         self.browser.getControl(name="form.ac_series").value = self.existing_series
    256         self.browser.getControl(name="form.ac_number").value = self.existing_number
     287        self.browser.getControl(name="form.firstname").value = 'Albert'
     288        self.browser.getControl(name="form.lastname").value = 'Einstein'
     289        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    257290        self.browser.getControl("Create application record").click()
    258291        self.assertTrue('Application initialized' in self.browser.contents)
    259         self.browser.open(self.add_applicant_path)
    260         self.browser.getControl(name="form.ac_series").value = '123'
    261         self.browser.getControl(name="form.ac_number").value = '456'
    262         self.browser.getControl("Create application record").click()
    263         self.assertTrue('is not a valid access code' in self.browser.contents)
    264292        self.browser.open(self.container_manage_path)
    265293        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    266294        ctrl = self.browser.getControl(name='val_id')
    267         ctrl.getControl(value=self.existing_pin).selected = True
     295        value = ctrl.options[0]
     296        ctrl.getControl(value=value).selected = True
    268297        self.browser.getControl("Remove selected", index=0).click()
    269298        self.assertTrue('Successfully removed:' in self.browser.contents)
    270299        self.browser.open(self.add_applicant_path)
    271         existing_pin = self.pins[2]
    272         parts = existing_pin.split('-')[1:]
    273         existing_series, existing_number = parts
    274         self.browser.getControl(name="form.ac_series").value = existing_series
    275         self.browser.getControl(name="form.ac_number").value = existing_number
     300        self.browser.getControl(name="form.firstname").value = 'Albert'
     301        self.browser.getControl(name="form.lastname").value = 'Einstein'
     302        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    276303        self.browser.getControl("Create application record").click()
    277304        self.assertTrue('Application initialized' in self.browser.contents)
    278         self.browser.open(self.container_manage_path)
    279         self.assertTrue(existing_pin in self.browser.contents)
    280         del self.app['applicants']['app2009'][existing_pin]
    281         ctrl = self.browser.getControl(name='val_id')
    282         ctrl.getControl(value=existing_pin).selected = True
    283         self.browser.getControl("Remove selected", index=0).click()
    284         self.assertMatches('...Could not delete...', self.browser.contents)
    285305        return
    286306
     
    288308        # Managers can manage applicants
    289309        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    290         self.applicant_path = 'http://localhost/app/applicants/app2009/%s' % self.pin_applicant
    291         self.applicant_view_path = self.applicant_path + '/index'
    292         self.applicant_manage_path = self.applicant_path + '/manage'
    293         self.applicant_slip_path = self.applicant_path + '/application_slip.pdf'
    294         self.browser.open(self.applicant_manage_path)
    295         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    296         self.browser.getControl(name="form.email").value = 'abc'
    297         self.browser.getControl("Save").click()
    298         self.assertMatches('...Invalid email address...', self.browser.contents)
    299         self.browser.getControl(name="form.email").value = 'abc@def.gh'
    300         self.browser.getControl(name="form.firstname").value = 'John'
    301         self.browser.getControl(name="form.lastname").value = 'Tester'
    302         self.browser.getControl(name="form.course1").value = ['CERT1']
    303         self.browser.getControl(name="form.course_admitted").value = ['CERT1']
    304         self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
    305         self.browser.getControl(name="form.lga").value = ['foreigner']
    306         self.browser.getControl(name="form.sex").value = ['m']
     310        self.slip_path = self.view_path + '/application_slip.pdf'
     311        self.browser.open(self.manage_path)
     312        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     313        self.fill_correct_values()
    307314        self.browser.getControl(name="transition").value = ['start']
    308315        self.browser.getControl("Save").click()
    309316        self.assertMatches('...Form has been saved...', self.browser.contents)
    310317        self.assertMatches('...Application started by zope.mgr...', self.browser.contents)
    311         self.browser.open(self.applicant_view_path)
    312         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    313         self.browser.open(self.applicant_slip_path)
     318        self.browser.open(self.view_path)
     319        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     320        self.browser.open(self.slip_path)
    314321        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    315322        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
    316         self.browser.open(self.applicant_manage_path)
     323        self.browser.open(self.manage_path)
    317324        self.browser.getControl(name="form.course_admitted").value = []
    318325        self.browser.getControl("Save").click()
    319         self.browser.open(self.applicant_slip_path)
     326        self.browser.open(self.slip_path)
    320327        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    321328        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
    322         return
    323 
    324     def test_view_applicant(self):
    325         # Applicants can login and view their application
    326         self.login_path = 'http://localhost/app/applicants/app2009/login'
    327         self.browser.open(self.login_path)
    328         pin = self.pins[2]
    329         parts = pin.split('-')[1:]
    330         existing_series, existing_number = parts
    331         ac_series = self.browser.getControl(name="form.ac_series")
    332         ac_series.value = existing_series
    333         ac_number = self.browser.getControl(name="form.ac_number")
    334         ac_number.value = existing_number
    335         self.browser.getControl(name="SUBMIT").click()
    336         self.assertTrue(self.browser.url != self.login_path)
    337         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    338329        return
    339330
    340331    def test_passport_edit_view(self):
    341332        # We get a default image after login
    342         login_path = 'http://localhost/app/applicants/app2009/login'
    343         self.browser.open(login_path)
    344         pin = self.pins[2]
    345         parts = pin.split('-')[1:]
    346         existing_series, existing_number = parts
    347         ac_series = self.browser.getControl(name="form.ac_series")
    348         ac_series.value = existing_series
    349         ac_number = self.browser.getControl(name="form.ac_number")
    350         ac_number.value = existing_number
    351         self.browser.getControl(name="SUBMIT").click()
    352         pin = self.pins[2]
    353         #appl = self.getRootFolder()['app']['applicants']['app2009']
    354         #appl = appl[pin]
    355         #passp = appl.passport
    356         #passp_len = len(passp.file.read())
    357         #self.assertEqual(passp_len, PH_LEN)
    358         #image_url = "%s/%s" % (self.browser.url, 'placeholder.jpg')
    359         image_url = "%s/%s" % (self.browser.url, 'passport.jpg')
    360         #self.browser.open(image_url)
    361         self.browser.open('passport.jpg')
     333        self.browser.open(self.login_path)
     334        self.login()
     335        self.browser.open(self.browser.url + '/passport.jpg')
    362336        self.assertEqual(self.browser.headers['status'], '200 Ok')
    363337        self.assertEqual(self.browser.headers['content-type'], 'image/jpeg')
     
    366340            self.browser.headers['content-length'], str(PH_LEN))
    367341
    368 
    369342    def test_edit_applicant(self):
    370343        # Applicants can edit their record
    371         self.login_path = 'http://localhost/app/applicants/app2009/login'
    372344        self.browser.open(self.login_path)
    373         pin = self.pins[2]
    374         parts = pin.split('-')[1:]
    375         existing_series, existing_number = parts
    376         ac_series = self.browser.getControl(name="form.ac_series")
    377         ac_series.value = existing_series
    378         ac_number = self.browser.getControl(name="form.ac_number")
    379         ac_number.value = existing_number
    380         self.browser.getControl(name="SUBMIT").click()
     345        self.login()
     346        self.browser.open(self.edit_path)
    381347        self.assertTrue(self.browser.url != self.login_path)
    382348        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    383         self.browser.getControl(name="form.firstname").value = 'John'
    384         self.browser.getControl(name="form.lastname").value = 'Tester'
    385         self.browser.getControl(name="form.course1").value = ['CERT1']
    386         self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
    387         self.browser.getControl(name="form.lga").value = ['foreigner']
    388         self.browser.getControl(name="form.sex").value = ['m']
     349        self.fill_correct_values()
    389350        self.browser.getControl("Save").click()
    390351        self.assertMatches('...Form has been saved...', self.browser.contents)
     
    421382        return
    422383
    423     # We have no local roles yet
    424     #def test_local_roles_add_delete(self):
    425     #    # Managers can assign and delete local roles of applicants containers
    426     #    myusers = self.app['users']
    427     #    myusers.addUser('bob', 'bobssecret')
    428     #    self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    429     #    self.browser.open(self.manage_container_path)
    430     #    self.browser.getControl(name="user").value = ['bob']
    431     #    self.browser.getControl(name="local_role").value = [
    432     #        'waeup.local.ApplicationsOfficer']
    433     #    self.browser.getControl("Add local role").click()
    434     #    self.assertTrue('<td>bob</td>' in self.browser.contents)
    435     #    ctrl = self.browser.getControl(name='role_id')
    436     #    ctrl.getControl(value='bob|waeup.ApplicationsOfficer').selected = True
    437     #    self.browser.getControl("Remove selected local roles").click()
    438     #    self.assertTrue('Successfully removed:' in self.browser.contents)
    439     #    self.assertFalse('<td>bob</td>' in self.browser.contents)
    440     #    return
    441 
    442 class LoginTest(FunctionalTestCase):
    443     # Here we check login view of applicants containers.
    444     #
    445     # Tests in here do only cover login attempts without any PINs
    446     # created before.
    447 
    448     layer = FunctionalLayer
    449 
    450     def setUp(self):
    451         super(LoginTest, self).setUp()
    452 
    453         # Setup a sample site for each test
    454         app = University()
    455         self.dc_root = tempfile.mkdtemp()
    456         app['datacenter'].setStoragePath(self.dc_root)
    457         self.login_path = 'http://localhost/app/applicants/testapplicants/login'
    458 
    459         # Add an applicants container where we can login (or not)
    460         applicantscontainer = ApplicantsContainer()
    461         applicantscontainer.ac_prefix = 'APP'
    462         delta = timedelta(days=10)
    463         applicantscontainer.startdate = date.today() - delta
    464         applicantscontainer.enddate = date.today() + delta
    465         app['applicants']['testapplicants'] = applicantscontainer
    466 
    467         # Put the prepopulated site into test ZODB and prepare test
    468         # browser
    469         self.getRootFolder()['app'] = app
    470         self.browser = Browser()
    471         self.browser.handleErrors = False
    472 
    473     def tearDown(self):
    474         super(LoginTest, self).tearDown()
    475         shutil.rmtree(self.dc_root)
    476 
    477     def test_anonymous_access(self):
    478         # Anonymous users can access a login page
    479         self.browser.open(self.login_path)
    480         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    481         return
    482 
    483     def test_anonymous_invalid_creds(self):
    484         # Anonymous users giving invalid credentials stay at the page
    485         self.browser.open(self.login_path)
    486         # We do not give credentials but send the form as-is
    487         submit = self.browser.getControl(name='SUBMIT')
    488         submit.click()
    489         # We are still at the same page...
    490         self.assertEqual(self.browser.url, self.login_path)
    491         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    492         return
    493 
    494     def test_anonymous_invalid_creds_warning(self):
    495         # Entering wrong credentials will yield a warning
    496         self.browser.open(self.login_path)
    497         # We do not give credentials but send the form as-is
    498         submit = self.browser.getControl(name='SUBMIT')
    499         submit.click()
    500         self.assertTrue(
    501             'Entered credentials are invalid' in self.browser.contents)
    502         return
    503 
    504     def test_manager_no_warnings(self):
    505         # Browsing the login screen as a manager, won't raise warnings
    506         # Authenticate ourself as manager
    507         self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    508         self.browser.open(self.login_path)
    509         # Submit the form w/o any credentials
    510         self.browser.getControl(name="SUBMIT").click()
    511         self.assertTrue(
    512             'Entered credentials are invalid' not in self.browser.contents)
    513         return
    514 
    515     def test_manager_no_redirect(self):
    516         # Browsing the login screen as a manager won't trigger a redirect
    517         # Authenticate ourself as manager
    518         self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    519         self.browser.open(self.login_path)
    520         # Submit the form w/o any credentials
    521         self.browser.getControl(name="SUBMIT").click()
    522         self.assertEqual(self.browser.url, self.login_path)
    523         return
    524 
    525     def test_display_entered_values(self):
    526         # After submit the entered values are displayed in the form
    527         self.browser.open(self.login_path)
    528         # Enter some value we can look for after submit
    529         ac_series = self.browser.getControl(name="form.ac_series")
    530         ac_series.value = '666'
    531         self.browser.getControl(name="SUBMIT").click()
    532         self.assertTrue('666' in self.browser.contents)
    533         return
    534 
    535 class LoginTestWithPINs(LoginTest):
    536     # Here we check login view of applicants containers with PINs provided.
    537 
    538     # As setting up pins is time-consuming we only set them up when
    539     # really needed (i.e. in this separate TestCase).
    540 
    541     layer = FunctionalLayer
    542 
    543     def setUp(self):
    544         super(LoginTestWithPINs, self).setUp()
    545 
    546         # Create 5 access codes with prefix'FOO' and cost 9.99 each
    547         pin_container = self.getRootFolder()['app']['accesscodes']
    548         pin_container.createBatch(
    549             datetime.now(), 'some_userid', 'APP', 9.99, 5)
    550         pins = pin_container[pin_container.keys()[0]].values()
    551         self.pins = [x.representation for x in pins]
    552         self.existing_pin = self.pins[0]
    553         parts = self.existing_pin.split('-')[1:]
    554         self.existing_series, self.existing_number = parts
    555         self.browser.handleErrors = False
    556 
    557     def tearDown(self):
    558         super(LoginTestWithPINs, self).tearDown()
    559 
    560     def test_anonymous_valid_login(self):
    561         # If we enter valid credentials, we get to the applicants form
    562         self.browser.open(self.login_path)
    563         # Enter some value we can look for after submit
    564         ac_series = self.browser.getControl(name="form.ac_series")
    565         ac_series.value = self.existing_series
    566         ac_number = self.browser.getControl(name="form.ac_number")
    567         ac_number.value = self.existing_number
    568         self.browser.getControl(name="SUBMIT").click()
    569         # We should be redirected to applicants form.
    570         self.assertTrue(self.browser.url != self.login_path)
    571         # Applicants see their Access Code in the contact form
    572         #self.browser.getLink("Contact").click()
    573         #self.assertTrue(
    574         #    'Access Code:' in self.browser.contents)
    575         return
    576 
    577     def test_anonymous_invalid_login(self):
    578         # If we enter wrong credentials we won't get far
    579         self.browser.open(self.login_path)
    580         # Enter some value we can look for after submit
    581         ac_series = self.browser.getControl(name="form.ac_series")
    582         ac_series.value = 'illegal series'
    583         ac_number = self.browser.getControl(name="form.ac_number")
    584         ac_number.value = 'invalid number'
    585         self.browser.getControl(name="SUBMIT").click()
    586         # We get a warning message
    587         self.assertTrue(
    588             'Entered credentials are invalid' in self.browser.contents)
    589         # We stay at the login page (no redirect)
    590         self.assertTrue(self.browser.url == self.login_path)
    591         return
    592 
    593384class ApplicantsPassportTests(ApplicantsFullSetup):
    594385    # Tests for uploading/browsing the passport image of appplicants
    595386
    596387    layer = FunctionalLayer
    597 
    598     def setUp(self):
    599         super(ApplicantsPassportTests, self).setUp()
    600         self.login_path = 'http://localhost/app/applicants/app2009/login'
    601         self.pin = self.pins[2]
    602         self.existing_series, self.existing_number = self.pin.split('-')[1:]
    603         self.edit_path = 'http://localhost/app/applicants/app2009/%s/edit' % (
    604             self.pin)
    605         self.manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
    606             'app2009', self.pin, 'manage')
    607 
    608     def tearDown(self):
    609         super(ApplicantsPassportTests, self).tearDown()
    610 
    611     def login(self):
    612         # Perform an applicant login. This creates an applicant record.
    613         #
    614         # This helper also sets `self.applicant`, which is the
    615         # applicant object created.
    616         self.browser.open(self.login_path)
    617         ac_series = self.browser.getControl(name="form.ac_series")
    618         ac_series.value = self.existing_series
    619         ac_number = self.browser.getControl(name="form.ac_number")
    620         ac_number.value = self.existing_number
    621         self.browser.getControl(name="SUBMIT").click()
    622         self.applicant = self.app['applicants']['app2009'][self.pin]
    623 
    624     def fill_correct_values(self):
    625         # Fill the edit form with suitable values
    626         self.browser.getControl(name="form.firstname").value = 'John'
    627         self.browser.getControl(name="form.lastname").value = 'Tester'
    628         self.browser.getControl(name="form.course1").value = ['CERT1']
    629         self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
    630         self.browser.getControl(name="form.lga").value = ['foreigner']
    631         self.browser.getControl(name="form.sex").value = ['m']
    632388
    633389    def image_url(self, filename):
     
    638394        #import pdb; pdb.set_trace()
    639395        self.login()
    640         self.assertEqual(self.browser.url, self.edit_path)
     396        self.assertEqual(self.browser.url, self.view_path)
     397        self.browser.open(self.edit_path)
     398        # There is a correct <img> link included
     399        self.assertTrue(
     400              '<img src="passport.jpg" />' in self.browser.contents)
     401        # Browsing the link shows a real image
     402        self.browser.open(self.image_url('passport.jpg'))
     403        self.assertEqual(
     404            self.browser.headers['content-type'], 'image/jpeg')
     405        self.assertEqual(len(self.browser.contents), PH_LEN)
     406
     407    def test_after_submit_default_browsable(self):
     408        # After submitting an applicant form the default image is
     409        # still visible
     410        self.login()
     411        self.browser.open(self.edit_path)
     412        self.browser.getControl("Save").click() # submit form
    641413        # There is a correct <img> link included
    642414        self.assertTrue(
     
    648420        self.assertEqual(len(self.browser.contents), PH_LEN)
    649421
    650     def test_after_submit_default_browsable(self):
    651         # After submitting an applicant form the default image is
    652         # still visible
    653         self.login()
    654         self.browser.getControl("Save").click() # submit form
    655         # There is a correct <img> link included
    656         self.assertTrue(
    657             '<img src="passport.jpg" />' in self.browser.contents)
    658         # Browsing the link shows a real image
    659         self.browser.open(self.image_url('passport.jpg'))
    660         self.assertEqual(
    661             self.browser.headers['content-type'], 'image/jpeg')
    662         self.assertEqual(len(self.browser.contents), PH_LEN)
    663 
    664422    def test_uploaded_image_respects_file_size_restriction(self):
    665423        # When we upload an image that is too big ( > 10 KB) we will
    666424        # get an error message
    667425        self.login()
     426        self.browser.open(self.edit_path)
    668427        # Create a pseudo image file and select it to be uploaded in form
    669428        photo_content = 'A' * 1024 * 21  # A string of 21 KB size
     
    694453        # even if there are still errors in the form
    695454        self.login()
     455        self.browser.open(self.edit_path)
    696456        # Create a pseudo image file and select it to be uploaded in form
    697457        photo_content = 'I pretend to be a graphics file'
     
    714474        # stored in an imagestorage
    715475        self.login()
     476        self.browser.open(self.edit_path)
    716477        # Create a pseudo image file and select it to be uploaded in form
    717478        pseudo_image = StringIO('I pretend to be a graphics file')
     
    731492        # if there are no errors in form
    732493        self.login()
     494        self.browser.open(self.edit_path)
    733495        self.fill_correct_values() # fill other fields with correct values
    734496        # Create a pseudo image file and select it to be uploaded in form
     
    751513        # stored in an imagestorage if form contains no errors
    752514        self.login()
     515        self.browser.open(self.edit_path)
    753516        self.fill_correct_values() # fill other fields with correct values
    754517        # Create a pseudo image file and select it to be uploaded in form
     
    768531        # Make sure uploaded images do really differ if we eject a
    769532        # change notfication (and do not if we don't)
    770         self.login() # Create applicant form
     533        self.login()
     534        self.browser.open(self.edit_path)
    771535        self.fill_correct_values() # fill other fields with correct values
    772536        self.browser.getControl("Save").click() # submit form
     
    795559        # Make sure that a correctly filled form with passport picture
    796560        # can be submitted
    797         self.login() # Create applicant form
     561        self.login()
     562        self.browser.getLink("Edit application record").click()
    798563        self.fill_correct_values() # fill other fields with correct values
    799564        # Create a pseudo image file and select it to be uploaded in form
     
    814579        # Now do the whole thing again but with correct state
    815580        self.login()
     581        self.browser.open(self.edit_path)
    816582        self.fill_correct_values()
    817583        pseudo_image = StringIO('I pretend to be a graphics file')
     
    836602    def test_locking(self):
    837603        # Make sure that locked forms can't be submitted
    838         self.login() # Create applicant form
     604        self.login()
     605        self.browser.open(self.edit_path)
    839606        self.fill_correct_values() # fill other fields with correct values
    840607        # Create a pseudo image file and select it to be uploaded in form
     
    844611        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
    845612        self.browser.getControl("Save").click()
    846         self.browser.getLink("Logout").click()
    847 
    848         # Login as manager and lock the form
    849         self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    850         self.browser.open(self.manage_path)
    851         self.browser.getControl(name="form.locked").value = True
    852         self.browser.getControl("Save").click()
    853         self.browser.getLink("Logout").click()
    854 
    855         # Login as applicant again and try to open the edit form
    856         self.login()
     613        # Now we lock the form
     614        self.applicant.locked = True
    857615        self.browser.open(self.edit_path)
    858616        self.assertEqual(self.browser.headers['Status'], '200 Ok')
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_catalog.py

    r7193 r7240  
    5858        # Create an applicant in an applicants container
    5959        self.container = ApplicantsContainer()
     60        self.container.code = u"mystuff"
    6061        setSite(self.app)
    6162        self.app['applicants']['mystuff'] = self.container
    62         self.applicant = Applicant()
    63         self.ac = u'FOO-666-123456789'
    64         self.applicant.access_code = self.ac
    65         self.app['applicants']['mystuff'][self.ac] = self.applicant
     63        self.applicant = Applicant(container=self.container)
     64        self.app['applicants']['mystuff'][
     65            self.applicant.application_number] = self.applicant
    6666        return
    6767
     
    8080        self.create_applicant()
    8181        q = getUtility(IQuery)
    82         subquery = Eq(('applicants_catalog', 'access_code'), self.ac)
     82        subquery = Eq(('applicants_catalog', 'applicant_id'),
     83            self.applicant.applicant_id)
    8384        results = list(q.searchResults(subquery))
    8485        self.assertEqual(len(results), 1)
    85 
    8686        result_applicant = results[0]
    8787        self.assertTrue(isinstance(result_applicant, Applicant))
    88         self.assertEqual(result_applicant.access_code, self.ac)
     88        self.assertEqual(result_applicant.applicant_id, self.applicant.applicant_id)
Note: See TracChangeset for help on using the changeset viewer.