Skip to content

Commit 693656f

Browse files
committed
Check in enhanced form for check command used by addPermission.
New form can include a **context dictionary that allows access to the name of the property, class, and permission being checked. This should make designing more complex permission requirements easier.
1 parent 90b0bfd commit 693656f

File tree

5 files changed

+118
-9
lines changed

5 files changed

+118
-9
lines changed

CHANGES.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ Features:
167167
usually be the same template used to edit the form. See the section
168168
on "Implementing Modal Editing Using @template" in
169169
``customizing.txt``. (John Rouillard)
170+
- New form of check function is permitted in permission definitions.
171+
If the check function is defined as:
172+
check(db, userid, itemid, **ctx)
173+
the ctx variable will have:
174+
ctx['property'] the name of the property being checked or None
175+
ctx['classname'] the class that is being checked or None
176+
ctx['permission'] the name of the permission (e.g. View, Edit)
177+
At some future date the older 3 argument style check command will
178+
be deprecated. See ``upgrading.txt`` for details.
170179

171180
Fixed:
172181

doc/customizing.txt

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ Put together, these settings appear in the tracker's ``schema.py`` file::
11831183
# Users should be able to edit their own details -- this permission
11841184
# is limited to only the situation where the Viewed or Edited item
11851185
# is their own.
1186-
def own_record(db, userid, itemid):
1186+
def own_record(db, userid, itemid, **ctx):
11871187
'''Determine whether the userid matches the item being accessed.'''
11881188
return userid == itemid
11891189
p = db.security.addPermission(name='View', klass='user', check=own_record,
@@ -1297,11 +1297,35 @@ The ``addPermission`` method takes a couple of optional parameters:
12971297
new Permission to (eg. ``... klass='user', properties=('name',
12981298
'email') ...``)
12991299
**check**
1300-
A function to be execute which returns boolean determining whether the
1301-
Permission is allowed. The function has the signature ``check(db, userid,
1302-
itemid)`` where ``db`` is a handle on the open database, ``userid`` is
1300+
A function to be executed which returns boolean determining whether
1301+
the Permission is allowed. If it returns True, the permission is
1302+
allowed, if it returns False the permission is denied. The function
1303+
can have one of two signatures::
1304+
1305+
check(db, userid, itemid)
1306+
1307+
or::
1308+
1309+
check(db, userid, itemid, **ctx)
1310+
1311+
where ``db`` is a handle on the open database, ``userid`` is
13031312
the user attempting access and ``itemid`` is the specific item being
1304-
accessed.
1313+
accessed. If the second form is used the ``ctx`` dictionary is
1314+
defined with the following values::
1315+
1316+
ctx['property'] the name of the property being checked or None if
1317+
it's a class check.
1318+
1319+
ctx['classname'] the name of the class that is being checked
1320+
(issue, query ....).
1321+
1322+
ctx['permission'] the name of the permission (e.g. View, Edit...).
1323+
1324+
The second form is preferred as it makes it easier to implement more
1325+
complex permission schemes. An example of the use of ``ctx`` can be
1326+
found in the ``upgrading.txt`` or `upgrading.html`_ document.
1327+
1328+
.. _`upgrading.html`: upgrading.html
13051329

13061330
Example Scenarios
13071331
~~~~~~~~~~~~~~~~~

doc/upgrading.txt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,57 @@ permission for the user role. After the change it should look like::
345345

346346
where the last three lines are the ones you need to add.
347347

348+
Enhancement to check command for Permissions
349+
--------------------------------------------
350+
351+
A new form of check function is permitted in permission definitions.
352+
The three argument form is still supported and will work the same
353+
as it always has (although it may be depricated in the future).
354+
355+
If the check function is defined as::
356+
357+
check(db, userid, itemid, **ctx)
358+
359+
the ctx variable will have the context to use when determining access
360+
rights::
361+
362+
ctx['property'] the name of the property being checked or None if
363+
it's a class check.
364+
365+
ctx['classname'] the name of the class that is being checked
366+
(issue, query ....).
367+
368+
ctx['permission'] the name of the permission (e.g. View, Edit...).
369+
370+
This should make defining complex permissions much easier. Consider::
371+
372+
def issue_private_access(db, userid, itemid, **ctx):
373+
if not db.issue.get(itemid, 'private'):
374+
# allow access to everything if not private
375+
return True
376+
377+
# It is a private issue hide nosy list
378+
# Note that the nosy property *must* be listed
379+
# in permissions argument to the addPermission
380+
# definition otherwise this check command
381+
# is not run.
382+
if ctx['property'] == 'nosy':
383+
return False # deny access to this property
384+
385+
# allow access for editing, viewing etc. of the class
386+
return True
387+
388+
389+
e = db.security.addPermission(name='Edit', klass='issue',
390+
check=issue_private_access,
391+
properties=['nosy'],
392+
description="Edit issue checks")
393+
394+
It is suggested that you change your checks to use the ``**ctx``
395+
parameter. This is expected to be the preferred form in the future.
396+
You do not need to use the ``ctx`` parameter in the function if you do
397+
not need it.
398+
348399
Migrating from 1.5.0 to 1.5.1
349400
=============================
350401

roundup/security.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,29 @@ class Permission:
2727
'''
2828
def __init__(self, name='', description='', klass=None,
2929
properties=None, check=None):
30+
import inspect
3031
self.name = name
3132
self.description = description
3233
self.klass = klass
3334
self.properties = properties
3435
self._properties_dict = support.TruthDict(properties)
3536
self.check = check
3637

38+
if check is None:
39+
self.check_version = 0
40+
else:
41+
args=inspect.getargspec(check)
42+
# FIXME change args[2] to args.keywords since python
43+
# 2.6 made getargspec a named tuple once roundup 1.6 released.
44+
# If there is a **parameter defined in the function spec, the
45+
# value of the 3rd argument in the tuple is not None.
46+
if args[2] is None:
47+
# function definition is function(db, userid, itemid)
48+
self.check_version = 1
49+
else:
50+
# function definition is function(db, userid, itemid, **other)
51+
self.check_version = 2
52+
3753
def test(self, db, permission, classname, property, userid, itemid):
3854
if permission != self.name:
3955
return 0
@@ -48,8 +64,12 @@ def test(self, db, permission, classname, property, userid, itemid):
4864

4965
# check code
5066
if itemid is not None and self.check is not None:
51-
if not self.check(db, userid, itemid):
52-
return 0
67+
if self.check_version == 1:
68+
if not self.check(db, userid, itemid):
69+
return 0
70+
elif self.check_version == 2:
71+
if not self.check(db, userid, itemid, property=property, permission=permission, classname=classname):
72+
return 0
5373

5474
# we have a winner
5575
return 1

test/test_security.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,13 @@ def testAccessControls(self):
155155
self.assertEquals(has('Test', none, 'test', property='c'), 0)
156156
self.assertEquals(has('Test', none, 'test'), 0)
157157

158-
# check function
159-
check = lambda db, userid, itemid: itemid == '1'
158+
# check function new style. Make sure that other args are passed.
159+
def check(db,userid,itemid, **other):
160+
prop = other['property']
161+
prop = other['classname']
162+
prop = other['permission']
163+
return (itemid == '1')
164+
160165
addRole(name='Role3')
161166
addToRole('Role3', add(name="Test", klass="test", check=check))
162167
user3 = self.db.user.create(username='user3', roles='Role3')

0 commit comments

Comments
 (0)