Mercurial > p > roundup > code
comparison roundup/security.py @ 8125:b358da7c89e5 permission-performance
Optimize filtering of search results
Now the Permission class constructor takes an optional argument
'filter'. Now if we do not find a permission on the whole class *and*
all permission objects on the current class with a check method also
have a filter method we can improve search performance by filtering in
the database (instead of in python).
| author | Ralf Schlatterbeck <rsc@runtux.com> |
|---|---|
| date | Mon, 21 Oct 2024 16:11:13 +0200 |
| parents | 2a4d0413bd20 |
| children | 8e9181dfc9fa |
comparison
equal
deleted
inserted
replaced
| 8121:2a4d0413bd20 | 8125:b358da7c89e5 |
|---|---|
| 16 - description | 16 - description |
| 17 - klass (optional) | 17 - klass (optional) |
| 18 - properties (optional) | 18 - properties (optional) |
| 19 - check function (optional) | 19 - check function (optional) |
| 20 - props_only (optional, internal field is limit_perm_to_props_only) | 20 - props_only (optional, internal field is limit_perm_to_props_only) |
| 21 - filter function (optional) returns filter arguments for | |
| 22 determining which records are visible by the user. The filter | |
| 23 function comes into play when determining if a set of nodes | |
| 24 found via a filter call of a class can be seen by the user -- | |
| 25 the normal way would be to call the permissions for each item | |
| 26 found, the filter call performs this on the database for all | |
| 27 nodes. | |
| 28 Signature of the filter function: | |
| 29 filter(db, userid, klass) | |
| 30 The filter must return a list of dictionaries with filter parameters. | |
| 31 Note that sort and group parameters of the filter call should | |
| 32 not be set by filter method (they will be overwritten) and the | |
| 33 parameter search_matches must not be set. | |
| 34 An empty list returned means no access for this filter method. | |
| 21 | 35 |
| 22 The klass may be unset, indicating that this permission is not | 36 The klass may be unset, indicating that this permission is not |
| 23 locked to a particular class. That means there may be multiple | 37 locked to a particular class. That means there may be multiple |
| 24 Permissions for the same name for different classes. | 38 Permissions for the same name for different classes. |
| 25 | 39 |
| 45 ''' | 59 ''' |
| 46 | 60 |
| 47 limit_perm_to_props_only = False | 61 limit_perm_to_props_only = False |
| 48 | 62 |
| 49 def __init__(self, name='', description='', klass=None, | 63 def __init__(self, name='', description='', klass=None, |
| 50 properties=None, check=None, props_only=None): | 64 properties=None, check=None, props_only=None, filter=None): |
| 51 from roundup.anypy import findargspec | 65 from roundup.anypy import findargspec |
| 52 self.name = name | 66 self.name = name |
| 53 self.description = description | 67 self.description = description |
| 54 self.klass = klass | 68 self.klass = klass |
| 55 self.properties = properties | 69 self.properties = properties |
| 56 self._properties_dict = support.TruthDict(properties) | 70 self._properties_dict = support.TruthDict(properties) |
| 57 self.check = check | 71 self.check = check |
| 72 self.filter = filter | |
| 58 if properties is not None: | 73 if properties is not None: |
| 59 # Set to None unless properties are defined. | 74 # Set to None unless properties are defined. |
| 60 # This means that: | 75 # This means that: |
| 61 # a=Property(name="Edit", klass="issue", check=dummy, | 76 # a=Property(name="Edit", klass="issue", check=dummy, |
| 62 # props_only=True) | 77 # props_only=True) |
| 209 cn = p.klass | 224 cn = p.klass |
| 210 if p.klass not in self._permissions[pn]: | 225 if p.klass not in self._permissions[pn]: |
| 211 self._permissions[pn][cn] = dict (((False, []), (True, []))) | 226 self._permissions[pn][cn] = dict (((False, []), (True, []))) |
| 212 self._permissions[pn][cn][bool(p.check)].append(p) | 227 self._permissions[pn][cn][bool(p.check)].append(p) |
| 213 | 228 |
| 229 def filter_iter (self, permission, classname): | |
| 230 """ Loop over all permissions for the current role on the class | |
| 231 with a check method (and props_only False). | |
| 232 """ | |
| 233 if permission not in self._permissions: | |
| 234 return | |
| 235 for c in (None, classname): | |
| 236 if c not in self._permissions[permission]: | |
| 237 continue | |
| 238 perms = self._permissions[permission][c][True] | |
| 239 for p in perms: | |
| 240 if p.limit_perm_to_props_only and p.properties: | |
| 241 continue | |
| 242 yield p | |
| 243 | |
| 214 def hasPermission (self, db, perm, uid, classname, property, itemid, chk): | 244 def hasPermission (self, db, perm, uid, classname, property, itemid, chk): |
| 215 # if itemid is given a classname must, too, checked in caller | 245 # if itemid is given a classname must, too, checked in caller |
| 216 assert not itemid or classname | 246 assert not itemid or classname |
| 217 perms = self._permissions | 247 perms = self._permissions |
| 218 if perm not in perms: | 248 if perm not in perms: |
| 281 from roundup.cgi import client | 311 from roundup.cgi import client |
| 282 client.initialiseSecurity(self) | 312 client.initialiseSecurity(self) |
| 283 from roundup import mailgw | 313 from roundup import mailgw |
| 284 mailgw.initialiseSecurity(self) | 314 mailgw.initialiseSecurity(self) |
| 285 | 315 |
| 316 def filter_iter(self, permission, userid, classname): | |
| 317 """ Loop over all permissions for the current user on the class | |
| 318 with a check method (and props_only False). | |
| 319 """ | |
| 320 for rolename in self.db.user.get_roles(userid): | |
| 321 if not rolename or (rolename not in self.role): | |
| 322 continue | |
| 323 r = self.role[rolename] | |
| 324 for perm in r.filter_iter(permission, classname): | |
| 325 yield perm | |
| 326 | |
| 286 def getPermission(self, permission, classname=None, properties=None, | 327 def getPermission(self, permission, classname=None, properties=None, |
| 287 check=None, props_only=None): | 328 check=None, props_only=None): |
| 288 ''' Find the Permission matching the name and for the class, if the | 329 ''' Find the Permission matching the name and for the class, if the |
| 289 classname is specified. | 330 classname is specified. |
| 290 | 331 |
| 356 v = r.hasPermission(self.db, permission, userid, classname, | 397 v = r.hasPermission(self.db, permission, userid, classname, |
| 357 property, itemid, has_check) | 398 property, itemid, has_check) |
| 358 if v: | 399 if v: |
| 359 return v | 400 return v |
| 360 return False | 401 return False |
| 402 | |
| 403 def is_filterable(self, permission, userid, classname): | |
| 404 """ Check if all permissions for the current user on the class | |
| 405 with a check method (and props_only False) also have a | |
| 406 filter method. We only consider permissions with props_only | |
| 407 set to False. Note that this will return True if there are | |
| 408 no permissions with a check method found, the performed | |
| 409 checks later will find no matching records. | |
| 410 """ | |
| 411 for perm in self.filter_iter (permission, userid, classname): | |
| 412 if not perm.filter: | |
| 413 return False | |
| 414 return True | |
| 361 | 415 |
| 362 def roleHasSearchPermission(self, classname, property, *rolenames): | 416 def roleHasSearchPermission(self, classname, property, *rolenames): |
| 363 """ For each of the given roles, check the permissions. | 417 """ For each of the given roles, check the permissions. |
| 364 Property can be a transitive property. | 418 Property can be a transitive property. |
| 365 """ | 419 """ |
