Code

better detection of unset required props
[roundup.git] / roundup / security.py
index 7475ca62eb9067272f1088094522ee4cee47a452..e9f196efd74a7146e6c927b43c06f8c692976c55 100644 (file)
@@ -1,59 +1,53 @@
 import weakref
 
 import weakref
 
-from roundup import hyperdb, volatiledb
+from roundup import hyperdb
 
 
-class PermissionClass(volatiledb.VolatileClass):
-    ''' Include the default attributes:
-        - name (String)
-        - classname (String)
-        - description (String)
+class Permission:
+    ''' Defines a Permission with the attributes
+        - name
+        - description
+        - klass (optional)
 
 
-        The classname may be unset, indicating that this permission is not
+        The klass may be unset, indicating that this permission is not
         locked to a particular class. That means there may be multiple
         Permissions for the same name for different classes.
     '''
         locked to a particular class. That means there may be multiple
         Permissions for the same name for different classes.
     '''
-    def __init__(self, db, classname, **properties):
-        """ set up the default properties
-        """
-        if not properties.has_key('name'):
-            properties['name'] = hyperdb.String()
-        if not properties.has_key('klass'):
-            properties['klass'] = hyperdb.String()
-        if not properties.has_key('description'):
-            properties['description'] = hyperdb.String()
-        volatiledb.VolatileClass.__init__(self, db, classname, **properties)
-
-class RoleClass(volatiledb.VolatileClass):
-    ''' Include the default attributes:
-        - name (String, key)
-        - description (String)
-        - permissions (PermissionClass Multilink)
+    def __init__(self, name='', description='', klass=None):
+        self.name = name
+        self.description = description
+        self.klass = klass
+
+    def __repr__(self):
+        return '<Permission 0x%x %r,%r>'%(id(self), self.name, self.klass)
+
+class Role:
+    ''' Defines a Role with the attributes
+        - name
+        - description
+        - permissions
     '''
     '''
-    def __init__(self, db, classname, **properties):
-        """ set up the default properties
-        """
-        if not properties.has_key('name'):
-            properties['name'] = hyperdb.String()
-        if not properties.has_key('description'):
-            properties['description'] = hyperdb.String()
-        if not properties.has_key('permissions'):
-            properties['permissions'] = hyperdb.Multilink('permission')
-        volatiledb.VolatileClass.__init__(self, db, classname, **properties)
-        self.setkey('name')
+    def __init__(self, name='', description='', permissions=None):
+        self.name = name
+        self.description = description
+        if permissions is None:
+            permissions = []
+        self.permissions = permissions
+
+    def __repr__(self):
+        return '<Role 0x%x %r,%r>'%(id(self), self.name, self.permissions)
 
 class Security:
     def __init__(self, db):
         ''' Initialise the permission and role classes, and add in the
             base roles (for admin user).
         '''
 
 class Security:
     def __init__(self, db):
         ''' Initialise the permission and role classes, and add in the
             base roles (for admin user).
         '''
-        # use a weak ref to avoid circularity
-        self.db = weakref.proxy(db)
+        self.db = weakref.proxy(db)       # use a weak ref to avoid circularity
 
 
-        # create the permission class instance (we only need one))
-        self.permission = PermissionClass(db, "permission")
+        # permssions are mapped by name to a list of Permissions by class
+        self.permission = {}
 
 
-        # create the role class instance (we only need one)
-        self.role = RoleClass(db, "role")
+        # roles are mapped by name to the Role
+        self.role = {}
 
         # the default Roles
         self.addRole(name="User", description="A regular user, no privs")
 
         # the default Roles
         self.addRole(name="User", description="A regular user, no privs")
@@ -63,37 +57,58 @@ class Security:
         ee = self.addPermission(name="Edit",
             description="User may edit everthing")
         self.addPermissionToRole('Admin', ee)
         ee = self.addPermission(name="Edit",
             description="User may edit everthing")
         self.addPermissionToRole('Admin', ee)
-        ae = self.addPermission(name="Access",
+        ae = self.addPermission(name="View",
             description="User may access everything")
         self.addPermissionToRole('Admin', ae)
             description="User may access everything")
         self.addPermissionToRole('Admin', ae)
-        ae = self.addPermission(name="Assign",
-            description="User may be assigned to anything")
-        self.addPermissionToRole('Admin', ae)
         reg = self.addPermission(name="Register Web",
             description="User may register through the web")
         reg = self.addPermission(name="Register Web",
             description="User may register through the web")
-        self.addPermissionToRole('Anonymous', reg)
         reg = self.addPermission(name="Register Email",
             description="User may register through the email")
         reg = self.addPermission(name="Register Email",
             description="User may register through the email")
-        self.addPermissionToRole('Anonymous', reg)
 
         # initialise the permissions and roles needed for the UIs
 
         # initialise the permissions and roles needed for the UIs
-        from roundup import cgi_client, mailgw
-        cgi_client.initialiseSecurity(self)
+        from roundup.cgi import client
+        client.initialiseSecurity(self)
+        from roundup import mailgw
         mailgw.initialiseSecurity(self)
 
         mailgw.initialiseSecurity(self)
 
-    def hasClassPermission(self, classname, permission, userid):
+    def getPermission(self, permission, classname=None):
+        ''' Find the Permission matching the name and for the class, if the
+            classname is specified.
+
+            Raise ValueError if there is no exact match.
+        '''
+        if not self.permission.has_key(permission):
+            raise ValueError, 'No permission "%s" defined'%permission
+
+        # look through all the permissions of the given name
+        for perm in self.permission[permission]:
+            # if we're passed a classname, the permission must match
+            if perm.klass is not None and perm.klass == classname:
+                return perm
+            # otherwise the permission klass must be unset
+            elif not perm.klass and not classname:
+                return perm
+        raise ValueError, 'No permission "%s" defined for "%s"'%(permission,
+            classname)
+
+    def hasPermission(self, permission, userid, classname=None):
         ''' Look through all the Roles, and hence Permissions, and see if
             "permission" is there for the specified classname.
         ''' Look through all the Roles, and hence Permissions, and see if
             "permission" is there for the specified classname.
-
         '''
         roles = self.db.user.get(userid, 'roles')
         '''
         roles = self.db.user.get(userid, 'roles')
-        for roleid in roles:
-            for permissionid in self.db.role.get(roleid, 'permissions'):
-                if self.db.permission.get(permissionid, 'name') != permission:
-                    continue
-                klass = self.db.permission.get(permissionid, 'klass')
-                if klass is None or klass == classname:
-                    return 1
+        if roles is None:
+            return 0
+        for rolename in roles.split(','):
+            if not rolename:
+                continue
+            # for each of the user's Roles, check the permissions
+            for perm in self.role[rolename].permissions:
+                # permission name match?
+                if perm.name == permission:
+                    # permission klass match?
+                    if perm.klass is None or perm.klass == classname:
+                        # we have a winner
+                        return 1
         return 0
 
     def hasNodePermission(self, classname, nodeid, **propspec):
         return 0
 
     def hasNodePermission(self, classname, nodeid, **propspec):
@@ -124,20 +139,23 @@ class Security:
         ''' Create a new Permission with the properties defined in
             'propspec'
         '''
         ''' Create a new Permission with the properties defined in
             'propspec'
         '''
-        return self.db.permission.create(**propspec)
+        perm = Permission(**propspec)
+        self.permission.setdefault(perm.name, []).append(perm)
+        return perm
 
     def addRole(self, **propspec):
         ''' Create a new Role with the properties defined in 'propspec'
         '''
 
     def addRole(self, **propspec):
         ''' Create a new Role with the properties defined in 'propspec'
         '''
-        return self.db.role.create(**propspec)
+        role = Role(**propspec)
+        self.role[role.name] = role
+        return role
 
 
-    def addPermissionToRole(self, rolename, permissionid):
+    def addPermissionToRole(self, rolename, permission):
         ''' Add the permission to the role's permission list.
 
         ''' Add the permission to the role's permission list.
 
-            'rolename' is the name of the role to add 'permissionid'.
+            'rolename' is the name of the role to add the permission to.
         '''
         '''
-        roleid = self.db.role.lookup(rolename)
-        permissions = self.db.role.get(roleid, 'permissions')
-        permissions.append(permissionid)
-        self.db.role.set(roleid, permissions=permissions)
+        role = self.role[rolename]
+        role.permissions.append(permission)
 
 
+# vim: set filetype=python ts=4 sw=4 et si