@@ -64,7 +64,7 @@ def format_object(self, *args, **kwargs):
6464 except Unauthorised as msg :
6565 code = 403
6666 data = msg
67- except UsageError as msg :
67+ except ( UsageError , KeyError ) as msg :
6868 code = 400
6969 data = msg
7070 except (AttributeError , Reject ) as msg :
@@ -344,7 +344,7 @@ def __init__(self, client, db):
344344 self .base_path = '%srest' % (self .db .config .TRACKER_WEB )
345345 self .data_path = self .base_path + '/data'
346346
347- def props_from_args (self , cl , args , itemid = None ):
347+ def props_from_args (self , cl , args , itemid = None , skip_protected = True ):
348348 """Construct a list of properties from the given arguments,
349349 and return them after validation.
350350
@@ -354,18 +354,46 @@ def props_from_args(self, cl, args, itemid=None):
354354 itemid (string, optional): itemid of the object
355355
356356 Returns:
357- dict: dictionary of validated properties
357+ dict: dictionary of validated properties excluding
358+ protected properties if strip_protected=True.
359+
360+ Raises: UsageError if property does not exist and is not
361+ prefixed with @ indicating it's a meta variable.
362+
358363
359364 """
360- class_props = cl .properties .keys ()
365+ unprotected_class_props = cl .properties .keys ()
366+ protected_class_props = [ p for p in
367+ list (cl .getprops (protected = True ))
368+ if p not in unprotected_class_props ]
361369 props = {}
362370 # props = dict.fromkeys(class_props, None)
363371
364372 for arg in args :
365373 key = arg .name
366374 value = arg .value
367- if key not in class_props :
375+ if key .startswith ('@' ):
376+ # meta setting, not db property setting/reference
368377 continue
378+ if key in protected_class_props :
379+ # Skip protected props as a convenience.
380+ # Allows user to get object with all props,
381+ # change one prop, submit entire object
382+ # without having to remove any protected props
383+ # FIXME: Enhancement: raise error if value of prop
384+ # doesn't match db entry. In this case assume user
385+ # is really trying to set value. Another possibility is
386+ # they have an old copy of the data and it has been
387+ # updated. In the update case, we want etag validation
388+ # to generate the exception to reduce confusion. I think
389+ # etag validation occurs before this function is called but
390+ # I am not positive.
391+ if skip_protected :
392+ continue
393+ elif key not in unprotected_class_props :
394+ # report bad props as this is an error.
395+ raise UsageError ("Property %s not found in class %s" % (key ,
396+ cl .classname ))
369397 props [key ] = self .prop_from_arg (cl , key , value , itemid )
370398
371399 return props
@@ -390,7 +418,7 @@ def prop_from_arg(self, cl, key, value, itemid=None):
390418 x = key .encode ('ascii' )
391419 except UnicodeEncodeError :
392420 raise UsageError (
393- 'argument %r is no valid ascii keyword' % key
421+ 'argument %r is not a valid ascii keyword' % key
394422 )
395423 if value :
396424 try :
@@ -944,6 +972,10 @@ def put_element(self, class_name, item_id, input):
944972 self .db .commit ()
945973 except (TypeError , IndexError , ValueError ) as message :
946974 raise ValueError (message )
975+ except KeyError as message :
976+ # key error returned for changing protected keys
977+ # and changing invalid keys
978+ raise UsageError (message )
947979
948980 result = {
949981 'id' : item_id ,
@@ -995,6 +1027,10 @@ def put_attribute(self, class_name, item_id, attr_name, input):
9951027 self .db .commit ()
9961028 except (TypeError , IndexError , ValueError ) as message :
9971029 raise ValueError (message )
1030+ except KeyError as message :
1031+ # key error returned for changing protected keys
1032+ # and changing invalid keys
1033+ raise UsageError (message )
9981034
9991035 result = {
10001036 'id' : item_id ,
@@ -1112,6 +1148,16 @@ def delete_attribute(self, class_name, item_id, attr_name, input):
11121148 )
11131149
11141150 class_obj = self .db .getclass (class_name )
1151+ if attr_name not in class_obj .getprops (protected = False ):
1152+ if attr_name in class_obj .getprops (protected = True ):
1153+ raise UsageError ("Attribute '%s' can not be updated " \
1154+ "for class %s." % (attr_name , class_name ))
1155+ else :
1156+ raise UsageError ("Attribute '%s' not valid for class %s." % (
1157+ attr_name , class_name ))
1158+ if attr_name in class_obj .get_required_props ():
1159+ raise UsageError ("Attribute '%s' is required by class %s and can not be deleted." % (
1160+ attr_name , class_name ))
11151161 props = {}
11161162 prop_obj = class_obj .get (item_id , attr_name )
11171163 if isinstance (prop_obj , list ):
@@ -1125,6 +1171,10 @@ def delete_attribute(self, class_name, item_id, attr_name, input):
11251171 self .db .commit ()
11261172 except (TypeError , IndexError , ValueError ) as message :
11271173 raise ValueError (message )
1174+ except KeyError as message :
1175+ # key error returned for changing protected keys
1176+ # and changing invalid keys
1177+ raise UsageError (message )
11281178
11291179 result = {
11301180 'status' : 'ok'
@@ -1196,8 +1246,10 @@ def patch_element(self, class_name, item_id, input):
11961246 }
11971247 else :
11981248 # else patch operation is processing data
1199- props = self .props_from_args (class_obj , input .value , item_id )
1249+ props = self .props_from_args (class_obj , input .value , item_id ,
1250+ skip_protected = False )
12001251
1252+ required_props = class_obj .get_required_props ()
12011253 for prop in props :
12021254 if not self .db .security .hasPermission (
12031255 'Edit' , self .db .getuid (), class_name , prop , item_id
@@ -1206,6 +1258,11 @@ def patch_element(self, class_name, item_id, input):
12061258 'Permission to edit %s of %s%s denied' %
12071259 (prop , class_name , item_id )
12081260 )
1261+ if op == 'remove' and prop in required_props :
1262+ raise UsageError (
1263+ "Attribute '%s' is required by class %s "
1264+ "and can not be removed." % (prop , class_name )
1265+ )
12091266
12101267 props [prop ] = self .patch_data (
12111268 op , class_obj .get (item_id , prop ), props [prop ]
@@ -1267,6 +1324,10 @@ def patch_attribute(self, class_name, item_id, attr_name, input):
12671324
12681325 prop = attr_name
12691326 class_obj = self .db .getclass (class_name )
1327+ if attr_name not in class_obj .getprops (protected = False ):
1328+ if attr_name in class_obj .getprops (protected = True ):
1329+ raise UsageError ("Attribute '%s' can not be updated " \
1330+ "for class %s." % (attr_name , class_name ))
12701331
12711332 self .raise_if_no_etag (class_name , item_id , input )
12721333
@@ -1285,6 +1346,10 @@ def patch_attribute(self, class_name, item_id, attr_name, input):
12851346 self .db .commit ()
12861347 except (TypeError , IndexError , ValueError ) as message :
12871348 raise ValueError (message )
1349+ except KeyError as message :
1350+ # key error returned for changing protected keys
1351+ # and changing invalid keys
1352+ raise UsageError (message )
12881353
12891354 result = {
12901355 'id' : item_id ,
@@ -1608,6 +1673,9 @@ def dispatch(self, method, uri, input):
16081673 self .client .setHeader ("Content-Type" , "application/xml" )
16091674 output = b2s (dicttoxml (output , root = False ))
16101675 else :
1676+ # FIXME?? consider moving this earlier. We should
1677+ # error out before doing any work if we can't
1678+ # display acceptable output.
16111679 self .client .response_code = 406
16121680 output = "Content type is not accepted by client"
16131681
0 commit comments