Skip to content

Commit cbe9610

Browse files
author
Ralf Schlatterbeck
committed
Clean up all the places where role processing occurs.
This is now in a central place in hyperdb.Class and is used consistently throughout. This also means now a template can override the way role processing occurs (e.g. for elaborate permission schemes). Thanks to intevation for funding the change. Note: On first glance the hyperdb.Class may not be the ideal place for role processing. On second thought: Roles may appear in other classes, too (e.g., a user_group or similar) which then don't need to reinvent the wheel. And I didn't want to introduce a separate UserClass (as is the case for the HTML classes) due to compatibility issues with existing schema.py out there.
1 parent d16c12c commit cbe9610

File tree

6 files changed

+69
-30
lines changed

6 files changed

+69
-30
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ Fixes:
4141
would trigger a traceback about an unbound variable.
4242
Add new regression test for this case. May be related to (now closed)
4343
issue1177477. Thanks to Intevation for funding the fix.
44+
- Clean up all the places where role processing occurs. This is now in a
45+
central place in hyperdb.Class and is used consistently throughout.
46+
This also means now a template can override the way role processing
47+
occurs (e.g. for elaborate permission schemes). Thanks to intevation
48+
for funding the change.
4449

4550
2009-10-09 1.4.10 (r4374)
4651

roundup/cgi/templating.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,12 +1205,9 @@ def hasPermission(self, permission, classname=_marker,
12051205
return self._db.security.hasPermission(permission,
12061206
self._nodeid, classname, property, itemid)
12071207

1208-
def hasRole(self, rolename):
1209-
"""Determine whether the user has the Role."""
1210-
roles = self._db.user.get(self._nodeid, 'roles').split(',')
1211-
for role in roles:
1212-
if role.strip() == rolename: return True
1213-
return False
1208+
def hasRole(self, *rolenames):
1209+
"""Determine whether the user has any role in rolenames."""
1210+
return self._db.user.has_role(self._nodeid, *rolenames)
12141211

12151212
def HTMLItem(client, classname, nodeid, anonymous=0):
12161213
if classname == 'user':

roundup/hyperdb.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,16 @@ def close(self):
760760
761761
"""
762762

763+
def iter_roles(roles):
764+
''' handle the text processing of turning the roles list
765+
into something python can use more easily
766+
'''
767+
if not roles or not roles.strip():
768+
raise StopIteration, "Empty roles given"
769+
for role in [x.lower().strip() for x in roles.split(',')]:
770+
yield role
771+
772+
763773
#
764774
# The base Class class
765775
#
@@ -1227,6 +1237,35 @@ def export_propnames(self):
12271237
propnames = self.getprops().keys()
12281238
propnames.sort()
12291239
return propnames
1240+
#
1241+
# convenience methods
1242+
#
1243+
def get_roles(self, nodeid):
1244+
"""Return iterator for all roles for this nodeid.
1245+
1246+
Yields string-processed roles.
1247+
This method can be overridden to provide a hook where we can
1248+
insert other permission models (e.g. get roles from database)
1249+
In standard schemas only a user has a roles property but
1250+
this may be different in customized schemas.
1251+
Note that this is the *central place* where role
1252+
processing happens!
1253+
"""
1254+
node = self.db.getnode(self.classname, nodeid)
1255+
return iter_roles(node['roles'])
1256+
1257+
def has_role(self, nodeid, *roles):
1258+
'''See if this node has any roles that appear in roles.
1259+
1260+
For convenience reasons we take a list.
1261+
In standard schemas only a user has a roles property but
1262+
this may be different in customized schemas.
1263+
'''
1264+
roles = dict.fromkeys ([r.strip().lower() for r in roles])
1265+
for role in self.get_roles(nodeid):
1266+
if role in roles:
1267+
return True
1268+
return False
12301269

12311270

12321271
class HyperdbValueError(ValueError):

roundup/mailgw.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class node. Any parts of other types are each stored in separate files
8686
from roundup import configuration, hyperdb, date, password, rfc2822, exceptions
8787
from roundup.mailer import Mailer, MessageSendError
8888
from roundup.i18n import _
89+
from roundup.hyperdb import iter_roles
8990

9091
try:
9192
import pyme, pyme.core, pyme.gpgme
@@ -163,24 +164,6 @@ def gpgh_sigs(sig):
163164
yield sig
164165
sig = sig.next
165166

166-
167-
def iter_roles(roles):
168-
''' handle the text processing of turning the roles list
169-
into something python can use more easily
170-
'''
171-
for role in [x.lower().strip() for x in roles.split(',')]:
172-
yield role
173-
174-
def user_has_role(db, userid, role_list):
175-
''' see if the given user has any roles that appear
176-
in the role_list
177-
'''
178-
for role in iter_roles(db.user.get(userid, 'roles')):
179-
if role in iter_roles(role_list):
180-
return True
181-
return False
182-
183-
184167
def check_pgp_sigs(sig, gpgctx, author):
185168
''' Theoretically a PGP message can have several signatures. GPGME
186169
returns status on all signatures in a linked list. Walk that
@@ -1256,8 +1239,8 @@ def _handle_message(self, message):
12561239
# or we will skip PGP processing
12571240
def pgp_role():
12581241
if self.instance.config.PGP_ROLES:
1259-
return user_has_role(self.db, author,
1260-
self.instance.config.PGP_ROLES)
1242+
return self.db.user.has_role(author,
1243+
iter_roles(self.instance.config.PGP_ROLES))
12611244
else:
12621245
return True
12631246

roundup/security.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,9 @@ def hasPermission(self, permission, userid, classname=None,
162162
Note that this functionality is actually implemented by the
163163
Permission.test() method.
164164
'''
165-
roles = self.db.user.get(userid, 'roles')
166-
if roles is None:
167-
return 0
168165
if itemid and classname is None:
169166
raise ValueError, 'classname must accompany itemid'
170-
for rolename in [x.lower().strip() for x in roles.split(',')]:
167+
for rolename in self.db.user.get_roles(userid):
171168
if not rolename or not self.role.has_key(rolename):
172169
continue
173170
# for each of the user's Roles, check the permissions

test/test_cgi.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,24 @@ def own_record(db, userid, itemid): return userid == itemid
648648
self.failUnlessRaises(exceptions.Unauthorised,
649649
actions.EditItemAction(cl).handle)
650650

651+
def testRoles(self):
652+
cl = self._make_client({})
653+
self.db.user.set('1', roles='aDmin, uSer')
654+
item = HTMLItem(cl, 'user', '1')
655+
self.assert_(item.hasRole('Admin'))
656+
self.assert_(item.hasRole('User'))
657+
self.assert_(item.hasRole('AdmiN'))
658+
self.assert_(item.hasRole('UseR'))
659+
self.assert_(item.hasRole('UseR','Admin'))
660+
self.assert_(item.hasRole('UseR','somethingelse'))
661+
self.assert_(item.hasRole('somethingelse','Admin'))
662+
self.assert_(not item.hasRole('userr'))
663+
self.assert_(not item.hasRole('adminn'))
664+
self.assert_(not item.hasRole(''))
665+
self.assert_(not item.hasRole(' '))
666+
self.db.user.set('1', roles='')
667+
self.assert_(not item.hasRole(''))
668+
651669
def testCSVExport(self):
652670
cl = self._make_client({'@columns': 'id,name'}, nodeid=None,
653671
userid='1')

0 commit comments

Comments
 (0)