Skip to content

Commit 5d6b53e

Browse files
committed
Fix error status for invalid props
Some places were raising AttributeError which results in a 405 (bad method) not a 400. Replace with UsageError or KeyError. Make rest.py::transitive_props run aginst a prop that has no transitive elements as well. So it will verify that assignedto exists even though it has no period like assignedto.name would. These should check properties in @fields and @sort. Also validate fields that are used as search params: ?assignedto=1 If the search prop was mispelled or incorrect, the search element was ignored as though it had not been specified. Now it returns a 400 to notify sender they sent an incorrect filter. Also remove unused statements that were originally for finding invalid props before we supported transitive props.
1 parent b5f1602 commit 5d6b53e

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

roundup/rest.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,13 +563,13 @@ def transitive_props (self, class_name, props):
563563
for pn in p.split('.'):
564564
# Tried to dereference a non-Link property
565565
if cn is None:
566-
raise AttributeError("Unknown: %s" % p)
566+
raise UsageError("Property %(base)s can not be dereferenced in %(p)s." % { "base": p[:-(len(pn)+1)], "p": p})
567567
cls = self.db.getclass(cn)
568568
# This raises a KeyError for unknown prop:
569569
try:
570570
prop = cls.getprops(protected=True)[pn]
571571
except KeyError:
572-
raise AttributeError("Unknown: %s" % p)
572+
raise KeyError("Unknown property: %s" % p)
573573
if isinstance(prop, hyperdb.Multilink):
574574
raise UsageError(
575575
'Multilink Traversal not allowed: %s' % p)
@@ -582,6 +582,13 @@ def transitive_props (self, class_name, props):
582582
cn = prop.classname
583583
except AttributeError:
584584
cn = None
585+
else:
586+
cls = self.db.getclass(cn)
587+
# This raises a KeyError for unknown prop:
588+
try:
589+
prop = cls.getprops(protected=True)[pn]
590+
except KeyError:
591+
raise KeyError("Unknown property: %s" % pn)
585592
checked_props.append (p)
586593
return checked_props
587594

@@ -802,11 +809,9 @@ def get_collection(self, class_name, input):
802809
f = value.split(",")
803810
if len(f) == 1:
804811
f = value.split(":")
805-
allprops = class_obj.getprops(protected=True)
806812
display_props.update(self.transitive_props(class_name, f))
807813
elif key == "@sort":
808814
f = value.split(",")
809-
allprops = class_obj.getprops(protected=True)
810815
for p in f:
811816
if not p:
812817
raise UsageError("Empty property "
@@ -845,6 +850,8 @@ def get_collection(self, class_name, input):
845850
except KeyError:
846851
raise UsageError("Field %s is not valid for %s class." %
847852
(p, class_name))
853+
# Call this for the side effect of validating the key
854+
_ = self.transitive_props(class_name, [ key ])
848855
# We drop properties without search permission silently
849856
# This reflects the current behavior of other roundup
850857
# interfaces
@@ -1024,7 +1031,6 @@ def get_element(self, class_name, item_id, input):
10241031
f = value.split(",")
10251032
if len(f) == 1:
10261033
f = value.split(":")
1027-
allprops = class_obj.getprops(protected=True)
10281034
props.update(self.transitive_props(class_name, f))
10291035
elif key == "@protected":
10301036
# allow client to request read only

test/rest_common.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,68 @@ def testGetTransitive(self):
391391
results = self.server.get_collection('issue', form)
392392
self.assertDictEqual(expected, results)
393393

394+
def testGetBadTransitive(self):
395+
"""
396+
Mess up the names of various properties and make sure we get a 400
397+
and a somewhat useful error message.
398+
"""
399+
base_path = self.db.config['TRACKER_WEB'] + 'rest/data/'
400+
#self.maxDiff=None
401+
self.create_sampledata()
402+
self.db.issue.set('2', status=self.db.status.lookup('closed'))
403+
self.db.issue.set('3', status=self.db.status.lookup('chatting'))
404+
expected = [
405+
{'error': {'msg': KeyError('Unknown property: assignedto.isse',),
406+
'status': 400}},
407+
{'error': {'msg': KeyError('Unknown property: stat',),
408+
'status': 400}},
409+
{'error': {'msg': KeyError('Unknown property: status.nam',),
410+
'status': 400}},
411+
]
412+
413+
## test invalid transitive property in @fields
414+
form = cgi.FieldStorage()
415+
form.list = [
416+
cgi.MiniFieldStorage('status.name', 'o'),
417+
cgi.MiniFieldStorage('@fields', 'status,assignedto.isse'),
418+
cgi.MiniFieldStorage('@sort', 'status.name'),
419+
]
420+
results = self.server.get_collection('issue', form)
421+
self.assertEqual(self.dummy_client.response_code, 400)
422+
self.assertEqual(repr(expected[0]['error']['msg']),
423+
repr(results['error']['msg']))
424+
self.assertEqual(expected[0]['error']['status'],
425+
results['error']['status'])
426+
427+
## test invalid property in @fields
428+
form = cgi.FieldStorage()
429+
form.list = [
430+
cgi.MiniFieldStorage('status.name', 'o'),
431+
cgi.MiniFieldStorage('@fields', 'stat,assignedto.isuse'),
432+
cgi.MiniFieldStorage('@sort', 'status.name'),
433+
]
434+
results = self.server.get_collection('issue', form)
435+
self.assertEqual(self.dummy_client.response_code, 400)
436+
self.assertEqual(repr(expected[1]['error']['msg']),
437+
repr(results['error']['msg']))
438+
self.assertEqual(expected[1]['error']['status'],
439+
results['error']['status'])
440+
441+
## test invalid transitive property in filter TODO
442+
form = cgi.FieldStorage()
443+
form.list = [
444+
cgi.MiniFieldStorage('status.nam', 'o'),
445+
cgi.MiniFieldStorage('@fields', 'status,assignedto.isuse'),
446+
cgi.MiniFieldStorage('@sort', 'status.name'),
447+
]
448+
results = self.server.get_collection('issue', form)
449+
# is currently 403 not 400
450+
self.assertEqual(self.dummy_client.response_code, 400)
451+
self.assertEqual(repr(expected[2]['error']['msg']),
452+
repr(results['error']['msg']))
453+
self.assertEqual(expected[2]['error']['status'],
454+
results['error']['status'])
455+
394456
def testGetExactMatch(self):
395457
""" Retrieve all issues with an exact title
396458
"""

0 commit comments

Comments
 (0)