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 """

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