Skip to content

Commit 75bde45

Browse files
committed
Fix actions
Permission for retire in roundup/actions.py was with 'Edit' permission, not 'Retire' permission. Add a 'restore' action to roundup/actions.py. Both are now correctly used in rest.py and xmlrpc.py (the latter had some errors when printint error messages). Also reworked the rest implementation: Despite the warnings in the roundup API in hyperdb.py the DELETE http method would *destroy* and not *retire* an item. This has been fixed. We also do not allow retire of a complete class (although this was implemented) because this seems to dangerous and we see no use-case.
1 parent 2175a37 commit 75bde45

File tree

4 files changed

+45
-20
lines changed

4 files changed

+45
-20
lines changed

roundup/actions.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright (C) 2009 Stefan Seefeld
33
# All rights reserved.
44
# For license terms see the file COPYING.txt.
5+
# Actions used in REST and XMLRPC APIs
56
#
67

78
from roundup.exceptions import Unauthorised
@@ -40,7 +41,19 @@ def gettext(self, msgid):
4041
_ = gettext
4142

4243

43-
class Retire(Action):
44+
class PermCheck(Action):
45+
def permission(self, designator):
46+
47+
classname, itemid = hyperdb.splitDesignator(designator)
48+
perm = self.db.security.hasPermission
49+
50+
if not perm('Retire', self.db.getuid(), classname=classname
51+
, itemid=itemid):
52+
raise Unauthorised(self._('You do not have permission to retire '
53+
'or restore the %(classname)s class.')
54+
%locals())
55+
56+
class Retire(PermCheck):
4457

4558
def handle(self, designator):
4659

@@ -57,12 +70,13 @@ def handle(self, designator):
5770
self.db.commit()
5871

5972

60-
def permission(self, designator):
73+
class Restore(PermCheck):
74+
75+
def handle(self, designator):
6176

6277
classname, itemid = hyperdb.splitDesignator(designator)
6378

64-
if not self.db.security.hasPermission('Edit', self.db.getuid(),
65-
classname=classname, itemid=itemid):
66-
raise Unauthorised(self._('You do not have permission to '
67-
'retire the %(classname)s class.')%classname)
68-
79+
# do the restore
80+
self.db.getclass(classname).restore(itemid)
81+
self.db.commit()
82+

roundup/rest.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,11 @@ def __init__(self, client, db):
243243
self.client = client
244244
self.db = db
245245
self.translator = client.translator
246-
self.actions = client.instance.actions.copy()
247-
self.actions.update({'retire': actions.Retire})
246+
# This used to be initialized from client.instance.actions which
247+
# would include too many actions that do not make sense in the
248+
# REST-API context, so for now we only permit the retire and
249+
# restore actions.
250+
self.actions = dict (retire = actions.Retire, restore = actions.Restore)
248251

249252
protocol = 'http'
250253
host = self.client.env['HTTP_HOST']
@@ -716,7 +719,9 @@ def put_attribute(self, class_name, item_id, attr_name, input):
716719
@Routing.route("/data/<:class_name>", 'DELETE')
717720
@_data_decorator
718721
def delete_collection(self, class_name, input):
719-
"""DELETE all objects in a class
722+
"""DELETE (retire) all objects in a class
723+
There is currently no use-case, so this is disabled and
724+
always returns Unauthorised.
720725
721726
Args:
722727
class_name (string): class name of the resource (Ex: issue, msg)
@@ -728,25 +733,26 @@ def delete_collection(self, class_name, input):
728733
status (string): 'ok'
729734
count (int): number of deleted objects
730735
"""
736+
raise Unauthorised('Deletion of a whole class disabled')
731737
if class_name not in self.db.classes:
732738
raise NotFound('Class %s not found' % class_name)
733739
if not self.db.security.hasPermission(
734-
'Delete', self.db.getuid(), class_name
740+
'Retire', self.db.getuid(), class_name
735741
):
736742
raise Unauthorised('Permission to delete %s denied' % class_name)
737743

738744
class_obj = self.db.getclass(class_name)
739745
for item_id in class_obj.list():
740746
if not self.db.security.hasPermission(
741-
'Delete', self.db.getuid(), class_name, itemid=item_id
747+
'Retire', self.db.getuid(), class_name, itemid=item_id
742748
):
743749
raise Unauthorised(
744-
'Permission to delete %s %s denied' % (class_name, item_id)
750+
'Permission to retire %s %s denied' % (class_name, item_id)
745751
)
746752

747753
count = len(class_obj.list())
748754
for item_id in class_obj.list():
749-
self.db.destroynode(class_name, item_id)
755+
class_obj.retire (item_id)
750756

751757
self.db.commit()
752758
result = {
@@ -759,7 +765,7 @@ def delete_collection(self, class_name, input):
759765
@Routing.route("/data/<:class_name>/<:item_id>", 'DELETE')
760766
@_data_decorator
761767
def delete_element(self, class_name, item_id, input):
762-
"""DELETE an object in a class
768+
"""DELETE (retire) an object in a class
763769
764770
Args:
765771
class_name (string): class name of the resource (Ex: issue, msg)
@@ -773,14 +779,15 @@ def delete_element(self, class_name, item_id, input):
773779
"""
774780
if class_name not in self.db.classes:
775781
raise NotFound('Class %s not found' % class_name)
782+
class_obj = self.db.classes [class_name]
776783
if not self.db.security.hasPermission(
777-
'Delete', self.db.getuid(), class_name, itemid=item_id
784+
'Retire', self.db.getuid(), class_name, itemid=item_id
778785
):
779786
raise Unauthorised(
780-
'Permission to delete %s %s denied' % (class_name, item_id)
787+
'Permission to retire %s %s denied' % (class_name, item_id)
781788
)
782789

783-
self.db.destroynode(class_name, item_id)
790+
class_obj.retire (item_id)
784791
self.db.commit()
785792
result = {
786793
'status': 'ok'

roundup/xmlrpc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def set(self, designator, *args):
179179
return result
180180

181181

182-
builtin_actions = {'retire': actions.Retire}
182+
builtin_actions = dict (retire = actions.Retire, restore = actions.Restore)
183183

184184
def action(self, name, *args):
185185
"""Execute a named action."""
@@ -189,7 +189,8 @@ def action(self, name, *args):
189189
elif name in self.builtin_actions:
190190
action_type = self.builtin_actions[name]
191191
else:
192-
raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys())))
192+
raise Exception('action "%s" is not supported %s'
193+
% (name, ','.join(self.actions.keys())))
193194
action = action_type(self.db, self.translator)
194195
return action.execute(*args)
195196

test/rest_common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def setUp(self):
3939
self.db.commit()
4040
self.db.close()
4141
self.db = self.instance.open('joe')
42+
# Allow joe to retire
43+
p = self.db.security.addPermission(name='Retire', klass='issue')
44+
self.db.security.addPermissionToRole('User', p)
4245

4346
self.db.tx_Source = 'web'
4447

0 commit comments

Comments
 (0)