public static Func <T, bool> ToPredicate <T>(this PathFilterExpression filterExpression) { if (filterExpression == null) { return(user => true); } if (string.IsNullOrWhiteSpace(filterExpression.Filter)) { throw new Exception("Invalid scim filter"); // TODO: (DG) exception handling } // parse our filter into an expression tree var declaryingType = filterExpression.Path == null ? typeof(T) : typeof(T) .GetProperty(filterExpression.Path, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase) .PropertyType; // if enumerable, get generic argument if (declaryingType.IsNonStringEnumerable()) { declaryingType = declaryingType.GetGenericArguments()[0]; } var lexer = new ScimFilterLexer(new AntlrInputStream(filterExpression.Filter)); var parser = new ScimFilterParser(new CommonTokenStream(lexer)); // create a visitor for the type of enumerable generic argument var filterVisitorType = typeof(ScimFilterVisitor <>).MakeGenericType(declaryingType); var filterVisitor = (IScimFilterVisitor)filterVisitorType.CreateInstance(); var filterDelegateExpression = filterVisitor.VisitExpression(parser.parse()); if (filterExpression.Path == null) { return(filterDelegateExpression.Compile().AsFunc <T, bool>()); } // TODO: (DG) add more comments var resourceArg = Expression.Parameter(typeof(T)); var resourceComplexAttr = Expression.Property(resourceArg, filterExpression.Path); var invokeFilterExpression = Expression.Invoke(filterDelegateExpression, resourceComplexAttr); var predicate = Expression.Lambda <Func <T, bool> >(invokeFilterExpression, resourceArg); return(predicate.Compile().AsFunc <T, bool>()); }
private IEnumerable<Node> GetAffectedMembers( IList<PathFilterExpression> pathTree, ref int lastPosition, Node node) { for (int i = lastPosition; i < pathTree.Count; i++) { // seems absurd, but this MAY be called recursively, OR simply // iterated via the for loop lastPosition = i; var jsonContract = (JsonObjectContract)_ContractResolver.ResolveContract(node.Target.GetType()); var attemptedProperty = jsonContract.Properties.GetClosestMatchProperty(pathTree[i].Path); if (attemptedProperty == null) { if (!_ServerConfiguration.ResourceExtensionExists(pathTree[i].Path)) { // property cannot be found, and we're not working with an extension. ErrorType = ScimErrorType.InvalidPath; break; } // this is a resource extension // TODO: (DG) the code below works as well and will remove once it's determined how // repositories will access and persist extension data. Currently, Extensions property is public. // var memberInfo = node.Target.GetType().GetProperty("Extensions", BindingFlags.NonPublic | BindingFlags.Instance); // var property = new JsonProperty // { // PropertyType = memberInfo.PropertyType, // DeclaringType = memberInfo.DeclaringType, // ValueProvider = new ReflectionValueProvider(memberInfo), // AttributeProvider = new ReflectionAttributeProvider(memberInfo), // Readable = true, // Writable = true, // ShouldSerialize = member => false // }; attemptedProperty = jsonContract.Properties.GetProperty("extensions", StringComparison.Ordinal); } // if there's nothing to filter and we're not yet at the last path entry, continue if (pathTree[i].Filter == null && i != pathTree.Count - 1) { // if they enter an invalid target if (attemptedProperty.PropertyType.IsTerminalObject()) { ErrorType = ScimErrorType.InvalidPath; break; } object targetValue; var propertyType = attemptedProperty.PropertyType; // support for resource extensions if (propertyType == typeof (ResourceExtensions)) { var resourceExtensions = (ResourceExtensions) attemptedProperty.ValueProvider.GetValue(node.Target); var extensionType = _ServerConfiguration.GetResourceExtensionType(node.Target.GetType(), pathTree[i].Path); if (_Operation.OperationType == OperationType.Remove && !resourceExtensions.Contains(extensionType)) break; targetValue = resourceExtensions.GetOrCreate(extensionType); } else { // if targetValue is null, then we need to initialize it, UNLESS we're in a remove operation // e.g. user.name.givenName, when name == null targetValue = attemptedProperty.ValueProvider.GetValue(node.Target); if (targetValue == null) { if (_Operation.OperationType == OperationType.Remove) break; if (!propertyType.IsNonStringEnumerable()) { // if just a normal complex type, just create a new instance targetValue = propertyType.CreateInstance(); } else { var enumerableInterface = propertyType.GetEnumerableType(); var listArgumentType = enumerableInterface.GetGenericArguments()[0]; var listType = typeof (List<>).MakeGenericType(listArgumentType); targetValue = listType.CreateInstance(); } attemptedProperty.ValueProvider.SetValue(node.Target, targetValue); } } // the Target becomes the Target's child property value // the Parent becomes the current Target node = new Node(targetValue, node.Target); continue; // keep traversing the path tree } if (pathTree[i].Filter != null) { // we can only filter enumerable types if (!attemptedProperty.PropertyType.IsNonStringEnumerable()) { ErrorType = ScimErrorType.InvalidFilter; break; } var enumerable = attemptedProperty.ValueProvider.GetValue(node.Target) as IEnumerable; if (enumerable == null) { // if the value of the attribute is null then there's nothing to filter // it should never get here beause ScimObjectAdapter should apply a // different ruleset for null values; replacing or setting the attribute value ErrorType = ScimErrorType.NoTarget; break; } dynamic predicate; try { // parse our filter into an expression tree var lexer = new ScimFilterLexer(new AntlrInputStream(pathTree[i].Filter)); var parser = new ScimFilterParser(new CommonTokenStream(lexer)); // create a visitor for the type of enumerable generic argument var enumerableType = attemptedProperty.PropertyType.GetGenericArguments()[0]; var filterVisitorType = typeof (ScimFilterVisitor<>).MakeGenericType(enumerableType); var filterVisitor = (IScimFilterVisitor) filterVisitorType.CreateInstance(_ServerConfiguration); predicate = filterVisitor.VisitExpression(parser.parse()).Compile(); } catch (Exception) { ErrorType = ScimErrorType.InvalidFilter; break; } // we have an enumerable and a filter predicate // for each element in the enumerable that satisfies the predicate, // visit that element as part of the path tree var originalHasElements = false; var filteredNodes = new List<Node>(); var enumerator = enumerable.GetEnumerator(); lastPosition = i + 1; // increase the position in the tree while (enumerator.MoveNext()) { originalHasElements = true; dynamic currentElement = enumerator.Current; if ((bool) predicate(currentElement)) { filteredNodes.AddRange( GetAffectedMembers( pathTree, ref lastPosition, new Node(enumerator.Current, node.Target))); } } /* SCIM PATCH 'replace' RULE: o If the target location is a multi-valued attribute for which a value selection filter ("valuePath") has been supplied and no record match was made, the service provider SHALL indicate failure by returning HTTP status code 400 and a "scimType" error code of "noTarget". */ if (originalHasElements && filteredNodes.Count == 0 && _Operation != null && _Operation.OperationType == OperationType.Replace) { throw new ScimPatchException( ScimErrorType.NoTarget, _Operation); } return filteredNodes; } } return new List<Node> { node }; }
private IEnumerable <Node> GetAffectedMembers( IList <PathFilterExpression> pathTree, ref int lastPosition, Node node) { for (int i = lastPosition; i < pathTree.Count; i++) { // seems absurd, but this MAY be called recursively, OR simply // iterated via the for loop lastPosition = i; var jsonContract = (JsonObjectContract)_ContractResolver.ResolveContract(node.Target.GetType()); var attemptedProperty = jsonContract.Properties.GetClosestMatchProperty(pathTree[i].Path); if (attemptedProperty == null) { if (!_ServerConfiguration.ResourceExtensionExists(pathTree[i].Path)) { // property cannot be found, and we're not working with an extension. ErrorType = ScimErrorType.InvalidPath; break; } // this is a resource extension // TODO: (DG) the code below works as well and will remove once it's determined how // repositories will access and persist extension data. Currently, Extensions property is public. // var memberInfo = node.Target.GetType().GetProperty("Extensions", BindingFlags.NonPublic | BindingFlags.Instance); // var property = new JsonProperty // { // PropertyType = memberInfo.PropertyType, // DeclaringType = memberInfo.DeclaringType, // ValueProvider = new ReflectionValueProvider(memberInfo), // AttributeProvider = new ReflectionAttributeProvider(memberInfo), // Readable = true, // Writable = true, // ShouldSerialize = member => false // }; attemptedProperty = jsonContract.Properties.GetProperty("extensions", StringComparison.Ordinal); } // if there's nothing to filter and we're not yet at the last path entry, continue if (pathTree[i].Filter == null && i != pathTree.Count - 1) { // if they enter an invalid target if (attemptedProperty.PropertyType.IsTerminalObject()) { ErrorType = ScimErrorType.InvalidPath; break; } object targetValue; var propertyType = attemptedProperty.PropertyType; // support for resource extensions if (propertyType == typeof(ResourceExtensions)) { var resourceExtensions = (ResourceExtensions)attemptedProperty.ValueProvider.GetValue(node.Target); var extensionType = _ServerConfiguration.GetResourceExtensionType(node.Target.GetType(), pathTree[i].Path); if (_Operation.OperationType == OperationType.Remove && !resourceExtensions.Contains(extensionType)) { break; } targetValue = resourceExtensions.GetOrCreate(extensionType); } else { // if targetValue is null, then we need to initialize it, UNLESS we're in a remove operation // e.g. user.name.givenName, when name == null targetValue = attemptedProperty.ValueProvider.GetValue(node.Target); if (targetValue == null) { if (_Operation.OperationType == OperationType.Remove) { break; } if (!propertyType.IsNonStringEnumerable()) { // if just a normal complex type, just create a new instance targetValue = propertyType.CreateInstance(); } else { var enumerableInterface = propertyType.GetEnumerableType(); var listArgumentType = enumerableInterface.GetGenericArguments()[0]; var listType = typeof(List <>).MakeGenericType(listArgumentType); targetValue = listType.CreateInstance(); } attemptedProperty.ValueProvider.SetValue(node.Target, targetValue); } } // the Target becomes the Target's child property value // the Parent becomes the current Target node = new Node(targetValue, node.Target); continue; // keep traversing the path tree } if (pathTree[i].Filter != null) { // we can only filter enumerable types if (!attemptedProperty.PropertyType.IsNonStringEnumerable()) { ErrorType = ScimErrorType.InvalidFilter; break; } var enumerable = attemptedProperty.ValueProvider.GetValue(node.Target) as IEnumerable; if (enumerable == null) { // if the value of the attribute is null then there's nothing to filter // it should never get here beause ScimObjectAdapter should apply a // different ruleset for null values; replacing or setting the attribute value ErrorType = ScimErrorType.NoTarget; break; } dynamic predicate; try { // parse our filter into an expression tree var lexer = new ScimFilterLexer(new AntlrInputStream(pathTree[i].Filter)); var parser = new ScimFilterParser(new CommonTokenStream(lexer)); // create a visitor for the type of enumerable generic argument var enumerableType = attemptedProperty.PropertyType.GetGenericArguments()[0]; var filterVisitorType = typeof(ScimFilterVisitor <>).MakeGenericType(enumerableType); var filterVisitor = (IScimFilterVisitor)filterVisitorType.CreateInstance(_ServerConfiguration); predicate = filterVisitor.VisitExpression(parser.parse()).Compile(); } catch (Exception) { ErrorType = ScimErrorType.InvalidFilter; break; } // we have an enumerable and a filter predicate // for each element in the enumerable that satisfies the predicate, // visit that element as part of the path tree var originalHasElements = false; var filteredNodes = new List <Node>(); var enumerator = enumerable.GetEnumerator(); lastPosition = i + 1; // increase the position in the tree while (enumerator.MoveNext()) { originalHasElements = true; dynamic currentElement = enumerator.Current; if ((bool)predicate(currentElement)) { filteredNodes.AddRange( GetAffectedMembers( pathTree, ref lastPosition, new Node(enumerator.Current, node.Target))); } } /* SCIM PATCH 'replace' RULE: * o If the target location is a multi-valued attribute for which a * value selection filter ("valuePath") has been supplied and no * record match was made, the service provider SHALL indicate failure * by returning HTTP status code 400 and a "scimType" error code of * "noTarget". */ if (originalHasElements && filteredNodes.Count == 0 && _Operation != null && _Operation.OperationType == OperationType.Replace) { throw new ScimPatchException( ScimErrorType.NoTarget, _Operation); } return(filteredNodes); } } return(new List <Node> { node }); }