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

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

Fix role assignment when creating an account.

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