source: main/waeup.kofa/trunk/src/waeup/kofa/authentication.py @ 8045

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

KOFA -> Kofa

  • Property svn:keywords set to Id
File size: 12.0 KB
Line 
1## $Id: authentication.py 7819 2012-03-08 22:28:46Z 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 Kofa.
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.kofa.interfaces import (ILocalRoleSetEvent,
35    IUserAccount, IAuthPluginUtility, IPasswordValidator,
36    IKofaPrincipal, IKofaPrincipalInfo)
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 KofaSessionCredentialsPlugin(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 KofaPrincipalInfo(object):
75    """An implementation of IKofaPrincipalInfo.
76
77    A Kofa principal info is created with id, login, title, description,
78    phone, email and user_type.
79    """
80    grok.implements(IKofaPrincipalInfo)
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 KofaPrincipal(Principal):
93    """A portal principal.
94
95    Kofa principals provide an extra `email`, `phone` and `user_type`
96    attribute extending ordinary principals.
97    """
98
99    grok.implements(IKofaPrincipal)
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 'KofaPrincipal(%r)' % self.id
115
116class AuthenticatedKofaPrincipalFactory(grok.MultiAdapter):
117    """Creates 'authenticated' Kofa principals.
118
119    Adapts (principal info, request) to a KofaPrincipal instance.
120
121    This adapter is used by the standard PAU to transform
122    KofaPrincipalInfos into KofaPrincipal instances.
123    """
124    grok.adapts(IKofaPrincipalInfo, 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 = KofaPrincipal(
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        if sorted(old_roles) == sorted(roles):
183            return
184        for role in old_roles:
185            # Remove old roles, not to be set now...
186            if role.startswith('waeup.') and role not in roles:
187                prm.unsetRoleForPrincipal(role, self.name)
188        for role in roles:
189            # Convert role to ASCII string to be in line with the
190            # event handler
191            prm.assignRoleToPrincipal(str(role), self.name)
192        return
193
194    roles = property(getSiteRolesForPrincipal, setSiteRolesForPrincipal)
195
196    def getLocalRoles(self):
197        return self._local_roles
198
199    def notifyLocalRoleChanged(self, obj, role_id, granted=True):
200        objects = self._local_roles.get(role_id, [])
201        if granted and obj not in objects:
202            objects.append(obj)
203        if not granted and obj in objects:
204            objects.remove(obj)
205        self._local_roles[role_id] = objects
206        if len(objects) == 0:
207            del self._local_roles[role_id]
208        self._p_changed = True
209        return
210
211class UserAuthenticatorPlugin(grok.GlobalUtility):
212    grok.implements(IAuthenticatorPlugin)
213    grok.provides(IAuthenticatorPlugin)
214    grok.name('users')
215
216    def authenticateCredentials(self, credentials):
217        if not isinstance(credentials, dict):
218            return None
219        if not ('login' in credentials and 'password' in credentials):
220            return None
221        account = self.getAccount(credentials['login'])
222        if account is None:
223            return None
224        if not account.checkPassword(credentials['password']):
225            return None
226        return KofaPrincipalInfo(
227            id=account.name,
228            title=account.title,
229            description=account.description,
230            email=account.email,
231            phone=account.phone,
232            user_type=u'user')
233
234    def principalInfo(self, id):
235        account = self.getAccount(id)
236        if account is None:
237            return None
238        return KofaPrincipalInfo(
239            id=account.name,
240            title=account.title,
241            description=account.description,
242            email=account.email,
243            phone=account.phone,
244            user_type=u'user')
245
246    def getAccount(self, login):
247        # ... look up the account object and return it ...
248        userscontainer = self.getUsersContainer()
249        if userscontainer is None:
250            return
251        return userscontainer.get(login, None)
252
253    def addAccount(self, account):
254        userscontainer = self.getUsersContainer()
255        if userscontainer is None:
256            return
257        # XXX: complain if name already exists...
258        userscontainer.addAccount(account)
259
260    def addUser(self, name, password, title=None, description=None):
261        userscontainer = self.getUsersContainer()
262        if userscontainer is None:
263            return
264        userscontainer.addUser(name, password, title, description)
265
266    def getUsersContainer(self):
267        site = grok.getSite()
268        return site['users']
269
270class PasswordValidator(grok.GlobalUtility):
271
272  grok.implements(IPasswordValidator)
273
274  def validate_password(self, pw, pw_repeat):
275       errors = []
276       if len(pw) < 3:
277         errors.append('Password must have at least 3 chars.')
278       if pw != pw_repeat:
279         errors.append('Passwords do not match.')
280       return errors
281
282class LocalRoleSetEvent(object):
283
284    grok.implements(ILocalRoleSetEvent)
285
286    def __init__(self, object, role_id, principal_id, granted=True):
287        self.object = object
288        self.role_id = role_id
289        self.principal_id = principal_id
290        self.granted = granted
291
292@grok.subscribe(IUserAccount, grok.IObjectRemovedEvent)
293def handle_account_removed(account, event):
294    """When an account is removed, local roles might have to be deleted.
295    """
296    local_roles = account.getLocalRoles()
297    principal = account.name
298    for role_id, object_list in local_roles.items():
299        for object in object_list:
300            try:
301                role_manager = IPrincipalRoleManager(object)
302            except TypeError:
303                # No role manager, no roles to remove
304                continue
305            role_manager.unsetRoleForPrincipal(role_id, principal)
306    return
307
308@grok.subscribe(IUserAccount, grok.IObjectAddedEvent)
309def handle_account_added(account, event):
310    """When an account is added, the local owner role and the global
311    AcademicsOfficer role must be set.
312    """
313    # We set the local Owner role
314    role_manager_account = IPrincipalRoleManager(account)
315    role_manager_account.assignRoleToPrincipal(
316        'waeup.local.Owner', account.name)
317    # We set the global AcademicsOfficer role
318    site = grok.getSite()
319    role_manager_site = IPrincipalRoleManager(site)
320    role_manager_site.assignRoleToPrincipal(
321        'waeup.AcademicsOfficer', account.name)
322    # Finally we have to notify the user account that the local role
323    # of the same object has changed
324    notify(LocalRoleSetEvent(
325        account, 'waeup.local.Owner', account.name, granted=True))
326    return
327
328@grok.subscribe(Interface, ILocalRoleSetEvent)
329def handle_local_role_changed(obj, event):
330    site = grok.getSite()
331    if site is None:
332        return
333    users = site.get('users', None)
334    if users is None:
335        return
336    role_id = event.role_id
337    if event.principal_id not in users.keys():
338        return
339    user = users[event.principal_id]
340    user.notifyLocalRoleChanged(event.object, event.role_id, event.granted)
341    return
342
343@grok.subscribe(Interface, grok.IObjectRemovedEvent)
344def handle_local_roles_on_obj_removed(obj, event):
345    try:
346        role_map = IPrincipalRoleMap(obj)
347    except TypeError:
348        # no map, no roles to remove
349        return
350    for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
351        notify(LocalRoleSetEvent(
352                obj, local_role, user_name, granted=False))
353    return
Note: See TracBrowser for help on using the repository browser.