Mercurial > p > roundup > code
comparison roundup/security.py @ 3117:460eb0209a9e
Permissions improvements.
- have Permissions only test the check function if itemid is suppled
- modify index templates to check for row-level Permission
- more documentation of security mechanisms
- better unit tests for security mechanisms
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Fri, 28 Jan 2005 03:51:19 +0000 |
| parents | ece73371713c |
| children | 75dc225613cc |
comparison
equal
deleted
inserted
replaced
| 3115:ece73371713c | 3117:460eb0209a9e |
|---|---|
| 46 # what about property? | 46 # what about property? |
| 47 if property is not None and not self._properties_dict[property]: | 47 if property is not None and not self._properties_dict[property]: |
| 48 return 0 | 48 return 0 |
| 49 | 49 |
| 50 # check code | 50 # check code |
| 51 if self.check is not None: | 51 if itemid is not None and self.check is not None: |
| 52 if not self.check(db, userid, itemid): | 52 if not self.check(db, userid, itemid): |
| 53 return 0 | 53 return 0 |
| 54 | 54 |
| 55 # we have a winner | 55 # we have a winner |
| 56 return 1 | 56 return 1 |
| 57 | 57 |
| 58 def __repr__(self): | 58 def __repr__(self): |
| 59 return '<Permission 0x%x %r,%r,%r,%r>'%(id(self), self.name, | 59 return '<Permission 0x%x %r,%r,%r,%r>'%(id(self), self.name, |
| 60 self.klass, self.properties, self.check) | 60 self.klass, self.properties, self.check) |
| 61 | |
| 62 def __cmp__(self, other): | |
| 63 if self.name != other.name: | |
| 64 return cmp(self.name, other.name) | |
| 65 | |
| 66 if self.klass != other.klass: return 1 | |
| 67 if self.properties != other.properties: return 1 | |
| 68 if self.check != other.check: return 1 | |
| 69 | |
| 70 # match | |
| 71 return 0 | |
| 61 | 72 |
| 62 class Role: | 73 class Role: |
| 63 ''' Defines a Role with the attributes | 74 ''' Defines a Role with the attributes |
| 64 - name | 75 - name |
| 65 - description | 76 - description |
| 107 from roundup.cgi import client | 118 from roundup.cgi import client |
| 108 client.initialiseSecurity(self) | 119 client.initialiseSecurity(self) |
| 109 from roundup import mailgw | 120 from roundup import mailgw |
| 110 mailgw.initialiseSecurity(self) | 121 mailgw.initialiseSecurity(self) |
| 111 | 122 |
| 112 def getPermission(self, permission, classname=None): | 123 def getPermission(self, permission, classname=None, properties=None, |
| 124 check=None): | |
| 113 ''' Find the Permission matching the name and for the class, if the | 125 ''' Find the Permission matching the name and for the class, if the |
| 114 classname is specified. | 126 classname is specified. |
| 115 | 127 |
| 116 Raise ValueError if there is no exact match. | 128 Raise ValueError if there is no exact match. |
| 117 ''' | 129 ''' |
| 123 self.db.getclass(classname) | 135 self.db.getclass(classname) |
| 124 except KeyError: | 136 except KeyError: |
| 125 raise ValueError, 'No class "%s" defined'%classname | 137 raise ValueError, 'No class "%s" defined'%classname |
| 126 | 138 |
| 127 # look through all the permissions of the given name | 139 # look through all the permissions of the given name |
| 140 tester = Permission(permission, klass=classname, properties=properties, | |
| 141 check=check) | |
| 128 for perm in self.permission[permission]: | 142 for perm in self.permission[permission]: |
| 129 # if we're passed a classname, the permission must match | 143 if perm == tester: |
| 130 if perm.klass is not None and perm.klass == classname: | |
| 131 return perm | |
| 132 # otherwise the permission klass must be unset | |
| 133 elif not perm.klass and not classname: | |
| 134 return perm | 144 return perm |
| 135 raise ValueError, 'No permission "%s" defined for "%s"'%(permission, | 145 raise ValueError, 'No permission "%s" defined for "%s"'%(permission, |
| 136 classname) | 146 classname) |
| 137 | 147 |
| 138 def hasPermission(self, permission, userid, classname=None, | 148 def hasPermission(self, permission, userid, classname=None, |
| 139 property=None, itemid=None): | 149 property=None, itemid=None): |
| 140 ''' Look through all the Roles, and hence Permissions, and see if | 150 '''Look through all the Roles, and hence Permissions, and |
| 141 "permission" is there for the specified classname. | 151 see if "permission" exists given the constraints of |
| 152 classname, property and itemid. | |
| 153 | |
| 154 If classname is specified (and only classname) then the | |
| 155 search will match if there is *any* Permission for that | |
| 156 classname, even if the Permission has additional | |
| 157 constraints. | |
| 158 | |
| 159 If property is specified, the Permission matched must have | |
| 160 either no properties listed or the property must appear in | |
| 161 the list. | |
| 162 | |
| 163 If itemid is specified, the Permission matched must have | |
| 164 either no check function defined or the check function, | |
| 165 when invoked, must return a True value. | |
| 166 | |
| 167 Note that this functionality is actually implemented by the | |
| 168 Permission.test() method. | |
| 142 ''' | 169 ''' |
| 143 roles = self.db.user.get(userid, 'roles') | 170 roles = self.db.user.get(userid, 'roles') |
| 144 if roles is None: | 171 if roles is None: |
| 145 return 0 | 172 return 0 |
| 146 if itemid and classname is None: | 173 if itemid and classname is None: |
| 148 for rolename in [x.lower().strip() for x in roles.split(',')]: | 175 for rolename in [x.lower().strip() for x in roles.split(',')]: |
| 149 if not rolename or not self.role.has_key(rolename): | 176 if not rolename or not self.role.has_key(rolename): |
| 150 continue | 177 continue |
| 151 # for each of the user's Roles, check the permissions | 178 # for each of the user's Roles, check the permissions |
| 152 for perm in self.role[rolename].permissions: | 179 for perm in self.role[rolename].permissions: |
| 153 # permission name match? | 180 # permission match? |
| 154 if perm.test(self.db, permission, classname, property, | 181 if perm.test(self.db, permission, classname, property, |
| 155 userid, itemid): | 182 userid, itemid): |
| 156 return 1 | 183 return 1 |
| 157 return 0 | 184 return 0 |
| 158 | 185 |
| 159 def hasNodePermission(self, classname, nodeid, **propspec): | |
| 160 ''' Check the named properties of the given node to see if the | |
| 161 userid appears in them. If it does, then the user is granted | |
| 162 this permission check. | |
| 163 | |
| 164 'propspec' consists of a set of properties and values that | |
| 165 must be present on the given node for access to be granted. | |
| 166 | |
| 167 If a property is a Link, the value must match the property | |
| 168 value. If a property is a Multilink, the value must appear | |
| 169 in the Multilink list. | |
| 170 ''' | |
| 171 klass = self.db.getclass(classname) | |
| 172 properties = klass.getprops() | |
| 173 for k,v in propspec.items(): | |
| 174 value = klass.get(nodeid, k) | |
| 175 if isinstance(properties[k], hyperdb.Multilink): | |
| 176 if v not in value: | |
| 177 return 0 | |
| 178 else: | |
| 179 if v != value: | |
| 180 return 0 | |
| 181 return 1 | |
| 182 | |
| 183 def addPermission(self, **propspec): | 186 def addPermission(self, **propspec): |
| 184 ''' Create a new Permission with the properties defined in | 187 ''' Create a new Permission with the properties defined in |
| 185 'propspec' | 188 'propspec'. See the Permission class for the possible |
| 189 keyword args. | |
| 186 ''' | 190 ''' |
| 187 perm = Permission(**propspec) | 191 perm = Permission(**propspec) |
| 188 self.permission.setdefault(perm.name, []).append(perm) | 192 self.permission.setdefault(perm.name, []).append(perm) |
| 189 return perm | 193 return perm |
| 190 | 194 |
| 193 ''' | 197 ''' |
| 194 role = Role(**propspec) | 198 role = Role(**propspec) |
| 195 self.role[role.name] = role | 199 self.role[role.name] = role |
| 196 return role | 200 return role |
| 197 | 201 |
| 198 def addPermissionToRole(self, rolename, permission, classname=None): | 202 def addPermissionToRole(self, rolename, permission, classname=None, |
| 203 properties=None, check=None): | |
| 199 ''' Add the permission to the role's permission list. | 204 ''' Add the permission to the role's permission list. |
| 200 | 205 |
| 201 'rolename' is the name of the role to add the permission to. | 206 'rolename' is the name of the role to add the permission to. |
| 202 | 207 |
| 203 'permission' is either a Permission *or* a permission name | 208 'permission' is either a Permission *or* a permission name |
| 204 accompanied by 'classname' (thus in the second case a Permission | 209 accompanied by 'classname' (thus in the second case a Permission |
| 205 is obtained by passing 'permission' and 'classname' to | 210 is obtained by passing 'permission' and 'classname' to |
| 206 self.getPermission) | 211 self.getPermission) |
| 207 ''' | 212 ''' |
| 208 if not isinstance(permission, Permission): | 213 if not isinstance(permission, Permission): |
| 209 permission = self.getPermission(permission, classname) | 214 permission = self.getPermission(permission, classname, |
| 215 properties, check) | |
| 210 role = self.role[rolename.lower()] | 216 role = self.role[rolename.lower()] |
| 211 role.permissions.append(permission) | 217 role.permissions.append(permission) |
| 212 | 218 |
| 213 # vim: set filetype=python sts=4 sw=4 et si : | 219 # vim: set filetype=python sts=4 sw=4 et si : |
