Mercurial > p > roundup > code
comparison roundup/security.py @ 8299:43899d99fc4d
refactor(ruff): multiple changes to clear ruff issues
Fix a couple of missing returns of booleans for security checks.
Turns an implicit return None into an explicit return False.
Fix loop index variable being reassigned inside loop by renaming index
variable. 2 instances.
Consolidate 2 isinstance calls to 1 with tuple class argument.
Replace dict(list comprehension) with dict conprehension.
Variable renames.
Removal of unused variable.
Whitespace fixes.
sort imports
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Wed, 22 Jan 2025 10:10:39 -0500 |
| parents | 302c797756e6 |
| children | 224ccb8b49ca |
comparison
equal
deleted
inserted
replaced
| 8298:cfa7d43a3658 | 8299:43899d99fc4d |
|---|---|
| 1 """Handle the security declarations used in Roundup trackers. | 1 """Handle the security declarations used in Roundup trackers. |
| 2 """ | 2 """ |
| 3 __docformat__ = 'restructuredtext' | 3 __docformat__ = 'restructuredtext' |
| 4 | 4 |
| 5 import logging | |
| 5 import weakref | 6 import weakref |
| 6 | 7 |
| 7 from roundup import hyperdb, support | 8 from roundup import hyperdb, support |
| 8 | 9 |
| 9 import logging | |
| 10 logger = logging.getLogger('roundup.security') | 10 logger = logging.getLogger('roundup.security') |
| 11 | 11 |
| 12 | 12 |
| 13 class Permission: | 13 class Permission: |
| 14 ''' Defines a Permission with the attributes | 14 ''' Defines a Permission with the attributes |
| 117 cls = db.getclass(klass) | 117 cls = db.getclass(klass) |
| 118 args = filter_function(db, userid, cls) | 118 args = filter_function(db, userid, cls) |
| 119 for a in args: | 119 for a in args: |
| 120 if cls.filter([itemid], **a): | 120 if cls.filter([itemid], **a): |
| 121 return True | 121 return True |
| 122 return False | |
| 123 | |
| 122 return check | 124 return check |
| 123 | 125 |
| 124 def test(self, db, permission, classname, property, userid, itemid): | 126 def test(self, db, permission, classname, property, userid, itemid): |
| 125 ''' Test permissions 5 args: | 127 ''' Test permissions 5 args: |
| 126 permission - string like Edit, Register etc. Required, no wildcard. | 128 permission - string like Edit, Register etc. Required, no wildcard. |
| 239 | 241 |
| 240 def __repr__(self): | 242 def __repr__(self): |
| 241 pl = self.permission_list() | 243 pl = self.permission_list() |
| 242 return '<Role 0x%x %r,%r>' % (id(self), self.name, pl) | 244 return '<Role 0x%x %r,%r>' % (id(self), self.name, pl) |
| 243 | 245 |
| 244 def addPermission (self, *permissions): | 246 def addPermission(self, *permissions): |
| 245 for p in permissions: | 247 for p in permissions: |
| 246 pn = p.name | 248 pn = p.name |
| 247 self._permissions.setdefault(pn, {}) | 249 self._permissions.setdefault(pn, {}) |
| 248 cn = p.klass | 250 cn = p.klass |
| 249 if p.klass not in self._permissions[pn]: | 251 if p.klass not in self._permissions[pn]: |
| 250 self._permissions[pn][cn] = dict (((False, []), (True, []))) | 252 self._permissions[pn][cn] = {False: [], True: []} |
| 251 self._permissions[pn][cn][bool(p.check)].append(p) | 253 self._permissions[pn][cn][bool(p.check)].append(p) |
| 252 | 254 |
| 253 def filter_iter (self, permission, classname): | 255 def filter_iter(self, permission, classname): |
| 254 """ Loop over all permissions for the current role on the class | 256 """ Loop over all permissions for the current role on the class |
| 255 with a check method (and props_only False). | 257 with a check method (and props_only False). |
| 256 """ | 258 """ |
| 257 if permission not in self._permissions: | 259 if permission not in self._permissions: |
| 258 return | 260 return |
| 263 for p in perms: | 265 for p in perms: |
| 264 if p.limit_perm_to_props_only and p.properties: | 266 if p.limit_perm_to_props_only and p.properties: |
| 265 continue | 267 continue |
| 266 yield p | 268 yield p |
| 267 | 269 |
| 268 def hasPermission (self, db, perm, uid, classname, property, itemid, chk): | 270 def hasPermission(self, db, perm, uid, classname, property, itemid, chk): |
| 269 # if itemid is given a classname must, too, checked in caller | 271 # if itemid is given a classname must, too, checked in caller |
| 270 if itemid and classname is None: | 272 if itemid and classname is None: |
| 271 raise ValueError('classname must accompany itemid') | 273 raise ValueError('classname must accompany itemid') |
| 272 | 274 |
| 273 perms = self._permissions | 275 perms = self._permissions |
| 285 for p in perms[perm][classname][chk]: | 287 for p in perms[perm][classname][chk]: |
| 286 # permission match? | 288 # permission match? |
| 287 if p.test(db, perm, classname, property, uid, itemid): | 289 if p.test(db, perm, classname, property, uid, itemid): |
| 288 return True | 290 return True |
| 289 | 291 |
| 290 def permission_list (self): | 292 return False |
| 293 | |
| 294 def permission_list(self): | |
| 291 """ Used for reporting in admin tool """ | 295 """ Used for reporting in admin tool """ |
| 292 l = [] | 296 perm_list = [] |
| 293 for p in self._permissions: | 297 for p in self._permissions: |
| 294 for c in self._permissions[p]: | 298 for c in self._permissions[p]: |
| 295 for cond in (False, True): | 299 for cond in (False, True): |
| 296 l.extend (self._permissions[p][c][cond]) | 300 perm_list.extend(self._permissions[p][c][cond]) |
| 297 l.sort (key = lambda x: (x.klass or '', x.name)) | 301 perm_list.sort(key=lambda x: (x.klass or '', x.name)) |
| 298 return l | 302 return perm_list |
| 299 | 303 |
| 300 def searchable (self, classname, propname): | 304 def searchable(self, classname, propname): |
| 301 for perm in 'View', 'Search': | 305 for perm_name in 'View', 'Search': |
| 302 # Only permissions without a check method | 306 # Only permissions without a check method |
| 303 if perm not in self._permissions: | 307 if perm_name not in self._permissions: |
| 304 continue | 308 continue |
| 305 p = self._permissions[perm] | 309 perms = self._permissions[perm_name] |
| 306 if classname not in p and None not in p: | 310 if classname not in perms and None not in perms: |
| 307 continue | 311 continue |
| 308 if None in p: | 312 if None in perms: |
| 309 for p in p[None][False]: | 313 for p in perms[None][False]: |
| 310 if p.searchable(classname, propname): | 314 if p.searchable(classname, propname): |
| 311 return True | 315 return True |
| 312 if classname in p: | 316 if classname in perms: |
| 313 for p in p[classname][False]: | 317 for p in perms[classname][False]: |
| 314 if p.searchable(classname, propname): | 318 if p.searchable(classname, propname): |
| 315 return True | 319 return True |
| 320 return False | |
| 316 | 321 |
| 317 | 322 |
| 318 class Security: | 323 class Security: |
| 319 def __init__(self, db): | 324 def __init__(self, db): |
| 320 ''' Initialise the permission and role classes, and add in the | 325 ''' Initialise the permission and role classes, and add in the |
| 332 self.addRole(name="User", description="A regular user, no privs") | 337 self.addRole(name="User", description="A regular user, no privs") |
| 333 self.addRole(name="Admin", description="An admin user, full privs") | 338 self.addRole(name="Admin", description="An admin user, full privs") |
| 334 self.addRole(name="Anonymous", description="An anonymous user") | 339 self.addRole(name="Anonymous", description="An anonymous user") |
| 335 | 340 |
| 336 # default permissions - Admin may do anything | 341 # default permissions - Admin may do anything |
| 337 for p in 'create edit restore retire view'.split(): | 342 for perm_name in 'create edit restore retire view'.split(): |
| 338 p = self.addPermission(name=p.title(), | 343 p = self.addPermission(name=perm_name.title(), |
| 339 description="User may %s everything" % p) | 344 description="User may %s everything" % |
| 345 perm_name) | |
| 340 self.addPermissionToRole('Admin', p) | 346 self.addPermissionToRole('Admin', p) |
| 341 | 347 |
| 342 # initialise the permissions and roles needed for the UIs | 348 # initialise the permissions and roles needed for the UIs |
| 343 from roundup.cgi import client | 349 from roundup.cgi import client |
| 344 client.initialiseSecurity(self) | 350 client.initialiseSecurity(self) |
| 439 filter method. We only consider permissions with props_only | 445 filter method. We only consider permissions with props_only |
| 440 set to False. Note that this will return True if there are | 446 set to False. Note that this will return True if there are |
| 441 no permissions with a check method found, the performed | 447 no permissions with a check method found, the performed |
| 442 checks later will find no matching records. | 448 checks later will find no matching records. |
| 443 """ | 449 """ |
| 444 for perm in self.filter_iter (permission, userid, classname): | 450 for perm in self.filter_iter(permission, userid, classname): |
| 445 if not perm.filter: | 451 if not perm.filter: |
| 446 return False | 452 return False |
| 447 return True | 453 return True |
| 448 | 454 |
| 449 def roleHasSearchPermission(self, classname, property, *rolenames): | 455 def roleHasSearchPermission(self, classname, property, *rolenames): |
| 450 """ For each of the given roles, check the permissions. | 456 """ For each of the given roles, check the permissions. |
| 451 Property can be a transitive property. | 457 Property can be a transitive property. |
| 452 """ | 458 """ |
| 453 perms = [] | |
| 454 # Note: break from inner loop means "found" | 459 # Note: break from inner loop means "found" |
| 455 # break from outer loop means "not found" | 460 # break from outer loop means "not found" |
| 456 cn = classname | 461 cn = classname |
| 457 prev = None | 462 prev = None |
| 458 prop = None | 463 prop = None |
| 476 else: | 481 else: |
| 477 break | 482 break |
| 478 else: | 483 else: |
| 479 # for Link and Multilink require search permission on label- | 484 # for Link and Multilink require search permission on label- |
| 480 # and order-properties and on ID | 485 # and order-properties and on ID |
| 481 if isinstance(prop, Multilink) or isinstance(prop, Link): | 486 if isinstance(prop, (Link, Multilink)): |
| 482 try: | 487 try: |
| 483 cls = self.db.getclass(prop.classname) | 488 cls = self.db.getclass(prop.classname) |
| 484 except KeyError: | 489 except KeyError: |
| 485 return 0 | 490 return 0 |
| 486 props = dict.fromkeys(('id', cls.labelprop(), cls.orderprop())) | 491 props = dict.fromkeys(('id', cls.labelprop(), cls.orderprop())) |
| 487 for p in props.keys(): | 492 for p in props: |
| 488 for rn in rolenames: | 493 for rn in rolenames: |
| 489 if self.role[rn].searchable(prop.classname, p): | 494 if self.role[rn].searchable(prop.classname, p): |
| 490 break | 495 break |
| 491 else: | 496 else: |
| 492 return 0 | 497 return 0 |
| 566 # filterspec or sort/group list | 571 # filterspec or sort/group list |
| 567 | 572 |
| 568 def filterFilterspec(self, userid, classname, filterspec): | 573 def filterFilterspec(self, userid, classname, filterspec): |
| 569 """ Return a filterspec that has all non-allowed properties removed. | 574 """ Return a filterspec that has all non-allowed properties removed. |
| 570 """ | 575 """ |
| 571 return dict([(k, v) for k, v in filterspec.items() | 576 return {k: v for k, v in filterspec.items() |
| 572 if self.hasSearchPermission(userid, classname, k)]) | 577 if self.hasSearchPermission(userid, classname, k)} |
| 573 | 578 |
| 574 def filterSortspec(self, userid, classname, sort): | 579 def filterSortspec(self, userid, classname, sort): |
| 575 """ Return a sort- or group-list that has all non-allowed properties | 580 """ Return a sort- or group-list that has all non-allowed properties |
| 576 removed. | 581 removed. |
| 577 """ | 582 """ |
