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

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