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 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; }
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); }