Skip to content

Commit 6e021f8

Browse files
author
Johannes Gijsbers
committed
Remove duplication in permission handling:
* Use execute() as a Template Method, with permission() and handle() as the hook methods. * Provide a default implementation for the permission() method, which checks for self.permissionType, if the attribute is defined. * New hasPermission() method which checks whether the current user has a permission on the current class editItemPermission method: * Better error message when user is editing roles when they shouldn't be. * Extracted isEditingSelf(), which checks whether a user is editing his/her own details. * Use hasPermission method.
1 parent 35c3488 commit 6e021f8

File tree

2 files changed

+60
-90
lines changed

2 files changed

+60
-90
lines changed

roundup/cgi/actions.py

Lines changed: 58 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# used by a couple of routines
1515
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
1616

17-
class Action:
17+
class Action:
1818
def __init__(self, client):
1919
self.client = client
2020
self.form = client.form
@@ -26,16 +26,34 @@ def __init__(self, client):
2626
self.base = client.base
2727
self.user = client.user
2828

29-
def handle(self):
29+
def execute(self):
3030
"""Execute the action specified by this object."""
31-
raise NotImplementedError
31+
self.permission()
32+
self.handle()
3233

34+
name = ''
35+
permissionType = None
3336
def permission(self):
3437
"""Check whether the user has permission to execute this action.
3538
36-
True by default.
39+
True by default. If the permissionType attribute is a string containing
40+
a simple permission, check whether the user has that permission.
41+
Subclasses must also define the name attribute if they define
42+
permissionType.
43+
44+
Despite having this permission, users may still be unauthorised to
45+
perform parts of actions. It is up to the subclasses to detect this.
3746
"""
38-
return 1
47+
if (self.permissionType and
48+
not self.hasPermission(self.permissionType)):
49+
50+
raise Unauthorised, _('You do not have permission to %s the %s class.' %
51+
(self.name, self.classname))
52+
53+
def hasPermission(self, permission):
54+
"""Check whether the user has 'permission' on the current class."""
55+
return self.db.security.hasPermission(permission, self.client.userid,
56+
self.client.classname)
3957

4058
class ShowAction(Action):
4159
def handle(self, typere=re.compile('[@:]type'),
@@ -53,19 +71,17 @@ def handle(self, typere=re.compile('[@:]type'),
5371
raise Redirect, url
5472

5573
class RetireAction(Action):
74+
name = 'retire'
75+
permissionType = 'Edit'
76+
5677
def handle(self):
57-
"""Retire the context item."""
78+
"""Retire the context item."""
5879
# if we want to view the index template now, then unset the nodeid
5980
# context info (a special-case for retire actions on the index page)
6081
nodeid = self.nodeid
6182
if self.template == 'index':
6283
self.client.nodeid = None
6384

64-
# generic edit is per-class only
65-
if not self.permission():
66-
raise Unauthorised, _('You do not have permission to retire %s' %
67-
self.classname)
68-
6985
# make sure we don't try to retire admin or anonymous
7086
if self.classname == 'user' and \
7187
self.db.user.get(nodeid, 'username') in ('admin', 'anonymous'):
@@ -79,15 +95,10 @@ def handle(self):
7995
_('%(classname)s %(itemid)s has been retired')%{
8096
'classname': self.classname.capitalize(), 'itemid': nodeid})
8197

82-
def permission(self):
83-
"""Determine whether the user has permission to retire this class.
84-
85-
Base behaviour is to check the user can edit this class.
86-
"""
87-
return self.db.security.hasPermission('Edit', self.client.userid,
88-
self.client.classname)
89-
9098
class SearchAction(Action):
99+
name = 'search'
100+
permissionType = 'View'
101+
91102
def handle(self, wcre=re.compile(r'[\s,]+')):
92103
"""Mangle some of the form variables.
93104
@@ -101,11 +112,6 @@ def handle(self, wcre=re.compile(r'[\s,]+')):
101112
Split any String query values on whitespace and comma.
102113
103114
"""
104-
# generic edit is per-class only
105-
if not self.permission():
106-
raise Unauthorised, _('You do not have permission to search %s' %
107-
self.classname)
108-
109115
self.fakeFilterVars()
110116
queryname = self.getQueryName()
111117

@@ -168,12 +174,11 @@ def getQueryName(self):
168174
if self.FV_QUERYNAME.match(key):
169175
return self.form[key].value.strip()
170176
return ''
171-
172-
def permission(self):
173-
return self.db.security.hasPermission('View', self.client.userid,
174-
self.client.classname)
175177

176178
class EditCSVAction(Action):
179+
name = 'edit'
180+
permissionType = 'Edit'
181+
177182
def handle(self):
178183
"""Performs an edit of all of a class' items in one go.
179184
@@ -182,12 +187,6 @@ def handle(self):
182187
removed lines are retired.
183188
184189
"""
185-
# this is per-class only
186-
if not self.permission():
187-
self.client.error_message.append(
188-
_('You do not have permission to edit %s' %self.classname))
189-
return
190-
191190
# get the CSV module
192191
if rcsv.error:
193192
self.client.error_message.append(_(rcsv.error))
@@ -271,34 +270,27 @@ def handle(self):
271270
self.db.commit()
272271

273272
self.client.ok_message.append(_('Items edited OK'))
274-
275-
def permission(self):
276-
return self.db.security.hasPermission('Edit', self.client.userid,
277-
self.client.classname)
278-
273+
279274
class _EditAction(Action):
275+
def isEditingSelf(self):
276+
"""Check whether a user is editing his/her own details."""
277+
return (self.nodeid == self.userid
278+
and self.db.user.get(self.nodeid, 'username') != 'anonymous')
279+
280280
def editItemPermission(self, props):
281281
"""Determine whether the user has permission to edit this item.
282282
283283
Base behaviour is to check the user can edit this class. If we're
284-
editing the"user" class, users are allowed to edit their own details.
284+
editing the "user" class, users are allowed to edit their own details.
285285
Unless it's the "roles" property, which requires the special Permission
286286
"Web Roles".
287287
"""
288-
# if this is a user node and the user is editing their own node, then
289-
# we're OK
290-
has = self.db.security.hasPermission
291288
if self.classname == 'user':
292-
# reject if someone's trying to edit "roles" and doesn't have the
293-
# right permission.
294-
if props.has_key('roles') and not has('Web Roles', self.userid,
295-
'user'):
296-
return 0
297-
# if the item being edited is the current user, we're ok
298-
if (self.nodeid == self.userid
299-
and self.db.user.get(self.nodeid, 'username') != 'anonymous'):
289+
if props.has_key('roles') and not self.hasPermission('Web Roles'):
290+
raise Unauthorised, _("You do not have permission to edit user roles")
291+
if self.isEditingSelf():
300292
return 1
301-
if self.db.security.hasPermission('Edit', self.userid, self.classname):
293+
if self.hasPermission('Edit'):
302294
return 1
303295
return 0
304296

@@ -310,11 +302,8 @@ def newItemPermission(self, props):
310302
if the user has the "Web Registration" Permission.
311303
312304
"""
313-
has = self.db.security.hasPermission
314-
if self.classname == 'user' and has('Web Registration', self.userid,
315-
'user'):
316-
return 1
317-
if has('Edit', self.userid, self.classname):
305+
if (self.classname == 'user' and self.hasPermission('Web Registration')
306+
or self.hasPermission('Edit')):
318307
return 1
319308
return 0
320309

@@ -503,7 +492,7 @@ def handle(self):
503492
try:
504493
props, links = self.client.parsePropsFromForm(create=True)
505494
except (ValueError, KeyError), message:
506-
self.error_message.append(_('Error: ') + str(message))
495+
self.client.error_message.append(_('Error: ') + str(message))
507496
return
508497

509498
# handle the props - edit or create
@@ -513,7 +502,7 @@ def handle(self):
513502

514503
except (ValueError, KeyError, IndexError), message:
515504
# these errors might just be indicative of user dumbness
516-
self.error_message.append(_('Error: ') + str(message))
505+
self.client.error_message.append(_('Error: ') + str(message))
517506
return
518507

519508
# commit now that all the tricky stuff is done
@@ -656,17 +645,20 @@ def handle(self):
656645
self.userid, urllib.quote(message))
657646

658647
class RegisterAction(Action):
648+
name = 'register'
649+
permissionType = 'Web Registration'
650+
659651
def handle(self):
660652
"""Attempt to create a new user based on the contents of the form
661653
and then set the cookie.
662654
663655
Return 1 on successful login.
664-
"""
656+
"""
665657
props = self.client.parsePropsFromForm()[0][('user', None)]
666658

667-
# make sure we're allowed to register
668-
if not self.permission(props):
669-
raise Unauthorised, _("You do not have permission to register")
659+
# registration isn't allowed to supply roles
660+
if props.has_key('roles'):
661+
raise Unauthorised, _("It is not permitted to supply roles at registration.")
670662

671663
try:
672664
self.db.user.lookup(props['username'])
@@ -717,19 +709,6 @@ def handle(self):
717709
# redirect to the "you're almost there" page
718710
raise Redirect, '%suser?@template=rego_progress'%self.base
719711

720-
def permission(self, props):
721-
"""Determine whether the user has permission to register
722-
723-
Base behaviour is to check the user has "Web Registration".
724-
725-
"""
726-
# registration isn't allowed to supply roles
727-
if props.has_key('roles'):
728-
return 0
729-
if self.db.security.hasPermission('Web Registration', self.userid):
730-
return 1
731-
return 0
732-
733712
class LogoutAction(Action):
734713
def handle(self):
735714
"""Make us really anonymous - nuke the cookie too."""
@@ -779,8 +758,9 @@ def handle(self):
779758
self.client.error_message.append(_('Incorrect password'))
780759
return
781760

782-
# make sure we're allowed to be here
783-
if not self.permission():
761+
# Determine whether the user has permission to log in.
762+
# Base behaviour is to check the user has "Web Access".
763+
if not self.hasPermission("Web Access"):
784764
self.client.make_user_anonymous()
785765
self.client.error_message.append(_("You do not have permission to login"))
786766
return
@@ -800,13 +780,3 @@ def verifyPassword(self, userid, password):
800780
if not password and not stored:
801781
return 1
802782
return 0
803-
804-
def permission(self):
805-
"""Determine whether the user has permission to log in.
806-
807-
Base behaviour is to check the user has "Web Access".
808-
809-
"""
810-
if not self.db.security.hasPermission('Web Access', self.client.userid):
811-
return 0
812-
return 1

roundup/cgi/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: client.py,v 1.159 2004-02-14 11:27:23 jlgijsbers Exp $
1+
# $Id: client.py,v 1.160 2004-02-14 19:58:20 jlgijsbers Exp $
22

33
"""WWW request handler (also used in the stand-alone server).
44
"""
@@ -554,7 +554,7 @@ def handle_action(self):
554554
raise ValueError, 'No such action "%s"'%action
555555
# call the mapped action
556556
try:
557-
action_klass(self).handle()
557+
action_klass(self).execute()
558558
except TypeError:
559559
# Old way of specifying actions.
560560
getattr(self, action_klass)()

0 commit comments

Comments
 (0)