Mercurial > p > roundup > code
comparison roundup/security.py @ 8472:224ccb8b49ca
refactor: change some classes to use __slots__
Speed up access to and reduce size of some low level classes. A few
classes in security.py, rest.py are heavily used. But for all, it
prevents adding random properties to lower level classes that people
shouldn't be mucking with. While doing this I found some test cases
accessing an invalid property name and this change caused the cases to
crash.
admin.py:
Use new method Role.props_dict() and Permission.props_dict() where
original code just referenced __dict__ when printing Role/Permission.
mlink_expr.py:
Add slots to multiple classes.
Classes Binary and Unary set real properties/attributes. Classes that
inherit from them (Equals, Empty, Not, Or, And) define empty slots
tuple to eliminate need for __dict__.
Class Expression also gets a slot.
rate_limit.py:
RateLimit and Gcra classes get slots.
A couple of pep8 fixes: sort imports, remove trailing spaces on a
line, remove unused noqa comment.
rest.py:
Add slots to class SimulateFieldStorageFromJson and FsValue
classes. The memory savings from this could be useful as well as
speedier access to the attributes.
security.py:
Add slots to Permission class. To prevent conflict between slot
limit_perm_to_props_only and the class variable of the same name,
rename the class variable to limit_perm_to_props_only_default.
Also define method props_dict() to allow other code to get a dict to
iterate over when checking permissions.
Add slots to class Role along with props_dict() method.
Add slots to class Security. Also have to add explicit __dict__ slot
to support test override of the hasPermission() method. Add
props_dict() method, currently unused, but added for symmetry.
support.py:
TruthDict and PrioList gets slots.
test/test_cgi.py:
Fix incorrect setting of permission property. Was setting
permissions. So testing may not have been doing what we thought it
was. Multiple places found with this typo.
Remove setting of permissions in some places where it should
have no effect on the test and looks like it was just copypasta.
test/test_xmlrpc.py
Remove setting of permissions in some places where it should
have no effect on the test and looks like it was just copypasta.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Mon, 03 Nov 2025 00:13:04 -0500 |
| parents | 43899d99fc4d |
| children | 19152fd94fcf |
comparison
equal
deleted
inserted
replaced
| 8471:8e72dc7b7f2f | 8472:224ccb8b49ca |
|---|---|
| 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 default value taken from Permission.limit_perm_to_props_only_default. | |
| 21 - filter function (optional) returns filter arguments for | 22 - filter function (optional) returns filter arguments for |
| 22 determining which records are visible by the user. The filter | 23 determining which records are visible by the user. The filter |
| 23 function comes into play when determining if a set of nodes | 24 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 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 the normal way would be to call the permissions for each item |
| 56 db.security.set_props_only_default() | 57 db.security.set_props_only_default() |
| 57 | 58 |
| 58 with a True or False value. | 59 with a True or False value. |
| 59 ''' | 60 ''' |
| 60 | 61 |
| 61 limit_perm_to_props_only = False | 62 __slots__ = ( |
| 63 "_properties_dict", | |
| 64 "check", | |
| 65 "check_version", | |
| 66 "description", | |
| 67 "filter", | |
| 68 "klass", | |
| 69 "limit_perm_to_props_only", | |
| 70 "name", | |
| 71 "properties") | |
| 72 | |
| 73 limit_perm_to_props_only_default = False | |
| 62 | 74 |
| 63 def __init__(self, name='', description='', klass=None, | 75 def __init__(self, name='', description='', klass=None, |
| 64 properties=None, check=None, props_only=None, filter=None): | 76 properties=None, check=None, props_only=None, filter=None): |
| 65 from roundup.anypy import findargspec | 77 from roundup.anypy import findargspec |
| 66 self.name = name | 78 self.name = name |
| 78 # b=Property(name="Edit", klass="issue", check=dummy, | 90 # b=Property(name="Edit", klass="issue", check=dummy, |
| 79 # props_only=False) | 91 # props_only=False) |
| 80 # a == b will be true. | 92 # a == b will be true. |
| 81 if props_only is None: | 93 if props_only is None: |
| 82 self.limit_perm_to_props_only = \ | 94 self.limit_perm_to_props_only = \ |
| 83 Permission.limit_perm_to_props_only | 95 Permission.limit_perm_to_props_only_default |
| 84 else: | 96 else: |
| 85 # see note on use of bool() in set_props_only_default() | 97 # see note on use of bool() in set_props_only_default() |
| 86 self.limit_perm_to_props_only = bool(props_only) | 98 self.limit_perm_to_props_only = bool(props_only) |
| 87 else: | 99 else: |
| 88 self.limit_perm_to_props_only = None | 100 self.limit_perm_to_props_only = None |
| 121 return True | 133 return True |
| 122 return False | 134 return False |
| 123 | 135 |
| 124 return check | 136 return check |
| 125 | 137 |
| 138 def props_dict(self): | |
| 139 return {name: getattr(self, name) for name in self.__slots__} | |
| 140 | |
| 126 def test(self, db, permission, classname, property, userid, itemid): | 141 def test(self, db, permission, classname, property, userid, itemid): |
| 127 ''' Test permissions 5 args: | 142 ''' Test permissions 5 args: |
| 128 permission - string like Edit, Register etc. Required, no wildcard. | 143 permission - string like Edit, Register etc. Required, no wildcard. |
| 129 classname - string like issue, msg etc. Can be None to match any | 144 classname - string like issue, msg etc. Can be None to match any |
| 130 class. | 145 class. |
| 220 ''' Defines a Role with the attributes | 235 ''' Defines a Role with the attributes |
| 221 - name | 236 - name |
| 222 - description | 237 - description |
| 223 - permissions | 238 - permissions |
| 224 ''' | 239 ''' |
| 240 | |
| 241 __slots__ = ("_permissions", "description", "name") | |
| 242 | |
| 225 def __init__(self, name='', description='', permissions=None): | 243 def __init__(self, name='', description='', permissions=None): |
| 226 self.name = name.lower() | 244 self.name = name.lower() |
| 227 self.description = description | 245 self.description = description |
| 228 # This is a dict of permission names each containing a dict of | 246 # This is a dict of permission names each containing a dict of |
| 229 # *class names*, with a special entry for non-class permissions | 247 # *class names*, with a special entry for non-class permissions |
| 298 for c in self._permissions[p]: | 316 for c in self._permissions[p]: |
| 299 for cond in (False, True): | 317 for cond in (False, True): |
| 300 perm_list.extend(self._permissions[p][c][cond]) | 318 perm_list.extend(self._permissions[p][c][cond]) |
| 301 perm_list.sort(key=lambda x: (x.klass or '', x.name)) | 319 perm_list.sort(key=lambda x: (x.klass or '', x.name)) |
| 302 return perm_list | 320 return perm_list |
| 321 | |
| 322 def props_dict(self): | |
| 323 return {name: getattr(self, name) for name in self.__slots__} | |
| 303 | 324 |
| 304 def searchable(self, classname, propname): | 325 def searchable(self, classname, propname): |
| 305 for perm_name in 'View', 'Search': | 326 for perm_name in 'View', 'Search': |
| 306 # Only permissions without a check method | 327 # Only permissions without a check method |
| 307 if perm_name not in self._permissions: | 328 if perm_name not in self._permissions: |
| 319 return True | 340 return True |
| 320 return False | 341 return False |
| 321 | 342 |
| 322 | 343 |
| 323 class Security: | 344 class Security: |
| 345 | |
| 346 # __dict__ is needed to allow mocking of db.security.hasPermission | |
| 347 # in test/test_templating.py. Define slots for properties used in | |
| 348 # production to increase speed. | |
| 349 __slots__ = ("__dict__", "db", "permission", "role") | |
| 350 | |
| 324 def __init__(self, db): | 351 def __init__(self, db): |
| 325 ''' Initialise the permission and role classes, and add in the | 352 ''' Initialise the permission and role classes, and add in the |
| 326 base roles (for admin user). | 353 base roles (for admin user). |
| 327 ''' | 354 ''' |
| 328 self.db = weakref.proxy(db) # use a weak ref to avoid circularity | 355 self.db = weakref.proxy(db) # use a weak ref to avoid circularity |
| 449 """ | 476 """ |
| 450 for perm in self.filter_iter(permission, userid, classname): | 477 for perm in self.filter_iter(permission, userid, classname): |
| 451 if not perm.filter: | 478 if not perm.filter: |
| 452 return False | 479 return False |
| 453 return True | 480 return True |
| 481 | |
| 482 def props_dict(self): | |
| 483 return {name: getattr(self, name) for name in self.__slots__} | |
| 454 | 484 |
| 455 def roleHasSearchPermission(self, classname, property, *rolenames): | 485 def roleHasSearchPermission(self, classname, property, *rolenames): |
| 456 """ For each of the given roles, check the permissions. | 486 """ For each of the given roles, check the permissions. |
| 457 Property can be a transitive property. | 487 Property can be a transitive property. |
| 458 """ | 488 """ |
| 542 if props_only is not None: | 572 if props_only is not None: |
| 543 # NOTE: only valid values are True and False because these | 573 # NOTE: only valid values are True and False because these |
| 544 # will be compared as part of tuple == tuple and | 574 # will be compared as part of tuple == tuple and |
| 545 # (3,) == (True,) is False even though 3 is a True value | 575 # (3,) == (True,) is False even though 3 is a True value |
| 546 # in a boolean context. So use bool() to coerce value. | 576 # in a boolean context. So use bool() to coerce value. |
| 547 Permission.limit_perm_to_props_only = \ | 577 Permission.limit_perm_to_props_only_default = \ |
| 548 bool(props_only) | 578 bool(props_only) |
| 549 | 579 |
| 550 def get_props_only_default(self): | 580 def get_props_only_default(self): |
| 551 return Permission.limit_perm_to_props_only | 581 return Permission.limit_perm_to_props_only_default |
| 552 | 582 |
| 553 def addPermissionToRole(self, rolename, permission, classname=None, | 583 def addPermissionToRole(self, rolename, permission, classname=None, |
| 554 properties=None, check=None, props_only=None): | 584 properties=None, check=None, props_only=None): |
| 555 ''' Add the permission to the role's permission list. | 585 ''' Add the permission to the role's permission list. |
| 556 | 586 |
