@@ -470,6 +470,45 @@ def prop_from_arg(self, cl, key, value, itemid=None):
470470
471471 return prop
472472
473+ def transitive_props (self , class_name , props ):
474+ """Construct a list of transitive properties from the given
475+ argument, and return it after permission check. Raises
476+ Unauthorised if no permission. Permission is checked by checking
477+ search-permission on the path (delimited by '.') excluding the
478+ last component and then checking View permission on the last
479+ component. We do not allow to traverse multilinks -- the last
480+ item of an expansion *may* be a multilink but in the middle of a
481+ transitive prop.
482+ """
483+ checked_props = []
484+ uid = self .db .getuid ()
485+ for p in props :
486+ pn = p
487+ cn = class_name
488+ if '.' in p :
489+ path , lc = p .rsplit ('.' , 1 )
490+ if not self .db .security .hasSearchPermission (uid , class_name , p ):
491+ raise (Unauthorised
492+ ('User does not have permission on "%s.%s"'
493+ % (class_name , p )))
494+ prev = prop = None
495+ # This shouldn't raise any errors, otherwise the search
496+ # permission check above would have failed
497+ for pn in path .split ('.' ):
498+ cls = self .db .getclass (cn )
499+ prop = cls .getprops (protected = True )[pn ]
500+ if isinstance (prop , hyperdb .Multilink ):
501+ raise UsageError (
502+ 'Multilink Traversal not allowed: %s' % p )
503+ cn = prop .classname
504+ cls = self .db .getclass (cn )
505+ # Now we have the classname in cn and the prop name in pn.
506+ if not self .db .security .hasPermission ('View' , uid , cn , pn ):
507+ raise (Unauthorised
508+ ('User does not have permission on "%s.%s"' % (cn , pn )))
509+ checked_props .append (p )
510+ return checked_props
511+
473512 def error_obj (self , status , msg , source = None ):
474513 """Return an error object"""
475514 self .client .response_code = status
@@ -567,12 +606,24 @@ def format_item(self, node, item_id, props=None, verbose=1):
567606 try :
568607 # pn = propname
569608 for pn in sorted (props ):
570- prop = props [pn ]
571- if not self .db .security .hasPermission (
572- 'View' , uid , class_name , pn , item_id
573- ):
609+ ok = False
610+ id = item_id
611+ nd = node
612+ cn = class_name
613+ for p in pn .split ('.' ):
614+ if not self .db .security .hasPermission (
615+ 'View' , uid , cn , p , id
616+ ):
617+ break
618+ cl = self .db .getclass (cn )
619+ nd = cl .getnode (id )
620+ id = v = getattr (nd , p )
621+ prop = cl .getprops (protected = True )[p ]
622+ cn = getattr (prop , 'classname' , None )
623+ else :
624+ ok = True
625+ if not ok :
574626 continue
575- v = getattr (node , pn )
576627 if isinstance (prop , (hyperdb .Link , hyperdb .Multilink )):
577628 linkcls = self .db .getclass (prop .classname )
578629 cp = '%s/%s/' % (self .data_path , prop .classname )
@@ -654,7 +705,7 @@ def get_collection(self, class_name, input):
654705 'index' : 1 # setting just size starts at page 1
655706 }
656707 verbose = 1
657- display_props = {}
708+ display_props = set ()
658709 sort = []
659710 for form_field in input .value :
660711 key = form_field .name
@@ -670,12 +721,7 @@ def get_collection(self, class_name, input):
670721 if len (f ) == 1 :
671722 f = value .split (":" )
672723 allprops = class_obj .getprops (protected = True )
673- for i in f :
674- try :
675- display_props [i ] = allprops [i ]
676- except KeyError :
677- raise UsageError ("Failed to find property '%s' "
678- "for class %s." % (i , class_name ))
724+ display_props .update (self .transitive_props (class_name , f ))
679725 elif key == "@sort" :
680726 f = value .split ("," )
681727 allprops = class_obj .getprops (protected = True )
@@ -780,8 +826,7 @@ def get_collection(self, class_name, input):
780826 # add verbose elements. 2 and above get identifying label.
781827 if verbose > 1 :
782828 lp = class_obj .labelprop ()
783- # Label prop may be a protected property like activity
784- display_props [lp ] = class_obj .getprops (protected = True )[lp ]
829+ display_props .add (lp )
785830
786831 # extract result from data
787832 result = {}
@@ -891,17 +936,13 @@ def get_element(self, class_name, item_id, input):
891936 value = form_field .value
892937 if key == "@fields" or key == "@attrs" :
893938 if props is None :
894- props = {}
939+ props = set ()
895940 # support , or : separated elements
896941 f = value .split ("," )
897942 if len (f ) == 1 :
898943 f = value .split (":" )
899944 allprops = class_obj .getprops (protected = True )
900- for i in f :
901- try :
902- props [i ] = allprops [i ]
903- except KeyError :
904- raise UsageError ("Failed to find property '%s' for class %s." % (i , class_name ))
945+ props .update (self .transitive_props (class_name , f ))
905946 elif key == "@protected" :
906947 # allow client to request read only
907948 # properties like creator, activity etc.
@@ -912,11 +953,11 @@ def get_element(self, class_name, item_id, input):
912953
913954 result = {}
914955 if props is None :
915- props = class_obj .getprops (protected = protected )
956+ props = set ( class_obj .getprops (protected = protected ) )
916957 else :
917958 if verbose > 1 :
918959 lp = class_obj .labelprop ()
919- props [ lp ] = class_obj . properties [ lp ]
960+ props . add ( lp )
920961
921962 result = {
922963 'id' : itemid ,
0 commit comments