source: main/waeup.sirp/branches/henrik-bootstrap/src/waeup/sirp/authentication.py @ 11110

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

Replace the term 'WAeUP' by SIRP which is a WAeUP product.

  • Property svn:keywords set to Id
File size: 11.9 KB
Line 
1## $Id: authentication.py 7321 2011-12-10 06:15:17Z 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, user_type=None, roles = []):
153        self.name = name
154        if title is None:
155            title = name
156        #if description is None:
157        #    description = title
158        self.title = title
159        self.description = description
160        self.email = email
161        self.phone = phone
162        self.user_type = user_type
163        self.setPassword(password)
164        #self.setSiteRolesForPrincipal(roles)
165        # We don't want to share this dict with other accounts
166        self._local_roles = dict()
167
168    def setPassword(self, password):
169        passwordmanager = getUtility(IPasswordManager, 'SHA1')
170        self.password = passwordmanager.encodePassword(password)
171
172    def checkPassword(self, password):
173        passwordmanager = getUtility(IPasswordManager, 'SHA1')
174        return passwordmanager.checkPassword(self.password, password)
175
176    def getSiteRolesForPrincipal(self):
177        prm = get_principal_role_manager()
178        roles = [x[0] for x in prm.getRolesForPrincipal(self.name)
179                 if x[0].startswith('waeup.')]
180        return roles
181
182    def setSiteRolesForPrincipal(self, roles):
183        prm = get_principal_role_manager()
184        old_roles = self.getSiteRolesForPrincipal()
185        for role in old_roles:
186            # Remove old roles, not to be set now...
187            if role.startswith('waeup.') and role not in roles:
188                prm.unsetRoleForPrincipal(role, self.name)
189        for role in roles:
190            prm.assignRoleToPrincipal(role, self.name)
191
192    roles = property(getSiteRolesForPrincipal, setSiteRolesForPrincipal)
193
194    def getLocalRoles(self):
195        return self._local_roles
196
197    def notifyLocalRoleChanged(self, obj, role_id, granted=True):
198        objects = self._local_roles.get(role_id, [])
199        if granted and obj not in objects:
200            objects.append(obj)
201        if not granted and obj in objects:
202            objects.remove(obj)
203        self._local_roles[role_id] = objects
204        if len(objects) == 0:
205            del self._local_roles[role_id]
206        self._p_changed = True
207        return
208
209class UserAuthenticatorPlugin(grok.GlobalUtility):
210    grok.implements(IAuthenticatorPlugin)
211    grok.provides(IAuthenticatorPlugin)
212    grok.name('users')
213
214    def authenticateCredentials(self, credentials):
215        if not isinstance(credentials, dict):
216            return None
217        if not ('login' in credentials and 'password' in credentials):
218            return None
219        account = self.getAccount(credentials['login'])
220        if account is None:
221            return None
222        if not account.checkPassword(credentials['password']):
223            return None
224        return SIRPPrincipalInfo(
225            id=account.name,
226            title=account.title,
227            description=account.description,
228            email=account.email,
229            phone=account.phone,
230            user_type=u'user')
231
232    def principalInfo(self, id):
233        account = self.getAccount(id)
234        if account is None:
235            return None
236        return SIRPPrincipalInfo(
237            id=account.name,
238            title=account.title,
239            description=account.description,
240            email=account.email,
241            phone=account.phone,
242            user_type=u'user')
243
244    def getAccount(self, login):
245        # ... look up the account object and return it ...
246        userscontainer = self.getUsersContainer()
247        if userscontainer is None:
248            return
249        return userscontainer.get(login, None)
250
251    def addAccount(self, account):
252        userscontainer = self.getUsersContainer()
253        if userscontainer is None:
254            return
255        # XXX: complain if name already exists...
256        userscontainer.addAccount(account)
257
258    def addUser(self, name, password, title=None, description=None):
259        userscontainer = self.getUsersContainer()
260        if userscontainer is None:
261            return
262        userscontainer.addUser(name, password, title, description)
263
264    def getUsersContainer(self):
265        site = grok.getSite()
266        return site['users']
267
268class PasswordValidator(grok.GlobalUtility):
269
270  grok.implements(IPasswordValidator)
271
272  def validate_password(self, pw, pw_repeat):
273       errors = []
274       if len(pw) < 3:
275         errors.append('Password must have at least 3 chars.')
276       if pw != pw_repeat:
277         errors.append('Passwords do not match.')
278       return errors
279
280class LocalRoleSetEvent(object):
281
282    grok.implements(ILocalRoleSetEvent)
283
284    def __init__(self, object, role_id, principal_id, granted=True):
285        self.object = object
286        self.role_id = role_id
287        self.principal_id = principal_id
288        self.granted = granted
289
290@grok.subscribe(IUserAccount, grok.IObjectRemovedEvent)
291def handle_account_removed(account, event):
292    """When an account is removed, local roles might have to be deleted.
293    """
294    local_roles = account.getLocalRoles()
295    principal = account.name
296    for role_id, object_list in local_roles.items():
297        for object in object_list:
298            try:
299                role_manager = IPrincipalRoleManager(object)
300            except TypeError:
301                # No role manager, no roles to remove
302                continue
303            role_manager.unsetRoleForPrincipal(role_id, principal)
304    return
305
306@grok.subscribe(IUserAccount, grok.IObjectAddedEvent)
307def handle_account_added(account, event):
308    """When an account is added, the local owner role and the global
309    AcademicsOfficer role must be set.
310    """
311    # We set the local Owner role
312    role_manager_account = IPrincipalRoleManager(account)
313    role_manager_account.assignRoleToPrincipal(
314        'waeup.local.Owner', account.name)
315    # We set the global AcademicsOfficer role
316    site = grok.getSite()
317    role_manager_site = IPrincipalRoleManager(site)
318    role_manager_site.assignRoleToPrincipal(
319        'waeup.AcademicsOfficer', account.name)
320    # Finally we have to notify the user account that the local role
321    # of the same object has changed
322    notify(LocalRoleSetEvent(
323        account, 'waeup.local.Owner', account.name, granted=True))
324    return
325
326@grok.subscribe(Interface, ILocalRoleSetEvent)
327def handle_local_role_changed(obj, event):
328    site = grok.getSite()
329    if site is None:
330        return
331    users = site.get('users', None)
332    if users is None:
333        return
334    role_id = event.role_id
335    if event.principal_id not in users.keys():
336        return
337    user = users[event.principal_id]
338    user.notifyLocalRoleChanged(event.object, event.role_id, event.granted)
339    return
340
341@grok.subscribe(Interface, grok.IObjectRemovedEvent)
342def handle_local_roles_on_obj_removed(obj, event):
343    try:
344        role_map = IPrincipalRoleMap(obj)
345    except TypeError:
346        # no map, no roles to remove
347        return
348    for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
349        notify(LocalRoleSetEvent(
350                obj, local_role, user_name, granted=False))
351    return
Note: See TracBrowser for help on using the repository browser.