Skip to content

Commit b267853

Browse files
author
Ralf Schlatterbeck
committed
- Add explicit "Search" permissions, see Security Fix below.
- Security Fix: Add a check for search-permissions: now we allow searching for properties only if the property is readable without a check method or if an explicit search permission (see above unter "Features) is given for the property. This fixes cases where a user doesn't have access to a property but can deduce the content by crafting a clever search, group or sort query. see doc/upgrading.txt for how to fix your trackers!
1 parent 00ba6c0 commit b267853

File tree

9 files changed

+352
-8
lines changed

9 files changed

+352
-8
lines changed

CHANGES.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@ This file contains the changes to the Roundup system over time. The entries
22
are given with the most recent entry first. If no other name is given,
33
Richard Jones did the change.
44

5-
20X0-XX-XX
5+
20XX-XX-XX 1.4.17 (rXXXX)
6+
7+
Features:
8+
9+
- Add explicit "Search" permissions, see Security Fix below.
610

711
Fixed:
12+
813
- Some minor typos fixed in doc/customizing.txt (Thanks Ralf Hemmecke).
14+
- Security Fix: Add a check for search-permissions: now we allow
15+
searching for properties only if the property is readable without a
16+
check method or if an explicit search permission (see above unter
17+
"Features) is given for the property. This fixes cases where a user
18+
doesn't have access to a property but can deduce the content by
19+
crafting a clever search, group or sort query.
20+
see doc/upgrading.txt for how to fix your trackers!
921

1022
2010-10-08 1.4.16 (r4541)
1123

doc/upgrading.txt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,45 @@ steps.
1313

1414
.. contents::
1515

16+
Migrating from 1.4.x to 1.4.17
17+
==============================
18+
19+
Searching now requires either read-permission without a check method, or
20+
you will have to add a "Search" permission for a class or a list of
21+
properties for a class (if you want to allow searching). For the classic
22+
template (or other templates derived from it) you want to add the
23+
following lines to your `schema.py` file::
24+
25+
p = db.security.addPermission(name='Search', klass='query')
26+
db.security.addPermissionToRole('User', p)
27+
28+
This is needed, because for the `query` class users may view only their
29+
own queries (or public queries). This is implemented with a `check`
30+
method, therefore the default search permissions will not allow
31+
searching and you'll have to add an explicit search permission.
32+
If you have modified your schema, you can check if you're missing any
33+
search permissions with the following script, run it in your tracker
34+
directory, it will list for each Class and Property the roles that may
35+
search for this property::
36+
37+
#!/usr/bin/python
38+
import os
39+
from roundup import instance
40+
41+
tracker = instance.open(os.getcwd ())
42+
db = tracker.open('admin')
43+
44+
for cl in sorted(db.getclasses()):
45+
print "Class:", cl
46+
for p in sorted(db.getclass(cl).properties.keys()):
47+
print " Property:", p
48+
roles = []
49+
for role in sorted(db.security.role.iterkeys()):
50+
if db.security.roleHasSearchPermission(role,cl,p):
51+
roles.append(role)
52+
print " roles may search:", ', '.join(roles)
53+
54+
1655
Migrating from 1.4.x to 1.4.12
1756
==============================
1857

roundup/cgi/templating.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,13 +673,21 @@ def filter(self, request=None, filterspec={}, sort=[], group=[]):
673673

674674
"request" takes precedence over the other three arguments.
675675
"""
676+
security = self._db.security
677+
userid = self._client.userid
676678
if request is not None:
679+
# for a request we asume it has already been
680+
# security-filtered
677681
filterspec = request.filterspec
678682
sort = request.sort
679683
group = request.group
684+
else:
685+
cn = self.classname
686+
filterspec = security.filterFilterspec(userid, cn, filterspec)
687+
sort = security.filterSortspec(userid, cn, sort)
688+
group = security.filterSortspec(userid, cn, group)
680689

681-
check = self._db.security.hasPermission
682-
userid = self._client.userid
690+
check = security.hasPermission
683691
if not check('Web Access', userid):
684692
return []
685693

@@ -2446,12 +2454,16 @@ def _post_init(self):
24462454
self.columns = handleListCGIValue(self.form[name])
24472455
break
24482456
self.show = support.TruthDict(self.columns)
2457+
security = self._client.db.security
2458+
userid = self._client.userid
24492459

24502460
# sorting and grouping
24512461
self.sort = []
24522462
self.group = []
24532463
self._parse_sort(self.sort, 'sort')
24542464
self._parse_sort(self.group, 'group')
2465+
self.sort = security.filterSortspec(userid, self.classname, self.sort)
2466+
self.group = security.filterSortspec(userid, self.classname, self.group)
24552467

24562468
# filtering
24572469
self.filter = []
@@ -2481,6 +2493,8 @@ def _post_init(self):
24812493
self.filterspec[name] = handleListCGIValue(fv)
24822494
else:
24832495
self.filterspec[name] = fv.value
2496+
self.filterspec = security.filterFilterspec(userid, self.classname,
2497+
self.filterspec)
24842498

24852499
# full-text search argument
24862500
self.search_text = None

roundup/security.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,28 @@ def test(self, db, permission, classname, property, userid, itemid):
5454
# we have a winner
5555
return 1
5656

57+
def searchable(self, db, permission, classname, property):
58+
""" A Permission is searchable for the given permission if it
59+
doesn't include a check method and otherwise matches the
60+
given parameters.
61+
"""
62+
if permission != self.name:
63+
return 0
64+
65+
# are we checking the correct class
66+
if self.klass != classname:
67+
return 0
68+
69+
# what about property?
70+
if not self._properties_dict[property]:
71+
return 0
72+
73+
if self.check:
74+
return 0
75+
76+
return 1
77+
78+
5779
def __repr__(self):
5880
return '<Permission 0x%x %r,%r,%r,%r>'%(id(self), self.name,
5981
self.klass, self.properties, self.check)
@@ -175,6 +197,44 @@ def hasPermission(self, permission, userid, classname=None,
175197
return 1
176198
return 0
177199

200+
def roleHasSearchPermission(self, rolename, classname, property):
201+
""" for each of the user's Roles, check the permissions
202+
"""
203+
for perm in self.role[rolename].permissions:
204+
# permission match?
205+
for p in 'View', 'Search':
206+
if perm.searchable(self.db, p, classname, property):
207+
return 1
208+
return 0
209+
210+
def hasSearchPermission(self, userid, classname, property):
211+
'''Look through all the Roles, and hence Permissions, and
212+
see if "permission" exists given the constraints of
213+
classname and property.
214+
215+
A search permission is granted if we find a 'View' or
216+
'Search' permission for the user which does *not* include
217+
a check function. If such a permission is found, the user may
218+
search for the given property in the given class.
219+
220+
Note that classname *and* property are mandatory arguments.
221+
222+
Contrary to hasPermission, the search will *not* match if
223+
there are additional constraints (namely a search function)
224+
on a Permission found.
225+
226+
Concerning property, the Permission matched must have
227+
either no properties listed or the property must appear in
228+
the list.
229+
'''
230+
for rolename in self.db.user.get_roles(userid):
231+
if not rolename or not self.role.has_key(rolename):
232+
continue
233+
# for each of the user's Roles, check the permissions
234+
if self.roleHasSearchPermission (rolename, classname, property):
235+
return 1
236+
return 0
237+
178238
def addPermission(self, **propspec):
179239
''' Create a new Permission with the properties defined in
180240
'propspec'. See the Permission class for the possible
@@ -208,4 +268,22 @@ def addPermissionToRole(self, rolename, permission, classname=None,
208268
role = self.role[rolename.lower()]
209269
role.permissions.append(permission)
210270

271+
# Convenience methods for removing non-allowed properties from a
272+
# filterspec or sort/group list
273+
274+
def filterFilterspec(self, userid, classname, filterspec):
275+
""" Return a filterspec that has all non-allowed properties removed.
276+
"""
277+
return dict ([(k, v) for k, v in filterspec.iteritems()
278+
if self.hasSearchPermission(userid,classname,k)])
279+
280+
def filterSortspec(self, userid, classname, sort):
281+
""" Return a sort- or group-list that has all non-allowed properties
282+
removed.
283+
"""
284+
if isinstance(sort, tuple) and sort[0] in '+-':
285+
sort = [sort]
286+
return [(d, p) for d, p in sort
287+
if self.hasSearchPermission(userid,classname,p)]
288+
211289
# vim: set filetype=python sts=4 sw=4 et si :

roundup/xmlrpc.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,15 @@ def list(self, classname, propname=None):
8989
def filter(self, classname, search_matches, filterspec,
9090
sort=[], group=[]):
9191
cl = self.db.getclass(classname)
92+
uid = self.db.getuid()
93+
security = self.db.security
94+
filterspec = security.filterFilterspec (uid, classname, filterspec)
95+
sort = security.filterSortspec (uid, classname, sort)
96+
group = security.filterSortspec (uid, classname, group)
9297
result = cl.filter(search_matches, filterspec, sort=sort, group=group)
93-
return result
98+
check = security.hasPermission
99+
x = [id for id in result if check('View', uid, classname, itemid=id)]
100+
return x
94101

95102
def display(self, designator, *properties):
96103
classname, itemid = hyperdb.splitDesignator(designator)

share/roundup/templates/classic/schema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ def edit_query(db, userid, itemid):
129129
p = db.security.addPermission(name='View', klass='query', check=view_query,
130130
description="User is allowed to view their own and public queries")
131131
db.security.addPermissionToRole('User', p)
132+
p = db.security.addPermission(name='Search', klass='query')
133+
db.security.addPermissionToRole('User', p)
132134
p = db.security.addPermission(name='Edit', klass='query', check=edit_query,
133135
description="User is allowed to edit their queries")
134136
db.security.addPermissionToRole('User', p)

share/roundup/templates/devel/schema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ def edit_query(db, userid, itemid):
327327
return userid == db.query.get(itemid, 'creator')
328328
p = db.security.addPermission(name='View', klass='query', check=view_query,
329329
description="User is allowed to view their own and public queries")
330+
p = db.security.addPermission(name='Search', klass='query')
331+
db.security.addPermissionToRole('User', p)
330332
for r in 'User', 'Developer', 'Coordinator':
331333
db.security.addPermissionToRole(r, p)
332334
p = db.security.addPermission(name='Edit', klass='query', check=edit_query,

0 commit comments

Comments
 (0)