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

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

Implement SIRPPrincipalInfo and SIRPPrincipal classes which provide ordinary principals with an extra email and phone attribute.

  • Property svn:keywords set to Id
File size: 11.7 KB
Line 
1## $Id: authentication.py 7233 2011-11-28 21:04:57Z 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 WAeUP portals.
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 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
74class SIRPPrincipalInfo(object):
75    """An implementation of ISIRPPrincipalInfo.
76
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):
83        self.id = id
84        self.title = title
85        self.description = description
86        self.email = email
87        self.phone = phone
88        self.credentialsPlugin = None
89        self.authenticatorPlugin = None
90
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
103        self.title = title
104        self.description = description
105        self.groups = []
106        self.email = email
107        self.phone = phone
108
109    def __repr__(self):
110        return 'SIRPPrincipal(%r)' % self.id
111
112class AuthenticatedSIRPPrincipalFactory(grok.MultiAdapter):
113    """Creates 'authenticated' SIRP principals.
114
115    Adapts (principal info, request) to a SIRPPrincipal instance.
116
117    This adapter is used by the standard PAU to transform
118    PrincipalInfos into Principal instances.
119    """
120    grok.adapts(ISIRPPrincipalInfo, IRequest)
121    grok.implements(IAuthenticatedPrincipalFactory)
122
123    def __init__(self, info, request):
124        self.info = info
125        self.request = request
126
127    def __call__(self, authentication):
128        principal = SIRPPrincipal(
129            self.info.id,
130            self.info.title,
131            self.info.description,
132            self.info.email,
133            self.info.phone,
134            )
135        notify(
136            AuthenticatedPrincipalCreated(
137                authentication, principal, self.info, self.request))
138        return principal
139
140class Account(grok.Model):
141    grok.implements(IUserAccount)
142
143    _local_roles = dict()
144
145    def __init__(self, name, password, title=None, description=None,
146                 email=None, phone=None, roles = []):
147        self.name = name
148        if title is None:
149            title = name
150        #if description is None:
151        #    description = title
152        self.title = title
153        self.description = description
154        self.email = email
155        self.phone = phone
156        self.setPassword(password)
157        #self.setSiteRolesForPrincipal(roles)
158        # We don't want to share this dict with other accounts
159        self._local_roles = dict()
160
161    def setPassword(self, password):
162        passwordmanager = getUtility(IPasswordManager, 'SHA1')
163        self.password = passwordmanager.encodePassword(password)
164
165    def checkPassword(self, password):
166        passwordmanager = getUtility(IPasswordManager, 'SHA1')
167        return passwordmanager.checkPassword(self.password, password)
168
169    def getSiteRolesForPrincipal(self):
170        prm = get_principal_role_manager()
171        roles = [x[0] for x in prm.getRolesForPrincipal(self.name)
172                 if x[0].startswith('waeup.')]
173        return roles
174
175    def setSiteRolesForPrincipal(self, roles):
176        prm = get_principal_role_manager()
177        old_roles = self.getSiteRolesForPrincipal()
178        for role in old_roles:
179            # Remove old roles, not to be set now...
180            if role.startswith('waeup.') and role not in roles:
181                prm.unsetRoleForPrincipal(role, self.name)
182        for role in roles:
183            prm.assignRoleToPrincipal(role, self.name)
184
185    roles = property(getSiteRolesForPrincipal, setSiteRolesForPrincipal)
186
187    def getLocalRoles(self):
188        return self._local_roles
189
190    def notifyLocalRoleChanged(self, obj, role_id, granted=True):
191        objects = self._local_roles.get(role_id, [])
192        if granted and obj not in objects:
193            objects.append(obj)
194        if not granted and obj in objects:
195            objects.remove(obj)
196        self._local_roles[role_id] = objects
197        if len(objects) == 0:
198            del self._local_roles[role_id]
199        self._p_changed = True
200        return
201
202class UserAuthenticatorPlugin(grok.GlobalUtility):
203    grok.implements(IAuthenticatorPlugin)
204    grok.provides(IAuthenticatorPlugin)
205    grok.name('users')
206
207    def authenticateCredentials(self, credentials):
208        if not isinstance(credentials, dict):
209            return None
210        if not ('login' in credentials and 'password' in credentials):
211            return None
212        account = self.getAccount(credentials['login'])
213
214        if account is None:
215            return None
216        if not account.checkPassword(credentials['password']):
217            return None
218        return SIRPPrincipalInfo(id=account.name,
219                             title=account.title,
220                             description=account.description,
221                             email=account.email,
222                             phone=account.phone)
223
224    def principalInfo(self, id):
225        account = self.getAccount(id)
226        if account is None:
227            return None
228        return SIRPPrincipalInfo(id=account.name,
229                             title=account.title,
230                             description=account.description,
231                             email=account.email,
232                             phone=account.phone)
233
234    def getAccount(self, login):
235        # ... look up the account object and return it ...
236        userscontainer = self.getUsersContainer()
237        if userscontainer is None:
238            return
239        return userscontainer.get(login, None)
240
241    def addAccount(self, account):
242        userscontainer = self.getUsersContainer()
243        if userscontainer is None:
244            return
245        # XXX: complain if name already exists...
246        userscontainer.addAccount(account)
247
248    def addUser(self, name, password, title=None, description=None):
249        userscontainer = self.getUsersContainer()
250        if userscontainer is None:
251            return
252        userscontainer.addUser(name, password, title, description)
253
254    def getUsersContainer(self):
255        site = grok.getSite()
256        return site['users']
257
258class PasswordValidator(grok.GlobalUtility):
259
260  grok.implements(IPasswordValidator)
261
262  def validate_password(self, pw, pw_repeat):
263       errors = []
264       if len(pw) < 3:
265         errors.append('Password must have at least 3 chars.')
266       if pw != pw_repeat:
267         errors.append('Passwords do not match.')
268       return errors
269
270class LocalRoleSetEvent(object):
271
272    grok.implements(ILocalRoleSetEvent)
273
274    def __init__(self, object, role_id, principal_id, granted=True):
275        self.object = object
276        self.role_id = role_id
277        self.principal_id = principal_id
278        self.granted = granted
279
280@grok.subscribe(IUserAccount, grok.IObjectRemovedEvent)
281def handle_account_removed(account, event):
282    """When an account is removed, local roles might have to be deleted.
283    """
284    local_roles = account.getLocalRoles()
285    principal = account.name
286    for role_id, object_list in local_roles.items():
287        for object in object_list:
288            try:
289                role_manager = IPrincipalRoleManager(object)
290            except TypeError:
291                # No role manager, no roles to remove
292                continue
293            role_manager.unsetRoleForPrincipal(role_id, principal)
294    return
295
296@grok.subscribe(IUserAccount, grok.IObjectAddedEvent)
297def handle_account_added(account, event):
298    """When an account is added, the local owner role and the global
299    AcademicsOfficer role must be set.
300    """
301    # We set the local Owner role
302    role_manager_account = IPrincipalRoleManager(account)
303    role_manager_account.assignRoleToPrincipal(
304        'waeup.local.Owner', account.name)
305    # We set the global AcademicsOfficer role
306    site = grok.getSite()
307    role_manager_site = IPrincipalRoleManager(site)
308    role_manager_site.assignRoleToPrincipal(
309        'waeup.AcademicsOfficer', account.name)
310    # Finally we have to notify the user account that the local role
311    # of the same object has changed
312    notify(LocalRoleSetEvent(
313        account, 'waeup.local.Owner', account.name, granted=True))
314    return
315
316@grok.subscribe(Interface, ILocalRoleSetEvent)
317def handle_local_role_changed(obj, event):
318    site = grok.getSite()
319    if site is None:
320        return
321    users = site.get('users', None)
322    if users is None:
323        return
324    role_id = event.role_id
325    if event.principal_id not in users.keys():
326        return
327    user = users[event.principal_id]
328    user.notifyLocalRoleChanged(event.object, event.role_id, event.granted)
329    return
330
331@grok.subscribe(Interface, grok.IObjectRemovedEvent)
332def handle_local_roles_on_obj_removed(obj, event):
333    try:
334        role_map = IPrincipalRoleMap(obj)
335    except TypeError:
336        # no map, no roles to remove
337        return
338    for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
339        notify(LocalRoleSetEvent(
340                obj, local_role, user_name, granted=False))
341    return
Note: See TracBrowser for help on using the repository browser.