changeset 5604:ed02a1e0aa5d REST-rebased

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.
author Ralf Schlatterbeck <rsc@runtux.com>
date Wed, 30 Jan 2019 14:12:27 +0100
parents 79da1ca2f94b
children 33f8bb777659
files roundup/actions.py roundup/rest.py roundup/xmlrpc.py test/rest_common.py
diffstat 4 files changed, 45 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/roundup/actions.py	Wed Jan 30 13:58:18 2019 +0100
+++ b/roundup/actions.py	Wed Jan 30 14:12:27 2019 +0100
@@ -2,6 +2,7 @@
 # Copyright (C) 2009 Stefan Seefeld
 # All rights reserved.
 # For license terms see the file COPYING.txt.
+# Actions used in REST and XMLRPC APIs
 #
 
 from roundup.exceptions import Unauthorised
@@ -40,7 +41,19 @@
     _ = gettext
 
 
-class Retire(Action):
+class PermCheck(Action):
+    def permission(self, designator):
+
+        classname, itemid = hyperdb.splitDesignator(designator)
+        perm = self.db.security.hasPermission
+
+        if not perm('Retire', self.db.getuid(), classname=classname
+                   , itemid=itemid):
+            raise Unauthorised(self._('You do not have permission to retire '
+                                      'or restore the %(classname)s class.')
+                                      %locals())
+
+class Retire(PermCheck):
 
     def handle(self, designator):
 
@@ -57,12 +70,13 @@
         self.db.commit()
 
 
-    def permission(self, designator):
+class Restore(PermCheck):
+
+    def handle(self, designator):
 
         classname, itemid = hyperdb.splitDesignator(designator)
 
-        if not self.db.security.hasPermission('Edit', self.db.getuid(),
-                                              classname=classname, itemid=itemid):
-            raise Unauthorised(self._('You do not have permission to '
-                                      'retire the %(classname)s class.')%classname)
-            
+        # do the restore
+        self.db.getclass(classname).restore(itemid)
+        self.db.commit()
+
--- a/roundup/rest.py	Wed Jan 30 13:58:18 2019 +0100
+++ b/roundup/rest.py	Wed Jan 30 14:12:27 2019 +0100
@@ -243,8 +243,11 @@
         self.client = client
         self.db = db
         self.translator = client.translator
-        self.actions = client.instance.actions.copy()
-        self.actions.update({'retire': actions.Retire})
+        # This used to be initialized from client.instance.actions which
+        # would include too many actions that do not make sense in the
+        # REST-API context, so for now we only permit the retire and
+        # restore actions.
+        self.actions = dict (retire = actions.Retire, restore = actions.Restore)
 
         protocol = 'http'
         host = self.client.env['HTTP_HOST']
@@ -716,7 +719,9 @@
     @Routing.route("/data/<:class_name>", 'DELETE')
     @_data_decorator
     def delete_collection(self, class_name, input):
-        """DELETE all objects in a class
+        """DELETE (retire) all objects in a class
+           There is currently no use-case, so this is disabled and
+           always returns Unauthorised.
 
         Args:
             class_name (string): class name of the resource (Ex: issue, msg)
@@ -728,25 +733,26 @@
                 status (string): 'ok'
                 count (int): number of deleted objects
         """
+        raise Unauthorised('Deletion of a whole class disabled')
         if class_name not in self.db.classes:
             raise NotFound('Class %s not found' % class_name)
         if not self.db.security.hasPermission(
-            'Delete', self.db.getuid(), class_name
+            'Retire', self.db.getuid(), class_name
         ):
             raise Unauthorised('Permission to delete %s denied' % class_name)
 
         class_obj = self.db.getclass(class_name)
         for item_id in class_obj.list():
             if not self.db.security.hasPermission(
-                'Delete', self.db.getuid(), class_name, itemid=item_id
+                'Retire', self.db.getuid(), class_name, itemid=item_id
             ):
                 raise Unauthorised(
-                    'Permission to delete %s %s denied' % (class_name, item_id)
+                    'Permission to retire %s %s denied' % (class_name, item_id)
                 )
 
         count = len(class_obj.list())
         for item_id in class_obj.list():
-            self.db.destroynode(class_name, item_id)
+            class_obj.retire (item_id)
 
         self.db.commit()
         result = {
@@ -759,7 +765,7 @@
     @Routing.route("/data/<:class_name>/<:item_id>", 'DELETE')
     @_data_decorator
     def delete_element(self, class_name, item_id, input):
-        """DELETE an object in a class
+        """DELETE (retire) an object in a class
 
         Args:
             class_name (string): class name of the resource (Ex: issue, msg)
@@ -773,14 +779,15 @@
         """
         if class_name not in self.db.classes:
             raise NotFound('Class %s not found' % class_name)
+        class_obj = self.db.classes [class_name]
         if not self.db.security.hasPermission(
-            'Delete', self.db.getuid(), class_name, itemid=item_id
+            'Retire', self.db.getuid(), class_name, itemid=item_id
         ):
             raise Unauthorised(
-                'Permission to delete %s %s denied' % (class_name, item_id)
+                'Permission to retire %s %s denied' % (class_name, item_id)
             )
 
-        self.db.destroynode(class_name, item_id)
+        class_obj.retire (item_id)
         self.db.commit()
         result = {
             'status': 'ok'
--- a/roundup/xmlrpc.py	Wed Jan 30 13:58:18 2019 +0100
+++ b/roundup/xmlrpc.py	Wed Jan 30 14:12:27 2019 +0100
@@ -179,7 +179,7 @@
         return result
 
 
-    builtin_actions = {'retire': actions.Retire}
+    builtin_actions = dict (retire = actions.Retire, restore = actions.Restore)
 
     def action(self, name, *args):
         """Execute a named action."""
@@ -189,7 +189,8 @@
         elif name in self.builtin_actions:
             action_type = self.builtin_actions[name]
         else:
-            raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys())))
+            raise Exception('action "%s" is not supported %s'
+                           % (name, ','.join(self.actions.keys())))
         action = action_type(self.db, self.translator)
         return action.execute(*args)
 
--- a/test/rest_common.py	Wed Jan 30 13:58:18 2019 +0100
+++ b/test/rest_common.py	Wed Jan 30 14:12:27 2019 +0100
@@ -39,6 +39,9 @@
         self.db.commit()
         self.db.close()
         self.db = self.instance.open('joe')
+        # Allow joe to retire
+        p = self.db.security.addPermission(name='Retire', klass='issue')
+        self.db.security.addPermissionToRole('User', p)
 
         self.db.tx_Source = 'web'
 

Roundup Issue Tracker: http://roundup-tracker.org/