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

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

Use prefix for SIRPPrincipal initialization.

I don't know exactly what the authentication prefix is, but it might be important?!

  • Property svn:keywords set to Id
File size: 11.8 KB
RevLine 
[7193]1## $Id: authentication.py 7239 2011-11-29 07:09:05Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[4073]18"""Authentication for WAeUP portals.
19"""
20import grok
[7169]21from zope.event import notify
[5900]22from zope.component import getUtility, getUtilitiesFor
[7169]23from zope.interface import Interface
24from zope.securitypolicy.interfaces import (
25    IPrincipalRoleMap, IPrincipalRoleManager)
[7233]26from zope.pluggableauth.factories import Principal
[6610]27from zope.pluggableauth.plugins.session import SessionCredentialsPlugin
28from zope.pluggableauth.interfaces import (
[7233]29        ICredentialsPlugin, IAuthenticatorPlugin,
30        IAuthenticatedPrincipalFactory, AuthenticatedPrincipalCreated)
31from zope.publisher.interfaces import IRequest
[6610]32from zope.password.interfaces import IPasswordManager
[4129]33from zope.securitypolicy.principalrole import principalRoleManager
[7169]34from waeup.sirp.interfaces import (ILocalRoleSetEvent,
[7233]35    IUserAccount, IAuthPluginUtility, IPasswordValidator,
36    ISIRPPrincipal, ISIRPPrincipalInfo)
[4073]37
38def setup_authentication(pau):
39    """Set up plugguble authentication utility.
40
41    Sets up an IAuthenticatorPlugin and
42    ICredentialsPlugin (for the authentication mechanism)
[5900]43
44    Then looks for any external utilities that want to modify the PAU.
[4073]45    """
[6661]46    pau.credentialsPlugins = ('No Challenge if Authenticated', 'credentials')
[6672]47    pau.authenticatorPlugins = ('users',)
[4073]48
[5900]49    # Give any third-party code and subpackages a chance to modify the PAU
50    auth_plugin_utilities = getUtilitiesFor(IAuthPluginUtility)
[5901]51    for name, util in auth_plugin_utilities:
[5900]52        util.register(pau)
53
[6673]54def get_principal_role_manager():
55    """Get a role manager for principals.
56
57    If we are currently 'in a site', return the role manager for the
58    portal or the global rolemanager else.
59    """
60    portal = grok.getSite()
61    if portal is not None:
62        return IPrincipalRoleManager(portal)
63    return principalRoleManager
64
[4073]65class WAeUPSessionCredentialsPlugin(grok.GlobalUtility,
66                                    SessionCredentialsPlugin):
67    grok.provides(ICredentialsPlugin)
68    grok.name('credentials')
69
70    loginpagename = 'login'
71    loginfield = 'form.login'
72    passwordfield = 'form.password'
73
[7233]74class SIRPPrincipalInfo(object):
75    """An implementation of ISIRPPrincipalInfo.
[4073]76
[7233]77    A SIRP principal info is created with id, login, title, description,
78    phone and email.
79    """
80    grok.implements(ISIRPPrincipalInfo)
81
82    def __init__(self, id, title, description, email, phone):
[4073]83        self.id = id
84        self.title = title
85        self.description = description
[7233]86        self.email = email
87        self.phone = phone
[4073]88        self.credentialsPlugin = None
89        self.authenticatorPlugin = None
90
[7233]91class SIRPPrincipal(Principal):
92    """A portal principal.
93
94    SIRP principals provide an extra `email` and `phone`
95    attribute extending ordinary principals.
96    """
97
98    grok.implements(ISIRPPrincipal)
99
100    def __init__(self, id, title=u'', description=u'', email=u'',
101                 phone=None, prefix=None):
102        self.id = id
[7239]103        if prefix is not None:
104            self.id = '%s.%s' % (prefix, self.id)
[7233]105        self.title = title
106        self.description = description
107        self.groups = []
108        self.email = email
109        self.phone = phone
110
111    def __repr__(self):
112        return 'SIRPPrincipal(%r)' % self.id
113
114class AuthenticatedSIRPPrincipalFactory(grok.MultiAdapter):
115    """Creates 'authenticated' SIRP principals.
116
117    Adapts (principal info, request) to a SIRPPrincipal instance.
118
119    This adapter is used by the standard PAU to transform
[7236]120    SIRPPrincipalInfos into SIRPPrincipal instances.
[7233]121    """
122    grok.adapts(ISIRPPrincipalInfo, IRequest)
123    grok.implements(IAuthenticatedPrincipalFactory)
124
125    def __init__(self, info, request):
126        self.info = info
127        self.request = request
128
129    def __call__(self, authentication):
130        principal = SIRPPrincipal(
131            self.info.id,
132            self.info.title,
133            self.info.description,
134            self.info.email,
135            self.info.phone,
[7239]136            authentication.prefix,
[7233]137            )
138        notify(
139            AuthenticatedPrincipalCreated(
140                authentication, principal, self.info, self.request))
141        return principal
142
[4073]143class Account(grok.Model):
[4109]144    grok.implements(IUserAccount)
[4129]145
[6180]146    _local_roles = dict()
147
[4125]148    def __init__(self, name, password, title=None, description=None,
[7233]149                 email=None, phone=None, roles = []):
[4073]150        self.name = name
[4087]151        if title is None:
152            title = name
[7197]153        #if description is None:
154        #    description = title
[4087]155        self.title = title
156        self.description = description
[7221]157        self.email = email
[7233]158        self.phone = phone
[4073]159        self.setPassword(password)
[7177]160        #self.setSiteRolesForPrincipal(roles)
[6180]161        # We don't want to share this dict with other accounts
162        self._local_roles = dict()
[4073]163
164    def setPassword(self, password):
165        passwordmanager = getUtility(IPasswordManager, 'SHA1')
166        self.password = passwordmanager.encodePassword(password)
167
168    def checkPassword(self, password):
169        passwordmanager = getUtility(IPasswordManager, 'SHA1')
170        return passwordmanager.checkPassword(self.password, password)
171
[7177]172    def getSiteRolesForPrincipal(self):
[6673]173        prm = get_principal_role_manager()
[4129]174        roles = [x[0] for x in prm.getRolesForPrincipal(self.name)
175                 if x[0].startswith('waeup.')]
176        return roles
[5055]177
[7177]178    def setSiteRolesForPrincipal(self, roles):
[6673]179        prm = get_principal_role_manager()
[7177]180        old_roles = self.getSiteRolesForPrincipal()
[4129]181        for role in old_roles:
182            # Remove old roles, not to be set now...
183            if role.startswith('waeup.') and role not in roles:
184                prm.unsetRoleForPrincipal(role, self.name)
185        for role in roles:
186            prm.assignRoleToPrincipal(role, self.name)
187
[7177]188    roles = property(getSiteRolesForPrincipal, setSiteRolesForPrincipal)
[4129]189
[6180]190    def getLocalRoles(self):
191        return self._local_roles
192
193    def notifyLocalRoleChanged(self, obj, role_id, granted=True):
194        objects = self._local_roles.get(role_id, [])
195        if granted and obj not in objects:
196            objects.append(obj)
197        if not granted and obj in objects:
198            objects.remove(obj)
199        self._local_roles[role_id] = objects
200        if len(objects) == 0:
201            del self._local_roles[role_id]
[6182]202        self._p_changed = True
[6180]203        return
204
[4073]205class UserAuthenticatorPlugin(grok.GlobalUtility):
[6625]206    grok.implements(IAuthenticatorPlugin)
[4073]207    grok.provides(IAuthenticatorPlugin)
208    grok.name('users')
209
210    def authenticateCredentials(self, credentials):
211        if not isinstance(credentials, dict):
212            return None
213        if not ('login' in credentials and 'password' in credentials):
214            return None
215        account = self.getAccount(credentials['login'])
216
217        if account is None:
218            return None
219        if not account.checkPassword(credentials['password']):
220            return None
[7233]221        return SIRPPrincipalInfo(id=account.name,
[4612]222                             title=account.title,
[7233]223                             description=account.description,
224                             email=account.email,
225                             phone=account.phone)
[4073]226
227    def principalInfo(self, id):
228        account = self.getAccount(id)
229        if account is None:
230            return None
[7233]231        return SIRPPrincipalInfo(id=account.name,
[4612]232                             title=account.title,
[7233]233                             description=account.description,
234                             email=account.email,
235                             phone=account.phone)
[4073]236
237    def getAccount(self, login):
[4087]238        # ... look up the account object and return it ...
[7172]239        userscontainer = self.getUsersContainer()
240        if userscontainer is None:
[4087]241            return
[7172]242        return userscontainer.get(login, None)
[4087]243
244    def addAccount(self, account):
[7172]245        userscontainer = self.getUsersContainer()
246        if userscontainer is None:
[4087]247            return
248        # XXX: complain if name already exists...
[7172]249        userscontainer.addAccount(account)
[4087]250
[4091]251    def addUser(self, name, password, title=None, description=None):
[7172]252        userscontainer = self.getUsersContainer()
253        if userscontainer is None:
[4091]254            return
[7172]255        userscontainer.addUser(name, password, title, description)
[5055]256
[7172]257    def getUsersContainer(self):
[4087]258        site = grok.getSite()
[4743]259        return site['users']
[6203]260
[7147]261class PasswordValidator(grok.GlobalUtility):
262
263  grok.implements(IPasswordValidator)
264
265  def validate_password(self, pw, pw_repeat):
266       errors = []
267       if len(pw) < 3:
268         errors.append('Password must have at least 3 chars.')
269       if pw != pw_repeat:
270         errors.append('Passwords do not match.')
271       return errors
272
[7169]273class LocalRoleSetEvent(object):
274
275    grok.implements(ILocalRoleSetEvent)
276
277    def __init__(self, object, role_id, principal_id, granted=True):
278        self.object = object
279        self.role_id = role_id
280        self.principal_id = principal_id
281        self.granted = granted
282
[6203]283@grok.subscribe(IUserAccount, grok.IObjectRemovedEvent)
[6839]284def handle_account_removed(account, event):
[6203]285    """When an account is removed, local roles might have to be deleted.
286    """
287    local_roles = account.getLocalRoles()
288    principal = account.name
289    for role_id, object_list in local_roles.items():
290        for object in object_list:
291            try:
292                role_manager = IPrincipalRoleManager(object)
293            except TypeError:
294                # No role manager, no roles to remove
295                continue
296            role_manager.unsetRoleForPrincipal(role_id, principal)
297    return
[7169]298
299@grok.subscribe(IUserAccount, grok.IObjectAddedEvent)
300def handle_account_added(account, event):
301    """When an account is added, the local owner role and the global
[7185]302    AcademicsOfficer role must be set.
[7169]303    """
[7173]304    # We set the local Owner role
305    role_manager_account = IPrincipalRoleManager(account)
306    role_manager_account.assignRoleToPrincipal(
[7169]307        'waeup.local.Owner', account.name)
[7185]308    # We set the global AcademicsOfficer role
[7173]309    site = grok.getSite()
310    role_manager_site = IPrincipalRoleManager(site)
311    role_manager_site.assignRoleToPrincipal(
[7185]312        'waeup.AcademicsOfficer', account.name)
[7173]313    # Finally we have to notify the user account that the local role
[7169]314    # of the same object has changed
315    notify(LocalRoleSetEvent(
316        account, 'waeup.local.Owner', account.name, granted=True))
317    return
318
319@grok.subscribe(Interface, ILocalRoleSetEvent)
320def handle_local_role_changed(obj, event):
321    site = grok.getSite()
322    if site is None:
323        return
324    users = site.get('users', None)
325    if users is None:
326        return
327    role_id = event.role_id
328    if event.principal_id not in users.keys():
329        return
330    user = users[event.principal_id]
331    user.notifyLocalRoleChanged(event.object, event.role_id, event.granted)
332    return
333
334@grok.subscribe(Interface, grok.IObjectRemovedEvent)
335def handle_local_roles_on_obj_removed(obj, event):
336    try:
337        role_map = IPrincipalRoleMap(obj)
338    except TypeError:
339        # no map, no roles to remove
340        return
341    for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
342        notify(LocalRoleSetEvent(
343                obj, local_role, user_name, granted=False))
344    return
Note: See TracBrowser for help on using the repository browser.