private bool CheckIfPropertyExists( JsonPatchProperty patchProperty, object objectToApplyTo, Operation operation, string propertyPath) { if (patchProperty == null) { LogError(new JsonPatchError( objectToApplyTo, operation, Resources.FormatPropertyDoesNotExist(propertyPath))); return(false); } if (patchProperty.Property.Ignored) { LogError(new JsonPatchError( objectToApplyTo, operation, Resources.FormatCannotUpdateProperty(propertyPath))); return(false); } return(true); }
public PatchMember( string propertyPathInParent, JsonPatchProperty jsonPatchProperty, object target = null) { Target = target; PropertyPathInParent = propertyPathInParent; JsonPatchProperty = jsonPatchProperty; }
private void EnforceMutability(Operation operation, JsonPatchProperty patchProperty) { IScimTypeAttributeDefinition attrDefinition; var typeDefinition = _ServerConfiguration.GetScimTypeDefinition(patchProperty.Parent.GetType()); var propertyInfo = patchProperty.Property.DeclaringType.GetProperty(patchProperty.Property.UnderlyingName); // if attribute is readOnly OR (immutable and isReferenceType and current value is not null) if (typeDefinition != null && typeDefinition.AttributeDefinitions.TryGetValue(propertyInfo, out attrDefinition) && (attrDefinition.Mutability == Mutability.ReadOnly || (attrDefinition.Mutability == Mutability.Immutable && !attrDefinition.AttributeDescriptor.PropertyType.IsValueType && patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent) != null))) { throw new ScimPatchException( ScimErrorType.Mutability, operation); } }
private void CheckIfPropertyExists( JsonPatchProperty patchProperty, T objectToApplyTo, Operation <T> operation, string propertyPath) { if (patchProperty == null) { throw new JsonPatchException <T>( operation, string.Format("Patch failed: property at location {0} does not exist", propertyPath), objectToApplyTo, 422); } if (patchProperty.Property.Ignored) { throw new JsonPatchException <T>( operation, string.Format("Patch failed: cannot update property at location {0}", propertyPath), objectToApplyTo, 422); } }
private IEnumerable <PatchOperationResult> ReplaceNonDynamic(object value, Operation operation, PatchMember patchMember) { var patchProperty = patchMember.JsonPatchProperty; if (!patchProperty.Property.Writable) { throw new Exception(); // TODO: (DG) This is int server error. } EnforceMutability(operation, patchProperty); var instanceValue = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); // if the instance's property value is null or it is not an enumerable type if (instanceValue == null || !patchProperty.Property.PropertyType.IsNonStringEnumerable()) { /* * Here we are going to be setting or replacing a current value: * o If the target location is a single-value attribute, the attributes * value is replaced. * o If the target location path specifies an attribute that does not * exist, the service provider SHALL treat the operation as an "add". * (instanceValue == null) * o If the target location is a complex multi-valued attribute with a * value selection filter ("valuePath") and a specific sub-attribute * (e.g., "addresses[type eq "work"].streetAddress"), the matching * sub-attribute of all matching records is replaced. * (!patchProperty.Property.PropertyType.IsNonStringEnumerable()) */ var conversionResultTuple = ConvertToActualType( patchProperty.Property.PropertyType, value, instanceValue); if (!conversionResultTuple.CanBeConverted) { throw new ScimPatchException( ScimErrorType.InvalidValue, operation); } patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, conversionResultTuple.ConvertedInstance); return(new[] { new PatchOperationResult( patchProperty, instanceValue, conversionResultTuple.ConvertedInstance) }); } // Here we are going to be modifying a complex object: // The first case below handles the following SCIM rules: // o If the target location is a multi-valued attribute and a value // selection("valuePath") filter is specified that matches one or // more values of the multi-valued attribute, then all matching // record values SHALL be replaced. // o If the target location specifies a complex attribute, a set of // sub-attributes SHALL be specified in the "value" parameter, which // replaces any existing values or adds where an attribute did not // previously exist. Sub-attributes that are not specified in the // "value" parameter are left unchanged. if (patchMember.Target != null && (patchMember.Target is MultiValuedAttribute || !patchMember.Target.GetType().IsTerminalObject())) { // if value is null, we're setting the MultiValuedAttribute to null if (value == null) { return(new[] { RemoveNonDynamic(operation, patchMember) }); } // value should be an object composed of sub-attributes of the parent, a MultiValuedAttribute var operations = new List <PatchOperationResult>(); var resourcePatch = JObject.Parse(value.ToString()); var jsonContract = (JsonObjectContract)ContractResolver.ResolveContract(patchMember.Target.GetType()); foreach (var kvp in resourcePatch) { var attemptedProperty = jsonContract.Properties.GetClosestMatchProperty(kvp.Key); var pp = new JsonPatchProperty(attemptedProperty, patchMember.Target); EnforceMutability(operation, pp); var patch = new PatchMember(kvp.Key, pp); operations.AddRange( AddNonDynamic( kvp.Value, new Operation(operation.OperationType, kvp.Key, kvp.Value), patch)); } return(operations); } // The second case handles the following SCIM rule: // o If the target location is a multi-valued attribute and no filter // is specified, the attribute and all values are replaced. var genericTypeOfArray = patchProperty.Property.PropertyType.GetEnumerableType(); var conversionResult = ConvertToActualType(genericTypeOfArray, value); if (!conversionResult.CanBeConverted) { throw new ScimPatchException(ScimErrorType.InvalidValue, operation); } var array = CreateGenericListFromObject(instanceValue, false); array.AddPossibleRange(conversionResult.ConvertedInstance); patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, array); return(new[] { new PatchOperationResult( patchProperty, instanceValue, array) }); }
private IEnumerable <PatchOperationResult> AddNonDynamic(object value, Operation operation, PatchMember patchMember) { var patchProperty = patchMember.JsonPatchProperty; EnforceMutability(operation, patchProperty); var instanceValue = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); if (instanceValue == null || !patchProperty.Property.PropertyType.IsNonStringEnumerable()) { /* * Here we are going to be setting or replacing a current value: * o If the target location does not exist, the attribute and value are added. * (instanceValue == null) * o If the target location specifies a single-valued attribute, the existing value is replaced. * (!patchProperty.Property.PropertyType.IsNonStringEnumerable()) * o If the target location exists, the value is replaced. * (!patchProperty.Property.PropertyType.IsNonStringEnumerable()) */ var conversionResultTuple = ConvertToActualType( patchProperty.Property.PropertyType, value); if (!conversionResultTuple.CanBeConverted) { throw new ScimPatchException( ScimErrorType.InvalidValue, operation); } if (!patchProperty.Property.Writable) { throw new Exception(); // TODO: (DG) This is int server error. } patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, conversionResultTuple.ConvertedInstance); return(new[] { new PatchOperationResult( patchProperty, instanceValue, conversionResultTuple.ConvertedInstance) }); } /* * o If the target location already contains the value specified, no * changes SHOULD be made to the resource, and a success response * SHOULD be returned. Unless other operations change the resource, * this operation SHALL NOT change the modify timestamp of the * resource. */ // The first case below handles the following SCIM rule: // o If the target location specifies a complex attribute, a set of // sub - attributes SHALL be specified in the "value" parameter. if (patchMember.Target != null && (patchMember.Target is MultiValuedAttribute || !patchMember.Target.GetType().IsTerminalObject())) { // value should be an object composed of sub-attributes of the parent, a MultiValuedAttribute var operations = new List <PatchOperationResult>(); var resourcePatch = JObject.Parse(operation.Value.ToString()); var jsonContract = (JsonObjectContract)ContractResolver.ResolveContract(patchMember.Target.GetType()); foreach (var kvp in resourcePatch) { var attemptedProperty = jsonContract.Properties.GetClosestMatchProperty(kvp.Key); var pp = new JsonPatchProperty(attemptedProperty, patchMember.Target); var patch = new PatchMember(kvp.Key, pp); EnforceMutability(operation, pp); operations.AddRange( AddNonDynamic( kvp.Value, new Operation(operation.OperationType, kvp.Key, kvp.Value), patch)); } return(operations); } // Here we are going to be modifying an existing enumerable: // The second case handles the following SCIM rule: // o If the target location specifies a multi-valued attribute, a new // value is added to the attribute. var genericTypeOfArray = patchProperty.Property.PropertyType.GetEnumerableType(); var conversionResult = ConvertToActualType(genericTypeOfArray, value); if (!conversionResult.CanBeConverted) { throw new ScimPatchException(ScimErrorType.InvalidValue, operation); } var array = CreateGenericListFromObject(instanceValue); array.AddPossibleRange(conversionResult.ConvertedInstance); patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, array); return(new[] { new PatchOperationResult( patchProperty, instanceValue, array) }); }
public ObjectTreeAnalysisResult( object objectToSearch, string propertyPath, IContractResolver contractResolver) { // construct the analysis result. // split the propertypath, and if necessary, remove the first // empty item (that's the case when it starts with a "/") var propertyPathTree = propertyPath.Split( new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // we've now got a split up property tree "base/property/otherproperty/..." int lastPosition = 0; object targetObject = objectToSearch; for (int i = 0; i < propertyPathTree.Length; i++) { lastPosition = i; // if the current target object is an ExpandoObject (IDictionary<string, object>), // we cannot use the ContractResolver. var dictionary = targetObject as IDictionary <string, object>; if (dictionary != null) { // find the value in the dictionary if (dictionary.ContainsCaseInsensitiveKey(propertyPathTree[i])) { var possibleNewTargetObject = dictionary.GetValueForCaseInsensitiveKey(propertyPathTree[i]); // unless we're at the last item, we should set the targetobject // to the new object. If we're at the last item, we need to stop if (i != propertyPathTree.Length - 1) { targetObject = possibleNewTargetObject; } } else { break; } } else { // if the current part of the path is numeric, this means we're trying // to get the propertyInfo of a specific object in an array. To allow // for this, the previous value (targetObject) must be an IEnumerable, and // the position must exist. int numericValue = -1; if (int.TryParse(propertyPathTree[i], out numericValue)) { var element = GetElementAtFromObject(targetObject, numericValue); if (element != null) { targetObject = element; } else { break; } } else { var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType()); // does the property exist? var attemptedProperty = jsonContract .Properties .FirstOrDefault(p => string.Equals(p.PropertyName, propertyPathTree[i], StringComparison.OrdinalIgnoreCase)); if (attemptedProperty != null) { // unless we're at the last item, we should continue searching. // If we're at the last item, we need to stop if ((i != propertyPathTree.Length - 1)) { targetObject = attemptedProperty.ValueProvider.GetValue(targetObject); } } else { // property cannot be found, and we're not working with dynamics. // Stop, and return invalid path. break; } } } } if (propertyPathTree.Length - lastPosition != 1) { IsValidPathForAdd = false; IsValidPathForRemove = false; return; } // two things can happen now. The targetproperty can be an IDictionary - in that // case, it's valid for add if there's 1 item left in the propertyPathTree. // // it can also be a property info. In that case, if there's nothing left in the path // tree we're at the end, if there's one left we can try and set that. if (targetObject is IDictionary <string, object> ) { UseDynamicLogic = true; Container = (IDictionary <string, object>)targetObject; IsValidPathForAdd = true; PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1]; // to be able to remove this property, it must exist IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent); } else if (targetObject is IList) { System.Diagnostics.Debugger.Launch(); UseDynamicLogic = false; int index; if (!Int32.TryParse(propertyPathTree[propertyPathTree.Length - 1], out index)) { // We only support indexing into a list IsValidPathForAdd = false; IsValidPathForRemove = false; return; } IsValidPathForAdd = true; IsValidPathForRemove = ((IList)targetObject).Count > index; PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1]; } else { UseDynamicLogic = false; var property = propertyPathTree[propertyPathTree.Length - 1]; var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType()); var attemptedProperty = jsonContract .Properties .FirstOrDefault(p => string.Equals(p.PropertyName, property, StringComparison.OrdinalIgnoreCase)); if (attemptedProperty == null) { IsValidPathForAdd = false; IsValidPathForRemove = false; } else { IsValidPathForAdd = true; IsValidPathForRemove = true; JsonPatchProperty = new JsonPatchProperty(attemptedProperty, targetObject); PropertyPathInParent = property; } } }
private bool CheckIfPropertyExists( JsonPatchProperty patchProperty, object objectToApplyTo, Operation operation, string propertyPath) { if (patchProperty == null) { LogError(new JsonPatchError( objectToApplyTo, operation, Resources.FormatPropertyDoesNotExist(propertyPath))); return false; } if (patchProperty.Property.Ignored) { LogError(new JsonPatchError( objectToApplyTo, operation, Resources.FormatCannotUpdateProperty(propertyPath))); return false; } return true; }
private bool IsNonStringArray(JsonPatchProperty patchProperty) { return(!(patchProperty.Property.PropertyType == typeof(string)) && typeof(IList).GetTypeInfo().IsAssignableFrom( patchProperty.Property.PropertyType.GetTypeInfo())); }
public PatchOperationResult(JsonPatchProperty property, object oldValue, object newValue) { Property = property; OldValue = oldValue; NewValue = newValue; }
public ObjectTreeAnalysisResult( object objectToSearch, string propertyPath, IContractResolver contractResolver) { // construct the analysis result. // split the propertypath, and if necessary, remove the first // empty item (that's the case when it starts with a "/") var propertyPathTree = propertyPath.Split( new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // we've now got a split up property tree "base/property/otherproperty/..." int lastPosition = 0; object targetObject = objectToSearch; for (int i = 0; i < propertyPathTree.Length; i++) { lastPosition = i; // if the current target object is an ExpandoObject (IDictionary<string, object>), // we cannot use the ContractResolver. var dictionary = targetObject as IDictionary<string, object>; if (dictionary != null) { // find the value in the dictionary if (dictionary.ContainsCaseInsensitiveKey(propertyPathTree[i])) { var possibleNewTargetObject = dictionary.GetValueForCaseInsensitiveKey(propertyPathTree[i]); // unless we're at the last item, we should set the targetobject // to the new object. If we're at the last item, we need to stop if (i != propertyPathTree.Length - 1) { targetObject = possibleNewTargetObject; } } else { break; } } else { // if the current part of the path is numeric, this means we're trying // to get the propertyInfo of a specific object in an array. To allow // for this, the previous value (targetObject) must be an IEnumerable, and // the position must exist. int numericValue = -1; if (int.TryParse(propertyPathTree[i], out numericValue)) { var element = GetElementAtFromObject(targetObject, numericValue); if (element != null) { targetObject = element; } else { break; } } else { var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType()); // does the property exist? var attemptedProperty = jsonContract .Properties .FirstOrDefault(p => string.Equals(p.PropertyName, propertyPathTree[i], StringComparison.OrdinalIgnoreCase)); if (attemptedProperty != null) { // unless we're at the last item, we should continue searching. // If we're at the last item, we need to stop if ((i != propertyPathTree.Length - 1)) { targetObject = attemptedProperty.ValueProvider.GetValue(targetObject); } } else { // property cannot be found, and we're not working with dynamics. // Stop, and return invalid path. break; } } } } if (propertyPathTree.Length - lastPosition != 1) { IsValidPathForAdd = false; IsValidPathForRemove = false; return; } // two things can happen now. The targetproperty can be an IDictionary - in that // case, it's valid for add if there's 1 item left in the propertyPathTree. // // it can also be a property info. In that case, if there's nothing left in the path // tree we're at the end, if there's one left we can try and set that. if (targetObject is IDictionary<string, object>) { UseDynamicLogic = true; Container = (IDictionary<string, object>)targetObject; IsValidPathForAdd = true; PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1]; // to be able to remove this property, it must exist IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent); } else if (targetObject is IList) { UseDynamicLogic = false; int index; if (!Int32.TryParse(propertyPathTree[propertyPathTree.Length - 1], out index)) { // We only support indexing into a list IsValidPathForAdd = false; IsValidPathForRemove = false; return; } IsValidPathForAdd = true; IsValidPathForRemove = ((IList)targetObject).Count > index; PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1]; } else { UseDynamicLogic = false; var property = propertyPathTree[propertyPathTree.Length - 1]; var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType()); var attemptedProperty = jsonContract .Properties .FirstOrDefault(p => string.Equals(p.PropertyName, property, StringComparison.OrdinalIgnoreCase)); if (attemptedProperty == null) { IsValidPathForAdd = false; IsValidPathForRemove = false; } else { IsValidPathForAdd = true; IsValidPathForRemove = true; JsonPatchProperty = new JsonPatchProperty(attemptedProperty, targetObject); PropertyPathInParent = property; } } }