diff roundup/cgi/actions.py @ 2018:96a1bf48efdd

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.
author Johannes Gijsbers <jlgijsbers@users.sourceforge.net>
date Sat, 14 Feb 2004 19:58:20 +0000
parents 366d3bbce982
children bcb21e5722b8
line wrap: on
line diff
--- a/roundup/cgi/actions.py	Sat Feb 14 17:18:01 2004 +0000
+++ b/roundup/cgi/actions.py	Sat Feb 14 19:58:20 2004 +0000
@@ -14,7 +14,7 @@
 # used by a couple of routines
 chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
 
-class Action:
+class Action:    
     def __init__(self, client):
         self.client = client
         self.form = client.form
@@ -26,16 +26,34 @@
         self.base = client.base
         self.user = client.user
         
-    def handle(self):
+    def execute(self):
         """Execute the action specified by this object."""
-        raise NotImplementedError
+        self.permission()
+        self.handle()
 
+    name = ''
+    permissionType = None
     def permission(self):
         """Check whether the user has permission to execute this action.
 
-        True by default.
+        True by default. If the permissionType attribute is a string containing
+        a simple permission, check whether the user has that permission.
+        Subclasses must also define the name attribute if they define
+        permissionType.
+        
+        Despite having this permission, users may still be unauthorised to
+        perform parts of actions. It is up to the subclasses to detect this.        
         """
-        return 1
+        if (self.permissionType and
+            not self.hasPermission(self.permissionType)):
+
+            raise Unauthorised, _('You do not have permission to %s the %s class.' %
+                                  (self.name, self.classname))
+
+    def hasPermission(self, permission):
+        """Check whether the user has 'permission' on the current class."""
+        return self.db.security.hasPermission(permission, self.client.userid,
+                                              self.client.classname)
 
 class ShowAction(Action):
     def handle(self, typere=re.compile('[@:]type'),
@@ -53,19 +71,17 @@
         raise Redirect, url
 
 class RetireAction(Action):
+    name = 'retire'
+    permissionType = 'Edit'
+
     def handle(self):
-        """Retire the context item."""
+        """Retire the context item."""        
         # if we want to view the index template now, then unset the nodeid
         # context info (a special-case for retire actions on the index page)
         nodeid = self.nodeid
         if self.template == 'index':
             self.client.nodeid = None
 
-        # generic edit is per-class only
-        if not self.permission():
-            raise Unauthorised, _('You do not have permission to retire %s' %
-                                  self.classname)
-
         # make sure we don't try to retire admin or anonymous
         if self.classname == 'user' and \
                 self.db.user.get(nodeid, 'username') in ('admin', 'anonymous'):
@@ -79,15 +95,10 @@
             _('%(classname)s %(itemid)s has been retired')%{
                 'classname': self.classname.capitalize(), 'itemid': nodeid})
 
-    def permission(self):
-        """Determine whether the user has permission to retire this class.
-
-        Base behaviour is to check the user can edit this class.
-        """ 
-        return self.db.security.hasPermission('Edit', self.client.userid,
-                                              self.client.classname)
-
 class SearchAction(Action):
+    name = 'search'
+    permissionType = 'View'
+    
     def handle(self, wcre=re.compile(r'[\s,]+')):
         """Mangle some of the form variables.
 
@@ -101,11 +112,6 @@
         Split any String query values on whitespace and comma.
 
         """
-        # generic edit is per-class only
-        if not self.permission():
-            raise Unauthorised, _('You do not have permission to search %s' %
-                                  self.classname)
-
         self.fakeFilterVars()
         queryname = self.getQueryName()        
 
@@ -168,12 +174,11 @@
             if self.FV_QUERYNAME.match(key):
                 return self.form[key].value.strip()
         return ''
-        
-    def permission(self):
-        return self.db.security.hasPermission('View', self.client.userid,
-                                              self.client.classname)
 
 class EditCSVAction(Action):
+    name = 'edit'
+    permissionType = 'Edit'
+    
     def handle(self):
         """Performs an edit of all of a class' items in one go.
 
@@ -182,12 +187,6 @@
         removed lines are retired.
 
         """
-        # this is per-class only
-        if not self.permission():
-            self.client.error_message.append(
-                 _('You do not have permission to edit %s' %self.classname))
-            return
-
         # get the CSV module
         if rcsv.error:
             self.client.error_message.append(_(rcsv.error))
@@ -271,34 +270,27 @@
         self.db.commit()
 
         self.client.ok_message.append(_('Items edited OK'))
-
-    def permission(self):
-        return self.db.security.hasPermission('Edit', self.client.userid,
-                                              self.client.classname)
-
+    
 class _EditAction(Action):
+    def isEditingSelf(self):
+        """Check whether a user is editing his/her own details."""
+        return (self.nodeid == self.userid
+                and self.db.user.get(self.nodeid, 'username') != 'anonymous')
+    
     def editItemPermission(self, props):
         """Determine whether the user has permission to edit this item.
 
         Base behaviour is to check the user can edit this class. If we're
-        editing the"user" class, users are allowed to edit their own details.
+        editing the "user" class, users are allowed to edit their own details.
         Unless it's the "roles" property, which requires the special Permission
         "Web Roles".
         """
-        # if this is a user node and the user is editing their own node, then
-        # we're OK
-        has = self.db.security.hasPermission
         if self.classname == 'user':
-            # reject if someone's trying to edit "roles" and doesn't have the
-            # right permission.
-            if props.has_key('roles') and not has('Web Roles', self.userid,
-                    'user'):
-                return 0
-            # if the item being edited is the current user, we're ok
-            if (self.nodeid == self.userid
-                and self.db.user.get(self.nodeid, 'username') != 'anonymous'):
+            if props.has_key('roles') and not self.hasPermission('Web Roles'):
+                raise Unauthorised, _("You do not have permission to edit user roles")
+            if self.isEditingSelf():
                 return 1
-        if self.db.security.hasPermission('Edit', self.userid, self.classname):
+        if self.hasPermission('Edit'):
             return 1
         return 0
 
@@ -310,11 +302,8 @@
         if the user has the "Web Registration" Permission.
 
         """
-        has = self.db.security.hasPermission
-        if self.classname == 'user' and has('Web Registration', self.userid,
-                'user'):
-            return 1
-        if has('Edit', self.userid, self.classname):
+        if (self.classname == 'user' and self.hasPermission('Web Registration')
+            or self.hasPermission('Edit')):
             return 1
         return 0
 
@@ -503,7 +492,7 @@
         try:
             props, links = self.client.parsePropsFromForm(create=True)
         except (ValueError, KeyError), message:
-            self.error_message.append(_('Error: ') + str(message))
+            self.client.error_message.append(_('Error: ') + str(message))
             return
 
         # handle the props - edit or create
@@ -513,7 +502,7 @@
 
         except (ValueError, KeyError, IndexError), message:
             # these errors might just be indicative of user dumbness
-            self.error_message.append(_('Error: ') + str(message))
+            self.client.error_message.append(_('Error: ') + str(message))
             return
 
         # commit now that all the tricky stuff is done
@@ -656,17 +645,20 @@
                                                    self.userid, urllib.quote(message))
 
 class RegisterAction(Action):
+    name = 'register'
+    permissionType = 'Web Registration'
+    
     def handle(self):
         """Attempt to create a new user based on the contents of the form
         and then set the cookie.
 
         Return 1 on successful login.
-        """
+        """        
         props = self.client.parsePropsFromForm()[0][('user', None)]
 
-        # make sure we're allowed to register
-        if not self.permission(props):
-            raise Unauthorised, _("You do not have permission to register")
+        # registration isn't allowed to supply roles
+        if props.has_key('roles'):
+            raise Unauthorised, _("It is not permitted to supply roles at registration.")            
 
         try:
             self.db.user.lookup(props['username'])
@@ -717,19 +709,6 @@
         # redirect to the "you're almost there" page
         raise Redirect, '%suser?@template=rego_progress'%self.base
 
-    def permission(self, props):
-        """Determine whether the user has permission to register
-        
-        Base behaviour is to check the user has "Web Registration".
-        
-        """
-        # registration isn't allowed to supply roles
-        if props.has_key('roles'):
-            return 0
-        if self.db.security.hasPermission('Web Registration', self.userid):
-            return 1
-        return 0
-
 class LogoutAction(Action):
     def handle(self):
         """Make us really anonymous - nuke the cookie too."""
@@ -779,8 +758,9 @@
             self.client.error_message.append(_('Incorrect password'))
             return
 
-        # make sure we're allowed to be here
-        if not self.permission():
+        # Determine whether the user has permission to log in.
+        # Base behaviour is to check the user has "Web Access".
+        if not self.hasPermission("Web Access"):
             self.client.make_user_anonymous()
             self.client.error_message.append(_("You do not have permission to login"))
             return
@@ -800,13 +780,3 @@
         if not password and not stored:
             return 1
         return 0
-
-    def permission(self):
-        """Determine whether the user has permission to log in.
-
-        Base behaviour is to check the user has "Web Access".
-
-        """    
-        if not self.db.security.hasPermission('Web Access', self.client.userid):
-            return 0
-        return 1

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