diff --git a/src/authorization/transformers/entities.ts b/src/authorization/transformers/entities.ts index 716f1de20..019ce40f9 100644 --- a/src/authorization/transformers/entities.ts +++ b/src/authorization/transformers/entities.ts @@ -4,8 +4,10 @@ import { ConditionalQueryNode, EntitiesQueryNode, EntityFromIdQueryNode, + FirstOfListQueryNode, NullQueryNode, PERMISSION_DENIED_ERROR, + RootEntityIDQueryNode, RuntimeErrorQueryNode, TransformListQueryNode, VariableAssignmentQueryNode, @@ -15,6 +17,7 @@ import { FlexSearchQueryNode } from '../../query-tree/flex-search'; import { AccessOperation, AuthContext } from '../auth-basics'; import { PermissionResult } from '../permission-descriptors'; import { getPermissionDescriptorOfRootEntityType } from '../permission-descriptors-in-model'; +import { decapitalize } from '../../utils/utils'; export function transformEntitiesQueryNode(node: EntitiesQueryNode, authContext: AuthContext) { const permissionDescriptor = getPermissionDescriptorOfRootEntityType(node.rootEntityType); @@ -57,17 +60,32 @@ export function transformEntityFromIdQueryNode( { code: PERMISSION_DENIED_ERROR }, ); default: - const entityVar = new VariableQueryNode('entity'); + // EntityFromIdQueryNode is converted to FIRST(FOR e in ... FILTER ... RETURN), so it's + // best to just add the condition there + + const itemVariable = new VariableQueryNode(decapitalize(node.rootEntityType.name)); const condition = permissionDescriptor.getAccessCondition( authContext, AccessOperation.READ, - entityVar, + itemVariable, + ); + const idEqualsNode = new BinaryOperationQueryNode( + new RootEntityIDQueryNode(itemVariable), + BinaryOperator.EQUAL, + node.idNode, + ); + return new FirstOfListQueryNode( + new TransformListQueryNode({ + listNode: new EntitiesQueryNode(node.rootEntityType), + itemVariable, + filterNode: new BinaryOperationQueryNode( + idEqualsNode, + BinaryOperator.AND, + condition, + ), + maxCount: 1, + }), ); - return new VariableAssignmentQueryNode({ - variableNode: entityVar, - variableValueNode: node, - resultNode: new ConditionalQueryNode(condition, entityVar, new NullQueryNode()), - }); } } diff --git a/src/database/arangodb/aql-generator.ts b/src/database/arangodb/aql-generator.ts index cc28b3db6..0898c0213 100644 --- a/src/database/arangodb/aql-generator.ts +++ b/src/database/arangodb/aql-generator.ts @@ -486,8 +486,24 @@ register(WithPreExecutionQueryNode, (node, context) => { }); register(EntityFromIdQueryNode, (node, context) => { - const collection = getCollectionForType(node.rootEntityType, AccessType.EXPLICIT_READ, context); - return aql`DOCUMENT(${collection}, ${processNode(node.idNode, context)})`; + // We previously used the DOCUMENT() function here, but that function is discouraged if the + // collection is statically known. https://docs.arangodb.com/3.11/aql/functions/miscellaneous/#document + + const itemVariable = new VariableQueryNode(decapitalize(node.rootEntityType.name)); + const transformed = new FirstOfListQueryNode( + new TransformListQueryNode({ + listNode: new EntitiesQueryNode(node.rootEntityType), + itemVariable, + filterNode: new BinaryOperationQueryNode( + new RootEntityIDQueryNode(itemVariable), + BinaryOperator.EQUAL, + node.idNode, + ), + maxCount: 1, + }), + ); + + return processNode(transformed, context); }); register(PropertyAccessQueryNode, (node, context) => {