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

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

Rebuild applicants package (1st part). Applicants now have an applicant_id and a password and can use the regular login page to enter the portal.

Add user_type attribute to SIRPPrincipal objects.

Add some permissions in students package.

Some tests are still missing and will be re-added soon.

  • Property svn:keywords set to Id
File size: 12.1 KB
Line 
1## $Id: authentication.py 7240 2011-11-30 23:13:26Z 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, 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       
218        if not ('login' in credentials and 'password' in credentials):
219            return None
220        account = self.getAccount(credentials['login'])
221        if account is None:
222            return None
223        if not account.checkPassword(credentials['password']):
224            return None
225        return SIRPPrincipalInfo(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(id=account.name,
237                             title=account.title,
238                             description=account.description,
239                             email=account.email,
240                             phone=account.phone,
241                             user_type=u'user')
242
243    def getAccount(self, login):
244        # ... look up the account object and return it ...
245        userscontainer = self.getUsersContainer()
246        if userscontainer is None:
247            return
248        return userscontainer.get(login, None)
249
250    def addAccount(self, account):
251        userscontainer = self.getUsersContainer()
252        if userscontainer is None:
253            return
254        # XXX: complain if name already exists...
255        userscontainer.addAccount(account)
256
257    def addUser(self, name, password, title=None, description=None):
258        userscontainer = self.getUsersContainer()
259        if userscontainer is None:
260            return
261        userscontainer.addUser(name, password, title, description)
262
263    def getUsersContainer(self):
264        site = grok.getSite()
265        return site['users']
266
267class PasswordValidator(grok.GlobalUtility):
268
269  grok.implements(IPasswordValidator)
270
271  def validate_password(self, pw, pw_repeat):
272       errors = []
273       if len(pw) < 3:
274         errors.append('Password must have at least 3 chars.')
275       if pw != pw_repeat:
276         errors.append('Passwords do not match.')
277       return errors
278
279class LocalRoleSetEvent(object):
280
281    grok.implements(ILocalRoleSetEvent)
282
283    def __init__(self, object, role_id, principal_id, granted=True):
284        self.object = object
285        self.role_id = role_id
286        self.principal_id = principal_id
287        self.granted = granted
288
289@grok.subscribe(IUserAccount, grok.IObjectRemovedEvent)
290def handle_account_removed(account, event):
291    """When an account is removed, local roles might have to be deleted.
292    """
293    local_roles = account.getLocalRoles()
294    principal = account.name
295    for role_id, object_list in local_roles.items():
296        for object in object_list:
297            try:
298                role_manager = IPrincipalRoleManager(object)
299            except TypeError:
300                # No role manager, no roles to remove
301                continue
302            role_manager.unsetRoleForPrincipal(role_id, principal)
303    return
304
305@grok.subscribe(IUserAccount, grok.IObjectAddedEvent)
306def handle_account_added(account, event):
307    """When an account is added, the local owner role and the global
308    AcademicsOfficer role must be set.
309    """
310    # We set the local Owner role
311    role_manager_account = IPrincipalRoleManager(account)
312    role_manager_account.assignRoleToPrincipal(
313        'waeup.local.Owner', account.name)
314    # We set the global AcademicsOfficer role
315    site = grok.getSite()
316    role_manager_site = IPrincipalRoleManager(site)
317    role_manager_site.assignRoleToPrincipal(
318        'waeup.AcademicsOfficer', account.name)
319    # Finally we have to notify the user account that the local role
320    # of the same object has changed
321    notify(LocalRoleSetEvent(
322        account, 'waeup.local.Owner', account.name, granted=True))
323    return
324
325@grok.subscribe(Interface, ILocalRoleSetEvent)
326def handle_local_role_changed(obj, event):
327    site = grok.getSite()
328    if site is None:
329        return
330    users = site.get('users', None)
331    if users is None:
332        return
333    role_id = event.role_id
334    if event.principal_id not in users.keys():
335        return
336    user = users[event.principal_id]
337    user.notifyLocalRoleChanged(event.object, event.role_id, event.granted)
338    return
339
340@grok.subscribe(Interface, grok.IObjectRemovedEvent)
341def handle_local_roles_on_obj_removed(obj, event):
342    try:
343        role_map = IPrincipalRoleMap(obj)
344    except TypeError:
345        # no map, no roles to remove
346        return
347    for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
348        notify(LocalRoleSetEvent(
349                obj, local_role, user_name, granted=False))
350    return
Note: See TracBrowser for help on using the repository browser.