Пример #1
0
        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);
        }
Пример #2
0
 public PatchMember(
     string propertyPathInParent, 
     JsonPatchProperty jsonPatchProperty,
     object target = null)
 {
     Target = target;
     PropertyPathInParent = propertyPathInParent;
     JsonPatchProperty = jsonPatchProperty;
 }
Пример #3
0
 public PatchMember(
     string propertyPathInParent,
     JsonPatchProperty jsonPatchProperty,
     object target = null)
 {
     Target = target;
     PropertyPathInParent = propertyPathInParent;
     JsonPatchProperty    = jsonPatchProperty;
 }
Пример #4
0
        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);
            }
        }
Пример #5
0
 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);
     }
 }
Пример #6
0
        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)
            });
        }
Пример #7
0
        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)
            });
        }
Пример #8
0
        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;
                }
            }
        }
Пример #9
0
        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;
        }
Пример #10
0
 private bool IsNonStringArray(JsonPatchProperty patchProperty)
 {
     return(!(patchProperty.Property.PropertyType == typeof(string)) &&
            typeof(IList).GetTypeInfo().IsAssignableFrom(
                patchProperty.Property.PropertyType.GetTypeInfo()));
 }
Пример #11
0
 public PatchOperationResult(JsonPatchProperty property, object oldValue, object  newValue)
 {
     Property = property;
     OldValue = oldValue;
     NewValue = newValue;
 }
Пример #12
0
        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;
                }
            }
        }
Пример #13
0
 public PatchOperationResult(JsonPatchProperty property, object oldValue, object newValue)
 {
     Property = property;
     OldValue = oldValue;
     NewValue = newValue;
 }