diff --git a/src/authorization/permission-descriptors.ts b/src/authorization/permission-descriptors.ts index 8b9ff4d93..9d48c2670 100644 --- a/src/authorization/permission-descriptors.ts +++ b/src/authorization/permission-descriptors.ts @@ -5,7 +5,10 @@ import { BinaryOperationQueryNode, BinaryOperator, ConstBoolQueryNode, + CountQueryNode, FieldQueryNode, + IntersectionQueryNode, + ListQueryNode, LiteralQueryNode, QueryNode, UnknownValueQueryNode, @@ -239,6 +242,20 @@ export class ProfileBasedPermissionDescriptor extends PermissionDescriptor { if (restriction.valueTemplate !== undefined) { const values = permission.evaluateTemplate(restriction.valueTemplate, authContext); + + if (fieldPath.isList) { + return new BinaryOperationQueryNode( + new CountQueryNode( + new IntersectionQueryNode([ + fieldNode, + new LiteralQueryNode(values), + ]) + ), + BinaryOperator.GREATER_THAN_OR_EQUAL, + new LiteralQueryNode(1) + ); + } + return new BinaryOperationQueryNode( fieldNode, BinaryOperator.IN, @@ -255,6 +272,18 @@ export class ProfileBasedPermissionDescriptor extends PermissionDescriptor { if (!sanitizedClaimValues.length) { return ConstBoolQueryNode.FALSE; } + if (fieldPath.isList) { + return new BinaryOperationQueryNode( + new CountQueryNode( + new IntersectionQueryNode([ + fieldNode, + new LiteralQueryNode(sanitizedClaimValues), + ]) + ), + BinaryOperator.GREATER_THAN_OR_EQUAL, + new LiteralQueryNode(1) + ); + } return new BinaryOperationQueryNode( fieldNode, BinaryOperator.IN, diff --git a/src/database/arangodb/aql-generator.ts b/src/database/arangodb/aql-generator.ts index 954d90ce0..b39161766 100644 --- a/src/database/arangodb/aql-generator.ts +++ b/src/database/arangodb/aql-generator.ts @@ -28,6 +28,7 @@ import { FieldQueryNode, FirstOfListQueryNode, FollowEdgeQueryNode, + IntersectionQueryNode, ListItemQueryNode, ListQueryNode, LiteralQueryNode, @@ -450,6 +451,12 @@ register(ConcatListsQueryNode, (node, context) => { return aql`UNION(${listNodeStr})`; }); +register(IntersectionQueryNode, (node, context) => { + const listNodes = node.listNodes.map((node) => processNode(node, context)); + const listNodeStr = aql.join(listNodes, aql`, `); + return aql`INTERSECTION(${listNodeStr})`; +}); + register(VariableQueryNode, (node, context) => { return context.getVariable(node); }); @@ -1482,9 +1489,13 @@ function getRelationTraversalFragment({ if ( !( segment.vertexFilter instanceof BinaryOperationQueryNode && - segment.vertexFilter.lhs instanceof FieldQueryNode && - segment.vertexFilter.lhs.objectNode === segment.vertexFilterVariable + segment.vertexFilter.lhs instanceof FieldQueryNode ) + // !( + // segment.vertexFilter instanceof BinaryOperationQueryNode && + // segment.vertexFilter.lhs instanceof FieldQueryNode && + // segment.vertexFilter.lhs.objectNode === segment.vertexFilterVariable + // ) ) { throw new Error(`Unsupported filter pattern for graph traversal`); } diff --git a/src/query-tree/lists.ts b/src/query-tree/lists.ts index 7e1f6b706..73304d9bc 100644 --- a/src/query-tree/lists.ts +++ b/src/query-tree/lists.ts @@ -156,6 +156,20 @@ export class FirstOfListQueryNode extends QueryNode { } } + +export class IntersectionQueryNode extends QueryNode { + constructor(public readonly listNodes: ReadonlyArray) { + super(); + } + + describe() { + if (!this.listNodes.length) { + return `[]`; + } + return `intersection(${this.listNodes.map(listNode => listNode.describe()).join(',')})`; + } +} + /** * A node that evaluates to a specific item of a list, or NULL if the list is empty */