Skip to content

Commit 5a50d36

Browse files
committed
Change permission representation
Now permissions are checked in different order. Permissions without a check method (which are cheap to check) are checked first. Only if no permission is found do we check permissions with check methods.
1 parent 84be03b commit 5a50d36

File tree

5 files changed

+160
-90
lines changed

5 files changed

+160
-90
lines changed

roundup/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1920,7 +1920,7 @@ def do_security(self, args):
19201920
roles.sort()
19211921
for _rolename, role in roles:
19221922
sys.stdout.write(_('Role "%(name)s":\n') % role.__dict__)
1923-
for permission in role.permissions:
1923+
for permission in role.permission_list():
19241924
d = permission.__dict__
19251925
if permission.klass:
19261926
if permission.properties:

roundup/security.py

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,73 @@ class Role:
184184
def __init__(self, name='', description='', permissions=None):
185185
self.name = name.lower()
186186
self.description = description
187+
# This is a dict of permission names each containing a dict of
188+
# *class names*, with a special entry for non-class permissions
189+
# where the key is None. In each class dict we have a dictionary
190+
# with the values True and False for permission with and without
191+
# a check method. These dicts each contain a list of permissions.
187192
if permissions is None:
188-
permissions = []
189-
self.permissions = permissions
193+
self._permissions = {}
194+
elif isinstance(permissions, list):
195+
self._permissions = {}
196+
for p in permissions:
197+
self.addPermission(p)
198+
else:
199+
raise ValueError("Invalid permissions for Role: %s" % permissions)
190200

191201
def __repr__(self):
192-
return '<Role 0x%x %r,%r>' % (id(self), self.name, self.permissions)
202+
pl = self.permission_list()
203+
return '<Role 0x%x %r,%r>' % (id(self), self.name, pl)
204+
205+
def addPermission (self, *permissions):
206+
for p in permissions:
207+
pn = p.name
208+
self._permissions.setdefault(pn, {})
209+
cn = p.klass
210+
if p.klass not in self._permissions[pn]:
211+
self._permissions[pn][cn] = dict (((False, []), (True, [])))
212+
self._permissions[pn][cn][bool(p.check)].append(p)
213+
214+
def hasPermission (self, db, perm, uid, classname, property, itemid, chk):
215+
# if itemid is given a classname must, too, checked in caller
216+
assert not itemid or classname
217+
perms = self._permissions
218+
if perm not in perms:
219+
return False
220+
# If we have a classname we also need to check permission with
221+
# an empty classname (e.g. 'admin' has access on everything)
222+
if classname is not None and None in perms[perm]:
223+
for p in perms[perm][None][chk]:
224+
# permission match?
225+
if p.test(db, perm, classname, property, uid, itemid):
226+
return True
227+
if classname not in perms[perm]:
228+
return False
229+
for p in perms[perm][classname][chk]:
230+
# permission match?
231+
if p.test(db, perm, classname, property, uid, itemid):
232+
return True
233+
234+
def permission_list (self):
235+
""" Used for reporting in admin tool """
236+
l = []
237+
for p in self._permissions:
238+
for c in self._permissions[p]:
239+
for cond in (False, True):
240+
l.extend (self._permissions[p][c][cond])
241+
l.sort (key = lambda x: (x.klass or '', x.name))
242+
return l
243+
244+
def searchable (self, classname, propname):
245+
for perm in 'View', 'Search':
246+
# Only permissions without a check method
247+
if perm not in self._permissions:
248+
continue
249+
if classname not in self._permissions[perm]:
250+
continue
251+
for p in self._permissions[perm][classname][False]:
252+
if p.searchable(classname, propname):
253+
return True
193254

194255

195256
class Security:
@@ -199,7 +260,7 @@ def __init__(self, db):
199260
'''
200261
self.db = weakref.proxy(db) # use a weak ref to avoid circularity
201262

202-
# permssions are mapped by name to a list of Permissions by class
263+
# Permissions are mapped by name to a list of Permissions by class
203264
self.permission = {}
204265

205266
# roles are mapped by name to the Role
@@ -278,28 +339,30 @@ def hasPermission(self, permission, userid, classname=None,
278339
Permission.test() method.
279340
280341
'''
342+
if classname == 'status':
343+
import pdb; pdb.set_trace()
281344
if itemid and classname is None:
282345
raise ValueError('classname must accompany itemid')
283-
for rolename in self.db.user.get_roles(userid):
284-
if not rolename or (rolename not in self.role):
285-
continue
286-
# for each of the user's Roles, check the permissions
287-
for perm in self.role[rolename].permissions:
288-
# permission match?
289-
if perm.test(self.db, permission, classname, property,
290-
userid, itemid):
291-
return 1
292-
return 0
346+
# for each of the user's Roles, check the permissions
347+
# Note that checks with a check method are typically a lot more
348+
# expensive than the ones without. So we check the ones without
349+
# a check method first
350+
for has_check in False, True:
351+
for rolename in self.db.user.get_roles(userid):
352+
if not rolename or (rolename not in self.role):
353+
continue
354+
r = self.role[rolename]
355+
v = r.hasPermission(self.db, permission, userid, classname,
356+
property, itemid, has_check)
357+
if v:
358+
return v
359+
return False
293360

294361
def roleHasSearchPermission(self, classname, property, *rolenames):
295362
""" For each of the given roles, check the permissions.
296363
Property can be a transitive property.
297364
"""
298365
perms = []
299-
# pre-compute permissions
300-
for rn in rolenames:
301-
for perm in self.role[rn].permissions:
302-
perms.append(perm)
303366
# Note: break from inner loop means "found"
304367
# break from outer loop means "not found"
305368
cn = classname
@@ -319,8 +382,8 @@ def roleHasSearchPermission(self, classname, property, *rolenames):
319382
prop = cls.getprops()[propname]
320383
except KeyError:
321384
break
322-
for perm in perms:
323-
if perm.searchable(cn, propname):
385+
for rn in rolenames:
386+
if self.role[rn].searchable(cn, propname):
324387
break
325388
else:
326389
break
@@ -334,8 +397,8 @@ def roleHasSearchPermission(self, classname, property, *rolenames):
334397
return 0
335398
props = dict.fromkeys(('id', cls.labelprop(), cls.orderprop()))
336399
for p in props.keys():
337-
for perm in perms:
338-
if perm.searchable(prop.classname, p):
400+
for rn in rolenames:
401+
if self.role[rn].searchable(prop.classname, p):
339402
break
340403
else:
341404
return 0
@@ -409,7 +472,7 @@ def addPermissionToRole(self, rolename, permission, classname=None,
409472
permission = self.getPermission(permission, classname,
410473
properties, check, props_only)
411474
role = self.role[rolename.lower()]
412-
role.permissions.append(permission)
475+
role.addPermission(permission)
413476

414477
# Convenience methods for removing non-allowed properties from a
415478
# filterspec or sort/group list

test/db_test_base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3324,14 +3324,14 @@ def stdoutwrite(s):
33243324
'Role "admin":\n',
33253325
' User may create everything (Create)\n',
33263326
' User may edit everything (Edit)\n',
3327+
' User may use the email interface (Email Access)\n',
3328+
' User may access the rest interface (Rest Access)\n',
33273329
' User may restore everything (Restore)\n',
33283330
' User may retire everything (Retire)\n',
33293331
' User may view everything (View)\n',
33303332
' User may access the web interface (Web Access)\n',
3331-
' User may access the rest interface (Rest Access)\n',
3332-
' User may access the xmlrpc interface (Xmlrpc Access)\n',
33333333
' User may manipulate user Roles through the web (Web Roles)\n',
3334-
' User may use the email interface (Email Access)\n',
3334+
' User may access the xmlrpc interface (Xmlrpc Access)\n',
33353335
'Role "anonymous":\n', 'Role "user":\n',
33363336
' User is allowed to access msg (View for "msg" only)\n',
33373337
' Prevent users from seeing roles (View for "user": [\'username\', \'supervisor\', \'assignable\'] only)\n']

test/test_admin.py

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,33 +1420,33 @@ def testSecurityListOne(self):
14201420
ret = self.admin.main()
14211421

14221422
result = """Role "user":
1423-
User may access the web interface (Web Access)
14241423
User may use the email interface (Email Access)
14251424
User may access the rest interface (Rest Access)
1425+
User may access the web interface (Web Access)
14261426
User may access the xmlrpc interface (Xmlrpc Access)
1427-
User is allowed to access issue (View for "issue" only)
1428-
User is allowed to edit issue (Edit for "issue" only)
1429-
User is allowed to create issue (Create for "issue" only)
1430-
User is allowed to access file (View for "file" only)
1431-
User is allowed to edit file (Edit for "file" only)
14321427
User is allowed to create file (Create for "file" only)
1433-
User is allowed to access msg (View for "msg" only)
1434-
User is allowed to edit msg (Edit for "msg" only)
1435-
User is allowed to create msg (Create for "msg" only)
1436-
User is allowed to access keyword (View for "keyword" only)
1437-
User is allowed to edit keyword (Edit for "keyword" only)
1428+
User is allowed to edit file (Edit for "file" only)
1429+
User is allowed to access file (View for "file" only)
1430+
User is allowed to create issue (Create for "issue" only)
1431+
User is allowed to edit issue (Edit for "issue" only)
1432+
User is allowed to access issue (View for "issue" only)
14381433
User is allowed to create keyword (Create for "keyword" only)
1434+
User is allowed to edit keyword (Edit for "keyword" only)
1435+
User is allowed to access keyword (View for "keyword" only)
1436+
User is allowed to create msg (Create for "msg" only)
1437+
User is allowed to edit msg (Edit for "msg" only)
1438+
User is allowed to access msg (View for "msg" only)
14391439
User is allowed to access priority (View for "priority" only)
1440+
User is allowed to create queries (Create for "query" only)
1441+
User is allowed to edit their queries (Edit for "query" only)
1442+
User is allowed to restore their queries (Restore for "query" only)
1443+
User is allowed to retire their queries (Retire for "query" only)
1444+
(Search for "query" only)
1445+
User is allowed to view their own and public queries (View for "query" only)
14401446
User is allowed to access status (View for "status" only)
1447+
User is allowed to edit their own user details (Edit for "user": ('username', 'password', 'address', 'realname', 'phone', 'organisation', 'alternate_addresses', 'queries', 'timezone') only)
14411448
(View for "user": ('id', 'organisation', 'phone', 'realname', 'timezone', 'username') only)
14421449
User is allowed to view their own user details (View for "user" only)
1443-
User is allowed to edit their own user details (Edit for "user": ('username', 'password', 'address', 'realname', 'phone', 'organisation', 'alternate_addresses', 'queries', 'timezone') only)
1444-
User is allowed to view their own and public queries (View for "query" only)
1445-
(Search for "query" only)
1446-
User is allowed to edit their queries (Edit for "query" only)
1447-
User is allowed to retire their queries (Retire for "query" only)
1448-
User is allowed to restore their queries (Restore for "query" only)
1449-
User is allowed to create queries (Create for "query" only)
14501450
"""
14511451
print(out.getvalue())
14521452

@@ -1496,52 +1496,52 @@ def testSecurityListAll(self):
14961496
Role "admin":
14971497
User may create everything (Create)
14981498
User may edit everything (Edit)
1499+
User may use the email interface (Email Access)
1500+
User may access the rest interface (Rest Access)
14991501
User may restore everything (Restore)
15001502
User may retire everything (Retire)
15011503
User may view everything (View)
15021504
User may access the web interface (Web Access)
1503-
User may access the rest interface (Rest Access)
1504-
User may access the xmlrpc interface (Xmlrpc Access)
15051505
User may manipulate user Roles through the web (Web Roles)
1506-
User may use the email interface (Email Access)
1506+
User may access the xmlrpc interface (Xmlrpc Access)
15071507
Role "anonymous":
15081508
User may access the web interface (Web Access)
1509-
User is allowed to register new user (Register for "user" only)
1510-
User is allowed to access issue (View for "issue" only)
15111509
User is allowed to access file (View for "file" only)
1512-
User is allowed to access msg (View for "msg" only)
1510+
User is allowed to access issue (View for "issue" only)
15131511
User is allowed to access keyword (View for "keyword" only)
1512+
User is allowed to access msg (View for "msg" only)
15141513
User is allowed to access priority (View for "priority" only)
15151514
User is allowed to access status (View for "status" only)
1515+
User is allowed to register new user (Register for "user" only)
15161516
(Search for "user" only)
15171517
Role "user":
1518-
User may access the web interface (Web Access)
15191518
User may use the email interface (Email Access)
15201519
User may access the rest interface (Rest Access)
1520+
User may access the web interface (Web Access)
15211521
User may access the xmlrpc interface (Xmlrpc Access)
1522-
User is allowed to access issue (View for "issue" only)
1523-
User is allowed to edit issue (Edit for "issue" only)
1524-
User is allowed to create issue (Create for "issue" only)
1525-
User is allowed to access file (View for "file" only)
1526-
User is allowed to edit file (Edit for "file" only)
15271522
User is allowed to create file (Create for "file" only)
1528-
User is allowed to access msg (View for "msg" only)
1529-
User is allowed to edit msg (Edit for "msg" only)
1530-
User is allowed to create msg (Create for "msg" only)
1531-
User is allowed to access keyword (View for "keyword" only)
1532-
User is allowed to edit keyword (Edit for "keyword" only)
1523+
User is allowed to edit file (Edit for "file" only)
1524+
User is allowed to access file (View for "file" only)
1525+
User is allowed to create issue (Create for "issue" only)
1526+
User is allowed to edit issue (Edit for "issue" only)
1527+
User is allowed to access issue (View for "issue" only)
15331528
User is allowed to create keyword (Create for "keyword" only)
1529+
User is allowed to edit keyword (Edit for "keyword" only)
1530+
User is allowed to access keyword (View for "keyword" only)
1531+
User is allowed to create msg (Create for "msg" only)
1532+
User is allowed to edit msg (Edit for "msg" only)
1533+
User is allowed to access msg (View for "msg" only)
15341534
User is allowed to access priority (View for "priority" only)
1535+
User is allowed to create queries (Create for "query" only)
1536+
User is allowed to edit their queries (Edit for "query" only)
1537+
User is allowed to restore their queries (Restore for "query" only)
1538+
User is allowed to retire their queries (Retire for "query" only)
1539+
(Search for "query" only)
1540+
User is allowed to view their own and public queries (View for "query" only)
15351541
User is allowed to access status (View for "status" only)
1542+
User is allowed to edit their own user details (Edit for "user": ('username', 'password', 'address', 'realname', 'phone', 'organisation', 'alternate_addresses', 'queries', 'timezone') only)
15361543
(View for "user": ('id', 'organisation', 'phone', 'realname', 'timezone', 'username') only)
15371544
User is allowed to view their own user details (View for "user" only)
1538-
User is allowed to edit their own user details (Edit for "user": ('username', 'password', 'address', 'realname', 'phone', 'organisation', 'alternate_addresses', 'queries', 'timezone') only)
1539-
User is allowed to view their own and public queries (View for "query" only)
1540-
(Search for "query" only)
1541-
User is allowed to edit their queries (Edit for "query" only)
1542-
User is allowed to retire their queries (Retire for "query" only)
1543-
User is allowed to restore their queries (Restore for "query" only)
1544-
User is allowed to create queries (Create for "query" only)
15451545
"""
15461546
print(out.getvalue())
15471547

@@ -1579,43 +1579,50 @@ def testSecurityInvalidAttribute(self):
15791579
Role "admin":
15801580
User may create everything (Create)
15811581
User may edit everything (Edit)
1582+
User may use the email interface (Email Access)
1583+
User may access the rest interface (Rest Access)
15821584
User may restore everything (Restore)
15831585
User may retire everything (Retire)
15841586
User may view everything (View)
15851587
User may access the web interface (Web Access)
1586-
User may access the rest interface (Rest Access)
1587-
User may access the xmlrpc interface (Xmlrpc Access)
15881588
User may manipulate user Roles through the web (Web Roles)
1589-
User may use the email interface (Email Access)
1589+
User may access the xmlrpc interface (Xmlrpc Access)
15901590
Role "anonymous":
15911591
User may access the web interface (Web Access)
1592-
User is allowed to register new user (Register for "user" only)
1593-
User is allowed to access issue (View for "issue" only)
15941592
User is allowed to access file (View for "file" only)
1595-
User is allowed to access msg (View for "msg" only)
1593+
User is allowed to access issue (View for "issue" only)
15961594
User is allowed to access keyword (View for "keyword" only)
1595+
User is allowed to access msg (View for "msg" only)
15971596
User is allowed to access priority (View for "priority" only)
15981597
User is allowed to access status (View for "status" only)
1598+
User is allowed to register new user (Register for "user" only)
15991599
(Search for "user" only)
16001600
Role "user":
1601-
User may access the web interface (Web Access)
16021601
User may use the email interface (Email Access)
16031602
User may access the rest interface (Rest Access)
1603+
User may access the web interface (Web Access)
16041604
User may access the xmlrpc interface (Xmlrpc Access)
1605-
User is allowed to access issue (View for "issue" only)
1606-
User is allowed to edit issue (Edit for "issue" only)
1607-
User is allowed to create issue (Create for "issue" only)
1608-
User is allowed to access file (View for "file" only)
1609-
User is allowed to edit file (Edit for "file" only)
16101605
User is allowed to create file (Create for "file" only)
1611-
User is allowed to access msg (View for "msg" only)
1612-
User is allowed to edit msg (Edit for "msg" only)
1613-
User is allowed to create msg (Create for "msg" only)
1614-
User is allowed to access keyword (View for "keyword" only)
1615-
User is allowed to edit keyword (Edit for "keyword" only)
1606+
User is allowed to edit file (Edit for "file" only)
1607+
User is allowed to access file (View for "file" only)
1608+
User is allowed to create issue (Create for "issue" only)
1609+
User is allowed to edit issue (Edit for "issue" only)
1610+
User is allowed to access issue (View for "issue" only)
16161611
User is allowed to create keyword (Create for "keyword" only)
1612+
User is allowed to edit keyword (Edit for "keyword" only)
1613+
User is allowed to access keyword (View for "keyword" only)
1614+
User is allowed to create msg (Create for "msg" only)
1615+
User is allowed to edit msg (Edit for "msg" only)
1616+
User is allowed to access msg (View for "msg" only)
16171617
User is allowed to access priority (View for "priority" only)
1618+
User is allowed to create queries (Create for "query" only)
1619+
User is allowed to edit their queries (Edit for "query" only)
1620+
User is allowed to restore their queries (Restore for "query" only)
1621+
User is allowed to retire their queries (Retire for "query" only)
1622+
(Search for "query" only)
1623+
User is allowed to view their own and public queries (View for "query" only)
16181624
User is allowed to access status (View for "status" only)
1625+
User is allowed to edit their own user details (Edit for "user": ('username', 'password', 'address', 'realname', 'phone', 'organisation', 'alternate_addresses', 'queries', 'timezone') only)
16191626
(View for "user": ('id', 'organization', 'phone', 'realname', 'timezone', 'username') only)
16201627
16211628
**Invalid properties for user: ['organization']

0 commit comments

Comments
 (0)