public ScimPatchObjectAnalysis(
            ScimServerConfiguration serverConfiguration,
            object objectToSearch, 
            string filter, 
            IContractResolver contractResolver,
            Operation operation)
        {
            _ServerConfiguration = serverConfiguration;
            _ContractResolver = contractResolver;
            _Operation = operation;
            PatchMembers = new List<PatchMember>();

            /* 
                ScimFilter.cs will handle normalizing the actual path string. 
                
                Examples:
                "path":"members"
                "path":"name.familyName"
                "path":"addresses[type eq \"work\"]"
                "path":"members[value eq \"2819c223-7f76-453a-919d-413861904646\"]"
                "path":"members[value eq \"2819c223-7f76-453a-919d-413861904646\"].displayName"

                Once normalized, associate each resource member with its filter (if present).
                This is represented as a PathMember, which is essentially a tuple of <memberName, memberFilter?>
            */
            var pathTree = new ScimFilter(_ServerConfiguration.ResourceExtensionSchemas.Keys, filter).Paths.ToList();
            var lastPosition = 0;
            var nodes = GetAffectedMembers(pathTree, ref lastPosition, new Node(objectToSearch, null));
            
            if ((pathTree.Count - lastPosition) > 1)
            {
                IsValidPathForAdd = false;
                IsValidPathForRemove = false;
                return;
            }

            foreach (var node in nodes)
            {
                var attribute = node.Target as MultiValuedAttribute;
                JsonProperty attemptedProperty;
                if (attribute != null && PathIsMultiValuedEnumerable(pathTree[pathTree.Count - 1].Path, node, out attemptedProperty))
                {
                    /* Check if we're at a MultiValuedAttribute.
                       If so, then we'll return a special PatchMember.  This is because our actual target is 
                       an element within an enumerable. (e.g. User->Emails[element])
                       So a PatchMember must have three pieces of information: (following the example above)
                       > Parent (User)
                       > PropertyPath (emails)
                       > Actual Target (email instance/element)
                    */

                    UseDynamicLogic = false;
                    IsValidPathForAdd = true;
                    IsValidPathForRemove = true;
                    PatchMembers.Add(
                        new PatchMember(
                            pathTree[pathTree.Count - 1].Path,
                            new JsonPatchProperty(attemptedProperty, node.Parent),
                            node.Target));
                }
                else
                {
                    UseDynamicLogic = false;

                    var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(node.Target.GetType());
                    attemptedProperty = jsonContract.Properties.GetClosestMatchProperty(pathTree[pathTree.Count - 1].Path);
                    if (attemptedProperty == null)
                    {
                        IsValidPathForAdd = false;
                        IsValidPathForRemove = false;
                    }
                    else
                    {
                        IsValidPathForAdd = true;
                        IsValidPathForRemove = true;
                        PatchMembers.Add(
                            new PatchMember(
                                pathTree[pathTree.Count - 1].Path,
                                new JsonPatchProperty(attemptedProperty, node.Target)));
                    }
                }
            }
        }
Exemple #2
0
        public ScimPatchObjectAnalysis(
            ScimServerConfiguration serverConfiguration,
            object objectToSearch,
            string filter,
            IContractResolver contractResolver,
            Operation operation)
        {
            _ServerConfiguration = serverConfiguration;
            _ContractResolver    = contractResolver;
            _Operation           = operation;
            PatchMembers         = new List <PatchMember>();

            /*
             *  ScimFilter.cs will handle normalizing the actual path string.
             *
             *  Examples:
             *  "path":"members"
             *  "path":"name.familyName"
             *  "path":"addresses[type eq \"work\"]"
             *  "path":"members[value eq \"2819c223-7f76-453a-919d-413861904646\"]"
             *  "path":"members[value eq \"2819c223-7f76-453a-919d-413861904646\"].displayName"
             *
             *  Once normalized, associate each resource member with its filter (if present).
             *  This is represented as a PathMember, which is essentially a tuple of <memberName, memberFilter?>
             */
            var pathTree     = new ScimFilter(_ServerConfiguration.ResourceExtensionSchemas.Keys, filter).Paths.ToList();
            var lastPosition = 0;
            var nodes        = GetAffectedMembers(pathTree, ref lastPosition, new Node(objectToSearch, null));

            if ((pathTree.Count - lastPosition) > 1)
            {
                IsValidPathForAdd    = false;
                IsValidPathForRemove = false;
                return;
            }

            foreach (var node in nodes)
            {
                var          attribute = node.Target as MultiValuedAttribute;
                JsonProperty attemptedProperty;
                if (attribute != null && PathIsMultiValuedEnumerable(pathTree[pathTree.Count - 1].Path, node, out attemptedProperty))
                {
                    /* Check if we're at a MultiValuedAttribute.
                     * If so, then we'll return a special PatchMember.  This is because our actual target is
                     * an element within an enumerable. (e.g. User->Emails[element])
                     * So a PatchMember must have three pieces of information: (following the example above)
                     * > Parent (User)
                     * > PropertyPath (emails)
                     * > Actual Target (email instance/element)
                     */

                    UseDynamicLogic      = false;
                    IsValidPathForAdd    = true;
                    IsValidPathForRemove = true;
                    PatchMembers.Add(
                        new PatchMember(
                            pathTree[pathTree.Count - 1].Path,
                            new JsonPatchProperty(attemptedProperty, node.Parent),
                            node.Target));
                }
                else
                {
                    UseDynamicLogic = false;

                    var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(node.Target.GetType());
                    attemptedProperty = jsonContract.Properties.GetClosestMatchProperty(pathTree[pathTree.Count - 1].Path);
                    if (attemptedProperty == null)
                    {
                        IsValidPathForAdd    = false;
                        IsValidPathForRemove = false;
                    }
                    else
                    {
                        IsValidPathForAdd    = true;
                        IsValidPathForRemove = true;
                        PatchMembers.Add(
                            new PatchMember(
                                pathTree[pathTree.Count - 1].Path,
                                new JsonPatchProperty(attemptedProperty, node.Target)));
                    }
                }
            }
        }
        public static ScimQueryOptions GetScimQueryOptions(this IReadableStringCollection collection, ScimServerConfiguration configuration)
        {
            var queryOptions = new ScimQueryOptions();

            queryOptions.Attributes = collection.GetValues("attributes")
                .ToMaybe()
                .Bind(attributes => new HashSet<string>(attributes.Distinct(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase).ToMaybe())
                .FromMaybe(new HashSet<string>());

            queryOptions.ExcludedAttributes = collection.GetValues("excludedAttributes")
                .ToMaybe()
                .Bind(attributes => new HashSet<string>(attributes.Distinct(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase).ToMaybe())
                .FromMaybe(new HashSet<string>());

            queryOptions.Filter = collection.Get("filter")
                .ToMaybe()
                .Bind(filter =>
                      {
                          var scimFilter = new ScimFilter(configuration.ResourceExtensionSchemas.Keys, filter);

                          return PathFilterExpression.CreateFilterOnly(scimFilter.NormalizedFilterExpression).ToMaybe();
                      })
                .FromMaybe(null);

            queryOptions.SortBy = collection.Get("sortBy");

            queryOptions.SortOrder = collection.Get("sortOrder")
                .ToMaybe()
                .Bind(
                    sortOrder =>
                        (sortOrder.Equals("descending", StringComparison.OrdinalIgnoreCase)
                            ? SortOrder.Descending
                            : SortOrder.Ascending).ToMaybe())
                .FromMaybe(SortOrder.Ascending); // default

            queryOptions.StartIndex = collection.Get("startIndex")
                .ToMaybe()
                .Bind(index =>
                {
                    // The 1-based index of the first query result. A value less than 1 SHALL be interpreted as 1.

                    int indexInt = 1;
                    try { indexInt = Convert.ToInt32(index); } catch {}
                    
                    return (indexInt < 1 ? 1 : indexInt).ToMaybe();
                })
                .FromMaybe(1); // default

            queryOptions.Count = collection.Get("count")
                .ToMaybe()
                .Bind(count =>
                {
                    // Non-negative integer. Specifies the desired maximum number of query 
                    // results per page, e.g., 10. A negative value SHALL be interpreted as 
                    // "0". A value of "0" indicates that no resource indicates that no resource 
                    // except for "totalResults".

                    int countInt = 0;
                    try { countInt = Convert.ToInt32(count); } catch { }

                    return (countInt < 0 ? 0 : countInt).ToMaybe();
                })
                .FromMaybe(configuration.GetFeature<ScimFeatureFilter>(ScimFeatureType.Filter).MaxResults); // default

            return queryOptions;
        }
Exemple #4
0
        public static ScimQueryOptions GetScimQueryOptions(this IReadableStringCollection collection, ScimServerConfiguration configuration)
        {
            var queryOptions = new ScimQueryOptions();

            queryOptions.Attributes = collection.GetValues("attributes")
                                      .ToMaybe()
                                      .Bind(attributes => new HashSet <string>(attributes.Distinct(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase).ToMaybe())
                                      .FromMaybe(new HashSet <string>());

            queryOptions.ExcludedAttributes = collection.GetValues("excludedAttributes")
                                              .ToMaybe()
                                              .Bind(attributes => new HashSet <string>(attributes.Distinct(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase).ToMaybe())
                                              .FromMaybe(new HashSet <string>());

            queryOptions.Filter = collection.Get("filter")
                                  .ToMaybe()
                                  .Bind(filter =>
            {
                var scimFilter = new ScimFilter(configuration.ResourceExtensionSchemas.Keys, filter);

                return(PathFilterExpression.CreateFilterOnly(scimFilter.NormalizedFilterExpression).ToMaybe());
            })
                                  .FromMaybe(null);

            queryOptions.SortBy = collection.Get("sortBy");

            queryOptions.SortOrder = collection.Get("sortOrder")
                                     .ToMaybe()
                                     .Bind(
                sortOrder =>
                (sortOrder.Equals("descending", StringComparison.OrdinalIgnoreCase)
                            ? SortOrder.Descending
                            : SortOrder.Ascending).ToMaybe())
                                     .FromMaybe(SortOrder.Ascending); // default

            queryOptions.StartIndex = collection.Get("startIndex")
                                      .ToMaybe()
                                      .Bind(index =>
            {
                // The 1-based index of the first query result. A value less than 1 SHALL be interpreted as 1.

                int indexInt = 1;
                try { indexInt = Convert.ToInt32(index); } catch {}

                return((indexInt < 1 ? 1 : indexInt).ToMaybe());
            })
                                      .FromMaybe(1); // default

            queryOptions.Count = collection.Get("count")
                                 .ToMaybe()
                                 .Bind(count =>
            {
                // Non-negative integer. Specifies the desired maximum number of query
                // results per page, e.g., 10. A negative value SHALL be interpreted as
                // "0". A value of "0" indicates that no resource indicates that no resource
                // except for "totalResults".

                int countInt = 0;
                try { countInt = Convert.ToInt32(count); } catch { }

                return((countInt < 0 ? 0 : countInt).ToMaybe());
            })
                                 .FromMaybe(configuration.GetFeature <ScimFeatureFilter>(ScimFeatureType.Filter).MaxResults); // default

            return(queryOptions);
        }