Skip to content

Commit 372fa7f

Browse files
committed
Refactored REST code that formats an item for display. A GET on
/class_name or /class_name/item_id use the same back end code. So both now support use of @fields (or @attrs) as a comma (or colon) separated list of property names to display. @verbose is respected for all data for /class_name. This also obscures passwords so they don't leak even in encrypted form.
1 parent 1fecc6a commit 372fa7f

File tree

1 file changed

+108
-64
lines changed

1 file changed

+108
-64
lines changed

roundup/rest.py

Lines changed: 108 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,67 @@ def raise_if_no_etag(self, class_name, item_id, input):
471471
"If-Match is missing or does not match."
472472
" Retrieve asset and retry modification if valid.")
473473

474+
def format_item(self, node, item_id, props=None, verbose=1):
475+
''' display class obj as requested by verbose and
476+
props.
477+
'''
478+
uid = self.db.getuid()
479+
class_name = node.cl.classname
480+
result = {}
481+
try:
482+
# pn = propname
483+
for pn in sorted(props):
484+
prop = props[pn]
485+
if not self.db.security.hasPermission(
486+
'View', uid, class_name, pn, item_id
487+
):
488+
continue
489+
v = getattr(node, pn)
490+
if isinstance (prop, (hyperdb.Link, hyperdb.Multilink)):
491+
linkcls = self.db.getclass (prop.classname)
492+
cp = '%s/%s/' % (self.data_path, prop.classname)
493+
if verbose and v:
494+
if isinstance(v, type([])):
495+
r = []
496+
for id in v:
497+
d = dict(id = id, link = cp + id)
498+
if verbose > 1:
499+
label = linkcls.labelprop()
500+
d [label] = linkcls.get(id, label)
501+
r.append(d)
502+
result[pn] = r
503+
else:
504+
result[pn] = dict(id = v, link = cp + v)
505+
if verbose > 1:
506+
label = linkcls.labelprop()
507+
result[pn][label] = linkcls.get(v, label)
508+
else:
509+
result[pn] = v
510+
elif isinstance (prop, hyperdb.String) and pn == 'content':
511+
# Do not show the (possibly HUGE) content prop
512+
# unless very verbose, we display the standard
513+
# download link instead
514+
if verbose < 3:
515+
u = self.db.config.TRACKER_WEB
516+
p = u + '%s%s/' % (class_name, node.id)
517+
result[pn] = dict(link = p)
518+
else:
519+
result[pn] = v
520+
elif isinstance(prop, hyperdb.Password):
521+
if v != None: # locked users like anonymous have None
522+
result[pn] = "[password hidden scheme %s]"%v.scheme
523+
else:
524+
# Don't divulge it's a locked account. Choose most
525+
# secure as default.
526+
result[pn] = "[password hidden scheme PBKDF2]"
527+
else:
528+
result[pn] = v
529+
except KeyError as msg:
530+
raise UsageError("%s field not valid" % msg)
531+
532+
return result
533+
534+
474535
@Routing.route("/data/<:class_name>", 'GET')
475536
@_data_decorator
476537
def get_collection(self, class_name, input):
@@ -509,6 +570,7 @@ def get_collection(self, class_name, input):
509570
'index': 1 # setting just size starts at page 1
510571
}
511572
verbose = 1
573+
display_props = {}
512574
for form_field in input.value:
513575
key = form_field.name
514576
value = form_field.value
@@ -518,6 +580,12 @@ def get_collection(self, class_name, input):
518580
page[key] = value
519581
elif key == "@verbose":
520582
verbose = int (value)
583+
elif key == "@fields" or key == "@attrs":
584+
f = value.split(",")
585+
if len(f) == 1:
586+
f=value.split(",")
587+
for i in f:
588+
display_props[i] = class_obj.properties[i]
521589
else: # serve the filter purpose
522590
prop = class_obj.getprops()[key]
523591
# We drop properties without search permission silently
@@ -543,26 +611,31 @@ def get_collection(self, class_name, input):
543611
else:
544612
obj_list = class_obj.filter(None, filter_props)
545613

614+
# Sort list as specified by sortorder
615+
# This is more useful for things where there is an
616+
# explicit order. E.G. status has an order that is
617+
# roughly the progression of the issue through
618+
# the states so open is before closed.
619+
obj_list.sort()
620+
621+
# add verbose elements. 2 and above get identifying label.
622+
if verbose > 1:
623+
lp = class_obj.labelprop()
624+
display_props[lp] = class_obj.properties[lp]
625+
546626
# extract result from data
547627
result={}
548-
result['collection'] = [
549-
{'id': item_id, 'link': class_path + item_id}
550-
for item_id in obj_list
628+
result['collection']=[]
629+
for item_id in obj_list:
551630
if self.db.security.hasPermission(
552-
'View', uid, class_name, itemid=item_id
553-
)
554-
]
555-
556-
# add verbose elements. First identifying label.
557-
if verbose > 1:
558-
label = class_obj.labelprop()
559-
for obj in result['collection']:
560-
id = obj['id']
561-
if self.db.security.hasPermission(
562-
'View', uid, class_name, property=label,
563-
itemid=id
564-
):
565-
obj[label] = class_obj.get(id, label)
631+
'View', uid, class_name, itemid=item_id):
632+
r = {'id': item_id, 'link': class_path + item_id}
633+
if display_props:
634+
r.update(self.format_item(class_obj.getnode(item_id),
635+
item_id,
636+
props=display_props,
637+
verbose=verbose))
638+
result['collection'].append(r)
566639

567640
result_len = len(result['collection'])
568641

@@ -657,66 +730,37 @@ def get_element(self, class_name, item_id, input):
657730
for form_field in input.value:
658731
key = form_field.name
659732
value = form_field.value
660-
if key == "@fields":
661-
props = value.split(",")
662-
if key == "@protected":
733+
if key == "@fields" or key == "@attrs":
734+
if props is None:
735+
props = {}
736+
# support , or : separated elements
737+
f=value.split(",")
738+
if len(f) == 1:
739+
f=value.split(":")
740+
for i in f:
741+
props[i] = class_obj.properties[i]
742+
elif key == "@protected":
663743
# allow client to request read only
664744
# properties like creator, activity etc.
745+
# used only if no @fields/@attrs
665746
protected = value.lower() == "true"
666-
if key == "@verbose":
747+
elif key == "@verbose":
667748
verbose = int (value)
668749

669750
result = {}
670751
if props is None:
671752
props = class_obj.getprops(protected=protected)
753+
else:
754+
if verbose > 1:
755+
lp = class_obj.labelprop()
756+
props[lp] = class_obj.properties[lp]
672757

673-
try:
674-
for pn in sorted(props):
675-
prop = props[pn]
676-
if not self.db.security.hasPermission(
677-
'View', uid, class_name, pn, itemid
678-
):
679-
continue
680-
v = getattr(node, pn)
681-
if isinstance (prop, (hyperdb.Link, hyperdb.Multilink)):
682-
linkcls = self.db.getclass (prop.classname)
683-
cp = '%s/%s/' % (self.data_path, prop.classname)
684-
if verbose and v:
685-
if isinstance(v, type([])):
686-
r = []
687-
for id in v:
688-
d = dict(id = id, link = cp + id)
689-
if verbose > 1:
690-
label = linkcls.labelprop()
691-
d [label] = linkcls.get(id, label)
692-
r.append(d)
693-
result[pn] = r
694-
else:
695-
result[pn] = dict(id = v, link = cp + v)
696-
if verbose > 1:
697-
label = linkcls.labelprop()
698-
result[pn][label] = linkcls.get(v, label)
699-
else:
700-
result[pn] = v
701-
elif isinstance (prop, hyperdb.String) and pn == 'content':
702-
# Do not show the (possibly HUGE) content prop
703-
# unless very verbose, we display the standard
704-
# download link instead
705-
if verbose < 3:
706-
u = self.db.config.TRACKER_WEB
707-
p = u + '%s%s/' % (class_name, node.id)
708-
result[pn] = dict(link = p)
709-
else:
710-
result[pn] = v
711-
else:
712-
result[pn] = v
713-
except KeyError as msg:
714-
raise UsageError("%s field not valid" % msg)
715758
result = {
716759
'id': itemid,
717760
'type': class_name,
718761
'link': '%s/%s/%s' % (self.data_path, class_name, item_id),
719-
'attributes': dict(result),
762+
'attributes': self.format_item(node, itemid, props=props,
763+
verbose=verbose),
720764
'@etag': etag
721765
}
722766

0 commit comments

Comments
 (0)