feat: Update ModesExtractors to support new permission interface

This commit is contained in:
Joachim Van Herwegen
2022-06-29 10:56:17 +02:00
parent 23f0b37c28
commit 7085252b3f
8 changed files with 147 additions and 94 deletions

View File

@@ -1,8 +1,10 @@
import type { Operation } from '../../http/Operation';
import type { ResourceSet } from '../../storage/ResourceSet';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
import { isContainerIdentifier } from '../../util/PathUtil';
import { ModesExtractor } from './ModesExtractor';
import type { AccessMap } from './Permissions';
import { AccessMode } from './Permissions';
const READ_METHODS = new Set([ 'OPTIONS', 'GET', 'HEAD' ]);
@@ -31,33 +33,33 @@ export class MethodModesExtractor extends ModesExtractor {
}
}
public async handle({ method, target }: Operation): Promise<Set<AccessMode>> {
const modes = new Set<AccessMode>();
public async handle({ method, target }: Operation): Promise<AccessMap> {
const requiredModes: AccessMap = new IdentifierSetMultiMap();
// Reading requires Read permissions on the resource
if (READ_METHODS.has(method)) {
modes.add(AccessMode.read);
requiredModes.add(target, AccessMode.read);
}
// Setting a resource's representation requires Write permissions
if (method === 'PUT') {
modes.add(AccessMode.write);
requiredModes.add(target, AccessMode.write);
// …and, if the resource does not exist yet, Create permissions are required as well
if (!await this.resourceSet.hasResource(target)) {
modes.add(AccessMode.create);
requiredModes.add(target, AccessMode.create);
}
}
// Creating a new resource in a container requires Append access to that container
if (method === 'POST') {
modes.add(AccessMode.append);
requiredModes.add(target, AccessMode.append);
}
// Deleting a resource requires Delete access
if (method === 'DELETE') {
modes.add(AccessMode.delete);
requiredModes.add(target, AccessMode.delete);
// …and, if the target is a container, Read permissions are required as well
// as this exposes if a container is empty or not
if (isContainerIdentifier(target)) {
modes.add(AccessMode.read);
requiredModes.add(target, AccessMode.read);
}
}
return modes;
return requiredModes;
}
}

View File

@@ -3,7 +3,9 @@ import type { N3Patch } from '../../http/representation/N3Patch';
import { isN3Patch } from '../../http/representation/N3Patch';
import type { ResourceSet } from '../../storage/ResourceSet';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
import { ModesExtractor } from './ModesExtractor';
import type { AccessMap } from './Permissions';
import { AccessMode } from './Permissions';
/**
@@ -33,28 +35,28 @@ export class N3PatchModesExtractor extends ModesExtractor {
}
}
public async handle({ body, target }: Operation): Promise<Set<AccessMode>> {
public async handle({ body, target }: Operation): Promise<AccessMap> {
const { deletes, inserts, conditions } = body as N3Patch;
const accessModes = new Set<AccessMode>();
const requiredModes: AccessMap = new IdentifierSetMultiMap();
// When ?conditions is non-empty, servers MUST treat the request as a Read operation.
if (conditions.length > 0) {
accessModes.add(AccessMode.read);
requiredModes.add(target, AccessMode.read);
}
// When ?insertions is non-empty, servers MUST (also) treat the request as an Append operation.
if (inserts.length > 0) {
accessModes.add(AccessMode.append);
requiredModes.add(target, AccessMode.append);
if (!await this.resourceSet.hasResource(target)) {
accessModes.add(AccessMode.create);
requiredModes.add(target, AccessMode.create);
}
}
// When ?deletions is non-empty, servers MUST treat the request as a Read and Write operation.
if (deletes.length > 0) {
accessModes.add(AccessMode.read);
accessModes.add(AccessMode.write);
requiredModes.add(target, AccessMode.read);
requiredModes.add(target, AccessMode.write);
}
return accessModes;
return requiredModes;
}
}

View File

@@ -4,7 +4,9 @@ import type { Representation } from '../../http/representation/Representation';
import type { SparqlUpdatePatch } from '../../http/representation/SparqlUpdatePatch';
import type { ResourceSet } from '../../storage/ResourceSet';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
import { ModesExtractor } from './ModesExtractor';
import type { AccessMap } from './Permissions';
import { AccessMode } from './Permissions';
/**
@@ -34,31 +36,31 @@ export class SparqlUpdateModesExtractor extends ModesExtractor {
}
}
public async handle({ body, target }: Operation): Promise<Set<AccessMode>> {
public async handle({ body, target }: Operation): Promise<AccessMap> {
// Verified in `canHandle` call
const update = (body as SparqlUpdatePatch).algebra as Algebra.DeleteInsert;
const modes = new Set<AccessMode>();
const requiredModes: AccessMap = new IdentifierSetMultiMap();
if (this.isNop(update)) {
return modes;
return requiredModes;
}
// Access modes inspired by the requirements on N3 Patch requests
if (this.hasConditions(update)) {
modes.add(AccessMode.read);
requiredModes.add(target, AccessMode.read);
}
if (this.hasInserts(update)) {
modes.add(AccessMode.append);
requiredModes.add(target, AccessMode.append);
if (!await this.resourceSet.hasResource(target)) {
modes.add(AccessMode.create);
requiredModes.add(target, AccessMode.create);
}
}
if (this.hasDeletes(update)) {
modes.add(AccessMode.read);
modes.add(AccessMode.write);
requiredModes.add(target, AccessMode.read);
requiredModes.add(target, AccessMode.write);
}
return modes;
return requiredModes;
}
private isSparql(data: Representation): data is SparqlUpdatePatch {