diff --git a/config/default.json b/config/default.json index 7f23a2036..1d0d2456a 100644 --- a/config/default.json +++ b/config/default.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/memory.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/dynamic.json b/config/dynamic.json index d14f7cc22..06f883598 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/dynamic.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/example-https-file.json b/config/example-https-file.json index ab73a9ce3..05fc06a13 100644 --- a/config/example-https-file.json +++ b/config/example-https-file.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/file.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/file-no-setup.json b/config/file-no-setup.json index 562e9e59c..a151ed078 100644 --- a/config/file-no-setup.json +++ b/config/file-no-setup.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/file.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/file.json b/config/file.json index 0d9b4a8a3..d92106691 100644 --- a/config/file.json +++ b/config/file.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/file.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/ldp/README.md b/config/ldp/README.md index e3d715862..2602b5ae3 100644 --- a/config/ldp/README.md +++ b/config/ldp/README.md @@ -30,5 +30,4 @@ Contains a list of metadata writers that will be run on outgoing responses. ## Modes Determines which modes are needed for requests, by default this is based on the used HTTP method. -* *acl*: The default setup with specific support for accessing .acl documents. -* *no-acl*: Same as above but interprets .acl documents as any other document. +* *default*: Bases required modes on HTTP method. diff --git a/config/ldp/modes/acl.json b/config/ldp/modes/acl.json deleted file mode 100644 index 0c106bd81..000000000 --- a/config/ldp/modes/acl.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", - "@graph": [ - { - "comment": "Makes sure acl files require control permissions.", - "@id": "urn:solid-server:default:ModesExtractor", - "@type": "WaterfallHandler", - "handlers": [ - { - "@type": "AclModesExtractor", - "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" } - }, - { "@type": "MethodModesExtractor" }, - { "@type": "SparqlPatchModesExtractor" } - ] - } - ] -} diff --git a/config/ldp/modes/no-acl.json b/config/ldp/modes/default.json similarity index 82% rename from config/ldp/modes/no-acl.json rename to config/ldp/modes/default.json index 9a58553ee..29759a36b 100644 --- a/config/ldp/modes/no-acl.json +++ b/config/ldp/modes/default.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld", "@graph": [ { - "comment": "Extracts the required permissions based on the HTTP method.", + "comment": "Determines required modes based on HTTP methods.", "@id": "urn:solid-server:default:ModesExtractor", "@type": "WaterfallHandler", "handlers": [ diff --git a/config/memory-subdomains.json b/config/memory-subdomains.json index 0f4268f02..e1f057c3f 100644 --- a/config/memory-subdomains.json +++ b/config/memory-subdomains.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/memory.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/path-routing.json b/config/path-routing.json index 0a2c08452..16ff10a92 100644 --- a/config/path-routing.json +++ b/config/path-routing.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/regex.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/sparql-endpoint-no-setup.json b/config/sparql-endpoint-no-setup.json index 7b7f2cd8e..6c9b94a98 100644 --- a/config/sparql-endpoint-no-setup.json +++ b/config/sparql-endpoint-no-setup.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/sparql.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/sparql-endpoint.json b/config/sparql-endpoint.json index 89074ffab..d6af94502 100644 --- a/config/sparql-endpoint.json +++ b/config/sparql-endpoint.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/sparql.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", diff --git a/config/util/auxiliary/strategies/acl.json b/config/util/auxiliary/strategies/acl.json index 931e1e1a3..0b5da90cb 100644 --- a/config/util/auxiliary/strategies/acl.json +++ b/config/util/auxiliary/strategies/acl.json @@ -15,6 +15,7 @@ "@type": "RdfValidator", "converter": { "@id": "urn:solid-server:default:RepresentationConverter" } }, + "ownAuthorization": true, "requiredInRoot": true }, { diff --git a/src/authorization/AllStaticReader.ts b/src/authorization/AllStaticReader.ts index 57eafab6f..a520aaa76 100644 --- a/src/authorization/AllStaticReader.ts +++ b/src/authorization/AllStaticReader.ts @@ -16,7 +16,8 @@ export class AllStaticReader extends PermissionReader { read: allow, write: allow, append: allow, - control: allow, + create: allow, + delete: allow, }); } diff --git a/src/authorization/AuxiliaryReader.ts b/src/authorization/AuxiliaryReader.ts index 758e0eff6..161303da7 100644 --- a/src/authorization/AuxiliaryReader.ts +++ b/src/authorization/AuxiliaryReader.ts @@ -1,4 +1,4 @@ -import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy'; +import type { AuxiliaryStrategy } from '../ldp/auxiliary/AuxiliaryStrategy'; import type { PermissionSet } from '../ldp/permissions/Permissions'; import { getLoggerFor } from '../logging/LogUtil'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; @@ -15,9 +15,9 @@ export class AuxiliaryReader extends PermissionReader { protected readonly logger = getLoggerFor(this); private readonly resourceReader: PermissionReader; - private readonly auxiliaryStrategy: AuxiliaryIdentifierStrategy; + private readonly auxiliaryStrategy: AuxiliaryStrategy; - public constructor(resourceReader: PermissionReader, auxiliaryStrategy: AuxiliaryIdentifierStrategy) { + public constructor(resourceReader: PermissionReader, auxiliaryStrategy: AuxiliaryStrategy) { super(); this.resourceReader = resourceReader; this.auxiliaryStrategy = auxiliaryStrategy; @@ -44,6 +44,11 @@ export class AuxiliaryReader extends PermissionReader { if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) { throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.'); } + + if (this.auxiliaryStrategy.usesOwnAuthorization(auxiliaryAuth.identifier)) { + throw new NotImplementedHttpError('Auxiliary resource uses its own permissions.'); + } + return { ...auxiliaryAuth, identifier: this.auxiliaryStrategy.getAssociatedIdentifier(auxiliaryAuth.identifier), diff --git a/src/authorization/WebAclReader.ts b/src/authorization/WebAclReader.ts index 1853d3874..e2e939852 100644 --- a/src/authorization/WebAclReader.ts +++ b/src/authorization/WebAclReader.ts @@ -3,7 +3,9 @@ import { Store } from 'n3'; import { CredentialGroup } from '../authentication/Credentials'; import type { Credential, CredentialSet } from '../authentication/Credentials'; import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy'; -import type { Permission, PermissionSet } from '../ldp/permissions/Permissions'; +import { AclMode } from '../ldp/permissions/AclPermission'; +import type { AclPermission } from '../ldp/permissions/AclPermission'; +import type { PermissionSet } from '../ldp/permissions/Permissions'; import { AccessMode } from '../ldp/permissions/Permissions'; import type { Representation } from '../ldp/representation/Representation'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; @@ -14,7 +16,6 @@ import { createErrorMessage } from '../util/errors/ErrorUtil'; import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError'; import { InternalServerError } from '../util/errors/InternalServerError'; import { NotFoundHttpError } from '../util/errors/NotFoundHttpError'; -import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy'; import { readableToQuads } from '../util/StreamUtil'; import { ACL, RDF } from '../util/Vocabularies'; @@ -22,11 +23,11 @@ import type { AccessChecker } from './access-checkers/AccessChecker'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -const modesMap: Record = { +const modesMap: Record = { [ACL.Read]: AccessMode.read, [ACL.Write]: AccessMode.write, [ACL.Append]: AccessMode.append, - [ACL.Control]: AccessMode.control, + [ACL.Control]: AclMode.control, } as const; /** @@ -50,12 +51,6 @@ export class WebAclReader extends PermissionReader { this.accessChecker = accessChecker; } - public async canHandle({ identifier }: PermissionReaderInput): Promise { - if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) { - throw new NotImplementedHttpError('WebAclAuthorizer does not support permissions on auxiliary resources.'); - } - } - /** * Checks if an agent is allowed to execute the requested actions. * Will throw an error if this is not the case. @@ -66,24 +61,28 @@ export class WebAclReader extends PermissionReader { // Determine the required access modes this.logger.debug(`Retrieving permissions of ${credentials.agent?.webId} for ${identifier.path}`); + const isAcl = this.aclStrategy.isAuxiliaryIdentifier(identifier); + const mainIdentifier = isAcl ? this.aclStrategy.getAssociatedIdentifier(identifier) : identifier; + // Determine the full authorization for the agent granted by the applicable ACL - const acl = await this.getAclRecursive(identifier); - return this.createPermissions(credentials, acl); + const acl = await this.getAclRecursive(mainIdentifier); + return this.createPermissions(credentials, acl, isAcl); } /** * Creates an Authorization object based on the quads found in the ACL. * @param credentials - Credentials to check permissions for. * @param acl - Store containing all relevant authorization triples. + * @param isAcl - If the target resource is an acl document. */ - private async createPermissions(credentials: CredentialSet, acl: Store): + private async createPermissions(credentials: CredentialSet, acl: Store, isAcl: boolean): Promise { const publicPermissions = await this.determinePermissions(acl, credentials.public); const agentPermissions = await this.determinePermissions(acl, credentials.agent); return { - [CredentialGroup.agent]: agentPermissions, - [CredentialGroup.public]: publicPermissions, + [CredentialGroup.agent]: this.updateAclPermissions(agentPermissions, isAcl), + [CredentialGroup.public]: this.updateAclPermissions(publicPermissions, isAcl), }; } @@ -93,10 +92,10 @@ export class WebAclReader extends PermissionReader { * @param acl - Store containing all relevant authorization triples. * @param credentials - Credentials to find the permissions for. */ - private async determinePermissions(acl: Store, credentials?: Credential): Promise { - const permissions: Permission = {}; + private async determinePermissions(acl: Store, credentials?: Credential): Promise { + const aclPermissions: AclPermission = {}; if (!credentials) { - return permissions; + return aclPermissions; } // Apply all ACL rules @@ -108,18 +107,43 @@ export class WebAclReader extends PermissionReader { const modes = acl.getObjects(rule, ACL.mode, null); for (const { value: mode } of modes) { if (mode in modesMap) { - permissions[modesMap[mode]] = true; + aclPermissions[modesMap[mode]] = true; } } } } - if (permissions.write) { + if (aclPermissions.write) { // Write permission implies Append permission - permissions.append = true; + aclPermissions.append = true; } - return permissions; + return aclPermissions; + } + + /** + * Sets the correct values for non-acl permissions such as create and delete. + * Also adds the correct values to indicate that having control permission + * implies having read/write/etc. on the acl resource. + * + * The main reason for keeping the control value is so we can correctly set the WAC-Allow header later. + */ + private updateAclPermissions(aclPermissions: AclPermission, isAcl: boolean): AclPermission { + if (isAcl) { + return { + read: aclPermissions.control, + append: aclPermissions.control, + write: aclPermissions.control, + create: aclPermissions.control, + delete: aclPermissions.control, + control: aclPermissions.control, + }; + } + return { + ...aclPermissions, + create: aclPermissions.write, + delete: aclPermissions.write, + }; } /** diff --git a/src/index.ts b/src/index.ts index d425f2722..87540bdf6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -154,7 +154,6 @@ export * from './ldp/operations/PostOperationHandler'; export * from './ldp/operations/PutOperationHandler'; // LDP/Permissions -export * from './ldp/permissions/AclModesExtractor'; export * from './ldp/permissions/Permissions'; export * from './ldp/permissions/ModesExtractor'; export * from './ldp/permissions/MethodModesExtractor'; diff --git a/src/ldp/auxiliary/AuxiliaryStrategy.ts b/src/ldp/auxiliary/AuxiliaryStrategy.ts index c3eb27636..6ff0c6b13 100644 --- a/src/ldp/auxiliary/AuxiliaryStrategy.ts +++ b/src/ldp/auxiliary/AuxiliaryStrategy.ts @@ -9,6 +9,12 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy' * supported by this strategy. */ export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy { + /** + * Whether this auxiliary resources uses its own authorization instead of the associated resource authorization. + * @param identifier - Identifier of the auxiliary resource. + */ + usesOwnAuthorization: (identifier: ResourceIdentifier) => boolean; + /** * Whether the root storage container requires this auxiliary resource to be present. * If yes, this means they can't be deleted individually from such a container. diff --git a/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts b/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts index b253c37fd..a80c89737 100644 --- a/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts +++ b/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts @@ -14,13 +14,15 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy { private readonly identifierStrategy: AuxiliaryIdentifierStrategy; private readonly metadataGenerator?: MetadataGenerator; private readonly validator?: Validator; + private readonly ownAuthorization: boolean; private readonly requiredInRoot: boolean; public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator, - validator?: Validator, requiredInRoot = false) { + validator?: Validator, ownAuthorization = false, requiredInRoot = false) { this.identifierStrategy = identifierStrategy; this.metadataGenerator = metadataGenerator; this.validator = validator; + this.ownAuthorization = ownAuthorization; this.requiredInRoot = requiredInRoot; } @@ -40,6 +42,10 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy { return this.identifierStrategy.getAssociatedIdentifier(identifier); } + public usesOwnAuthorization(): boolean { + return this.ownAuthorization; + } + public isRequiredInRoot(): boolean { return this.requiredInRoot; } diff --git a/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts b/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts index ad93ba191..d63bb5427 100644 --- a/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts +++ b/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts @@ -18,6 +18,11 @@ export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy super(sources); } + public usesOwnAuthorization(identifier: ResourceIdentifier): boolean { + const source = this.getMatchingSource(identifier); + return source.usesOwnAuthorization(identifier); + } + public isRequiredInRoot(identifier: ResourceIdentifier): boolean { const source = this.getMatchingSource(identifier); return source.isRequiredInRoot(identifier); diff --git a/src/ldp/operations/metadata/WebAclMetadataCollector.ts b/src/ldp/operations/metadata/WebAclMetadataCollector.ts index ebb469ba7..b9baf99b3 100644 --- a/src/ldp/operations/metadata/WebAclMetadataCollector.ts +++ b/src/ldp/operations/metadata/WebAclMetadataCollector.ts @@ -1,10 +1,13 @@ import { ACL, AUTH } from '../../../util/Vocabularies'; -import type { AccessMode } from '../../permissions/Permissions'; +import { AclMode } from '../../permissions/AclPermission'; +import type { AclPermission } from '../../permissions/AclPermission'; +import { AccessMode } from '../../permissions/Permissions'; import type { OperationMetadataCollectorInput } from './OperationMetadataCollector'; import { OperationMetadataCollector } from './OperationMetadataCollector'; const VALID_METHODS = new Set([ 'HEAD', 'GET' ]); +const VALID_ACL_MODES = new Set([ AccessMode.read, AccessMode.write, AccessMode.append, AclMode.control ]); /** * Indicates which acl permissions are available on the requested resource. @@ -15,18 +18,20 @@ export class WebAclMetadataCollector extends OperationMetadataCollector { if (!operation.permissionSet || !VALID_METHODS.has(operation.method)) { return; } - const user = operation.permissionSet.agent ?? {}; - const everyone = operation.permissionSet.public ?? {}; + const user: AclPermission = operation.permissionSet.agent ?? {}; + const everyone: AclPermission = operation.permissionSet.public ?? {}; const modes = new Set([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]); for (const mode of modes) { - const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control'; - if (everyone[mode]) { - metadata.add(AUTH.terms.publicMode, ACL.terms[capitalizedMode]); - } - if (user[mode]) { - metadata.add(AUTH.terms.userMode, ACL.terms[capitalizedMode]); + if (VALID_ACL_MODES.has(mode)) { + const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control'; + if (everyone[mode]) { + metadata.add(AUTH.terms.publicMode, ACL.terms[capitalizedMode]); + } + if (user[mode]) { + metadata.add(AUTH.terms.userMode, ACL.terms[capitalizedMode]); + } } } } diff --git a/src/ldp/permissions/AclModesExtractor.ts b/src/ldp/permissions/AclModesExtractor.ts deleted file mode 100644 index fc9c8497b..000000000 --- a/src/ldp/permissions/AclModesExtractor.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; -import type { AuxiliaryIdentifierStrategy } from '../auxiliary/AuxiliaryIdentifierStrategy'; -import type { Operation } from '../operations/Operation'; -import { ModesExtractor } from './ModesExtractor'; -import { AccessMode } from './Permissions'; - -export class AclModesExtractor extends ModesExtractor { - private readonly aclStrategy: AuxiliaryIdentifierStrategy; - - public constructor(aclStrategy: AuxiliaryIdentifierStrategy) { - super(); - this.aclStrategy = aclStrategy; - } - - public async canHandle({ target }: Operation): Promise { - if (!this.aclStrategy.isAuxiliaryIdentifier(target)) { - throw new NotImplementedHttpError('Can only determine permissions of acl resources'); - } - } - - public async handle(): Promise> { - return new Set([ AccessMode.control ]); - } -} diff --git a/src/ldp/permissions/AclPermission.ts b/src/ldp/permissions/AclPermission.ts new file mode 100644 index 000000000..a2ee939f9 --- /dev/null +++ b/src/ldp/permissions/AclPermission.ts @@ -0,0 +1,10 @@ +import type { Permission } from './Permissions'; + +export enum AclMode { + control = 'control', +} + +// Adds a control field to the permissions to specify this WAC-specific value +export type AclPermission = Permission & { + [mode in AclMode]?: boolean; +}; diff --git a/src/ldp/permissions/MethodModesExtractor.ts b/src/ldp/permissions/MethodModesExtractor.ts index bf18fd0db..3904ecde1 100644 --- a/src/ldp/permissions/MethodModesExtractor.ts +++ b/src/ldp/permissions/MethodModesExtractor.ts @@ -27,6 +27,8 @@ export class MethodModesExtractor extends ModesExtractor { if (WRITE_METHODS.has(method)) { result.add(AccessMode.write); result.add(AccessMode.append); + result.add(AccessMode.create); + result.add(AccessMode.delete); } else if (APPEND_METHODS.has(method)) { result.add(AccessMode.append); } diff --git a/src/ldp/permissions/Permissions.ts b/src/ldp/permissions/Permissions.ts index ff574130c..5129456f2 100644 --- a/src/ldp/permissions/Permissions.ts +++ b/src/ldp/permissions/Permissions.ts @@ -7,7 +7,8 @@ export enum AccessMode { read = 'read', append = 'append', write = 'write', - control = 'control', + create = 'create', + delete = 'delete', } /** diff --git a/src/ldp/permissions/SparqlPatchModesExtractor.ts b/src/ldp/permissions/SparqlPatchModesExtractor.ts index 6c891160d..b65eae333 100644 --- a/src/ldp/permissions/SparqlPatchModesExtractor.ts +++ b/src/ldp/permissions/SparqlPatchModesExtractor.ts @@ -31,6 +31,8 @@ export class SparqlPatchModesExtractor extends ModesExtractor { if (this.needsWrite(update)) { result.add(AccessMode.write); result.add(AccessMode.append); + result.add(AccessMode.create); + result.add(AccessMode.delete); } else if (this.needsAppend(update)) { result.add(AccessMode.append); } diff --git a/test/integration/LdpHandlerWithAuth.test.ts b/test/integration/LdpHandlerWithAuth.test.ts index 17e8f19ee..7193c1aac 100644 --- a/test/integration/LdpHandlerWithAuth.test.ts +++ b/test/integration/LdpHandlerWithAuth.test.ts @@ -177,7 +177,8 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig, const response = await fetch(`${baseUrl}.acl`); expect(response.status).toBe(200); - expect(response.headers.get('wac-allow')).toBe('user="control",public="control"'); + expect(response.headers.get('wac-allow')) + .toBe('user="append control read write",public="append control read write"'); // Close response await response.text(); diff --git a/test/integration/config/ldp-with-auth.json b/test/integration/config/ldp-with-auth.json index 27ac73f7f..5e994f547 100644 --- a/test/integration/config/ldp-with-auth.json +++ b/test/integration/config/ldp-with-auth.json @@ -13,7 +13,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", "files-scs:config/util/auxiliary/acl.json", diff --git a/test/integration/config/run-with-redlock.json b/test/integration/config/run-with-redlock.json index dff6ab4b5..7994412e8 100644 --- a/test/integration/config/run-with-redlock.json +++ b/test/integration/config/run-with-redlock.json @@ -13,7 +13,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/memory.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", diff --git a/test/integration/config/server-dynamic-unsafe.json b/test/integration/config/server-dynamic-unsafe.json index e790ca999..d95ff54f6 100644 --- a/test/integration/config/server-dynamic-unsafe.json +++ b/test/integration/config/server-dynamic-unsafe.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/dynamic.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", diff --git a/test/integration/config/server-memory.json b/test/integration/config/server-memory.json index 0e11939b7..9953f3e91 100644 --- a/test/integration/config/server-memory.json +++ b/test/integration/config/server-memory.json @@ -17,7 +17,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/memory.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/test/integration/config/server-subdomains-unsafe.json b/test/integration/config/server-subdomains-unsafe.json index b7ecaada0..096aa615b 100644 --- a/test/integration/config/server-subdomains-unsafe.json +++ b/test/integration/config/server-subdomains-unsafe.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", "files-scs:config/util/auxiliary/acl.json", diff --git a/test/integration/config/server-without-auth.json b/test/integration/config/server-without-auth.json index 740eb20c7..bc7b95943 100644 --- a/test/integration/config/server-without-auth.json +++ b/test/integration/config/server-without-auth.json @@ -13,7 +13,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/memory.json", "files-scs:config/storage/key-value/memory.json", "files-scs:config/storage/middleware/default.json", diff --git a/test/integration/config/setup-memory.json b/test/integration/config/setup-memory.json index a5d31deec..72e282f1f 100644 --- a/test/integration/config/setup-memory.json +++ b/test/integration/config/setup-memory.json @@ -18,7 +18,7 @@ "files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/metadata-parser/default.json", "files-scs:config/ldp/metadata-writer/default.json", - "files-scs:config/ldp/modes/acl.json", + "files-scs:config/ldp/modes/default.json", "files-scs:config/storage/backend/memory.json", "files-scs:config/storage/key-value/resource-store.json", "files-scs:config/storage/middleware/default.json", diff --git a/test/unit/authorization/AllStaticReader.test.ts b/test/unit/authorization/AllStaticReader.test.ts index 6767aff1b..797f3fa1a 100644 --- a/test/unit/authorization/AllStaticReader.test.ts +++ b/test/unit/authorization/AllStaticReader.test.ts @@ -7,7 +7,8 @@ function getPermissions(allow: boolean): Permission { read: allow, write: allow, append: allow, - control: allow, + create: allow, + delete: allow, }; } diff --git a/test/unit/authorization/AuxiliaryReader.test.ts b/test/unit/authorization/AuxiliaryReader.test.ts index f2c607475..e86358129 100644 --- a/test/unit/authorization/AuxiliaryReader.test.ts +++ b/test/unit/authorization/AuxiliaryReader.test.ts @@ -1,7 +1,7 @@ import { CredentialGroup } from '../../../src/authentication/Credentials'; import { AuxiliaryReader } from '../../../src/authorization/AuxiliaryReader'; import type { PermissionReader } from '../../../src/authorization/PermissionReader'; -import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; +import type { AuxiliaryStrategy } from '../../../src/ldp/auxiliary/AuxiliaryStrategy'; import type { PermissionSet } from '../../../src/ldp/permissions/Permissions'; import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; @@ -12,8 +12,8 @@ describe('An AuxiliaryReader', (): void => { const associatedIdentifier = { path: 'http://test.com/foo' }; const auxiliaryIdentifier = { path: 'http://test.com/foo.dummy' }; const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }}; - let source: PermissionReader; - let strategy: AuxiliaryIdentifierStrategy; + let source: jest.Mocked; + let strategy: jest.Mocked; let reader: AuxiliaryReader; beforeEach(async(): Promise => { @@ -27,6 +27,7 @@ describe('An AuxiliaryReader', (): void => { isAuxiliaryIdentifier: jest.fn((identifier: ResourceIdentifier): boolean => identifier.path.endsWith(suffix)), getAssociatedIdentifier: jest.fn((identifier: ResourceIdentifier): ResourceIdentifier => ({ path: identifier.path.slice(0, -suffix.length) })), + usesOwnAuthorization: jest.fn().mockReturnValue(false), } as any; reader = new AuxiliaryReader(source, strategy); }); @@ -39,7 +40,12 @@ describe('An AuxiliaryReader', (): void => { ); await expect(reader.canHandle({ identifier: associatedIdentifier, credentials })) .rejects.toThrow(NotImplementedHttpError); - source.canHandle = jest.fn().mockRejectedValue(new Error('no source support')); + + strategy.usesOwnAuthorization.mockReturnValueOnce(true); + await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials })) + .rejects.toThrow(NotImplementedHttpError); + + source.canHandle.mockRejectedValue(new Error('no source support')); await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials })) .rejects.toThrow('no source support'); }); @@ -61,9 +67,15 @@ describe('An AuxiliaryReader', (): void => { expect(source.handleSafe).toHaveBeenLastCalledWith( { identifier: associatedIdentifier, credentials }, ); + await expect(reader.handleSafe({ identifier: associatedIdentifier, credentials })) .rejects.toThrow(NotImplementedHttpError); - source.handleSafe = jest.fn().mockRejectedValue(new Error('no source support')); + + strategy.usesOwnAuthorization.mockReturnValueOnce(true); + await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials })) + .rejects.toThrow(NotImplementedHttpError); + + source.handleSafe.mockRejectedValue(new Error('no source support')); await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials })) .rejects.toThrow('no source support'); }); diff --git a/test/unit/authorization/UnionPermissionReader.test.ts b/test/unit/authorization/UnionPermissionReader.test.ts index 1d52fc269..c93452d2d 100644 --- a/test/unit/authorization/UnionPermissionReader.test.ts +++ b/test/unit/authorization/UnionPermissionReader.test.ts @@ -44,13 +44,13 @@ describe('A UnionPermissionReader', (): void => { it('merges same fields using false > true > undefined.', async(): Promise => { readers[0].handle.mockResolvedValue( - { [CredentialGroup.agent]: { read: true, write: false, append: undefined, control: true }}, + { [CredentialGroup.agent]: { read: true, write: false, append: undefined, create: true, delete: undefined }}, ); readers[1].handle.mockResolvedValue( - { [CredentialGroup.agent]: { read: false, write: true, append: true, control: true }}, + { [CredentialGroup.agent]: { read: false, write: true, append: true, create: true, delete: undefined }}, ); await expect(unionReader.handle(input)).resolves.toEqual({ - [CredentialGroup.agent]: { read: false, write: false, append: true, control: true }, + [CredentialGroup.agent]: { read: false, write: false, append: true, create: true }, }); }); }); diff --git a/test/unit/authorization/WebAclReader.test.ts b/test/unit/authorization/WebAclReader.test.ts index da951f15b..601154cf7 100644 --- a/test/unit/authorization/WebAclReader.test.ts +++ b/test/unit/authorization/WebAclReader.test.ts @@ -12,7 +12,6 @@ import { INTERNAL_QUADS } from '../../../src/util/ContentTypes'; import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError'; import { InternalServerError } from '../../../src/util/errors/InternalServerError'; import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError'; -import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy'; import { guardedStreamFrom } from '../../../src/util/StreamUtil'; @@ -51,10 +50,8 @@ describe('A WebAclReader', (): void => { reader = new WebAclReader(aclStrategy, store, identifierStrategy, accessChecker); }); - it('handles all non-acl inputs.', async(): Promise => { - await expect(reader.canHandle({ identifier, credentials })).resolves.toBeUndefined(); - await expect(reader.canHandle({ identifier: aclStrategy.getAuxiliaryIdentifier(identifier) } as any)) - .rejects.toThrow(NotImplementedHttpError); + it('handles all input.', async(): Promise => { + await expect(reader.canHandle({ } as any)).resolves.toBeUndefined(); }); it('returns undefined permissions for undefined credentials.', async(): Promise => { @@ -137,15 +134,39 @@ describe('A WebAclReader', (): void => { await expect(promise).rejects.toThrow(ForbiddenHttpError); }); - it('allows an agent to append if they have write access.', async(): Promise => { + it('allows an agent to append/create/delete if they have write access.', async(): Promise => { store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([ quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); await expect(reader.handle({ identifier, credentials })).resolves.toEqual({ - [CredentialGroup.public]: { write: true, append: true }, - [CredentialGroup.agent]: { write: true, append: true }, + [CredentialGroup.public]: { write: true, append: true, create: true, delete: true }, + [CredentialGroup.agent]: { write: true, append: true, create: true, delete: true }, + }); + }); + + it('allows everything on an acl resource if control permissions are granted.', async(): Promise => { + store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([ + quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), + quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)), + ]) } as Representation); + await expect(reader.handle({ identifier: { path: `${identifier.path}.acl` }, credentials })).resolves.toEqual({ + [CredentialGroup.public]: { read: true, write: true, append: true, create: true, delete: true, control: true }, + [CredentialGroup.agent]: { read: true, write: true, append: true, create: true, delete: true, control: true }, + }); + }); + + it('rejects everything on an acl resource if there are no control permissions.', async(): Promise => { + store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([ + quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), + quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + ]) } as Representation); + await expect(reader.handle({ identifier: { path: `${identifier.path}.acl` }, credentials })).resolves.toEqual({ + [CredentialGroup.public]: {}, + [CredentialGroup.agent]: {}, }); }); diff --git a/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts b/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts index f09b7962b..6a4dbaae4 100644 --- a/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts +++ b/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts @@ -24,7 +24,7 @@ describe('A ComposedAuxiliaryStrategy', (): void => { validator = { handleSafe: jest.fn(), } as any; - strategy = new ComposedAuxiliaryStrategy(identifierStrategy, metadataGenerator, validator, true); + strategy = new ComposedAuxiliaryStrategy(identifierStrategy, metadataGenerator, validator, false, true); }); it('calls the AuxiliaryIdentifierStrategy for related calls.', async(): Promise => { @@ -45,6 +45,10 @@ describe('A ComposedAuxiliaryStrategy', (): void => { expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier); }); + it('returns the injected value for usesOwnAuthorization.', async(): Promise => { + expect(strategy.usesOwnAuthorization()).toBe(false); + }); + it('returns the injected value for isRequiredInRoot.', async(): Promise => { expect(strategy.isRequiredInRoot()).toBe(true); }); diff --git a/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts b/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts index b239f31cf..b37c7b28e 100644 --- a/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts +++ b/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts @@ -11,6 +11,10 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy { this.suffix = suffix; } + public usesOwnAuthorization(): boolean { + return true; + } + public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { return { path: `${identifier.path}${this.suffix}` }; } @@ -77,6 +81,15 @@ describe('A RoutingAuxiliaryStrategy', (): void => { expect(sources[1].addMetadata).toHaveBeenLastCalledWith(metadata); }); + it('#usesOwnAuthorization returns the result of the correct source.', async(): Promise => { + sources[0].usesOwnAuthorization = jest.fn(); + sources[1].usesOwnAuthorization = jest.fn(); + strategy.usesOwnAuthorization(dummy2Id); + expect(sources[0].usesOwnAuthorization).toHaveBeenCalledTimes(0); + expect(sources[1].usesOwnAuthorization).toHaveBeenCalledTimes(1); + expect(sources[1].usesOwnAuthorization).toHaveBeenLastCalledWith(dummy2Id); + }); + it('#isRequiredInRoot returns the result of the correct source.', async(): Promise => { sources[0].isRequiredInRoot = jest.fn(); sources[1].isRequiredInRoot = jest.fn(); diff --git a/test/unit/ldp/operations/metadata/WebAclMetadataCollector.test.ts b/test/unit/ldp/operations/metadata/WebAclMetadataCollector.test.ts index 87bc1a2e9..dfc53dda7 100644 --- a/test/unit/ldp/operations/metadata/WebAclMetadataCollector.test.ts +++ b/test/unit/ldp/operations/metadata/WebAclMetadataCollector.test.ts @@ -2,6 +2,7 @@ import 'jest-rdf'; import { CredentialGroup } from '../../../../../src/authentication/Credentials'; import { WebAclMetadataCollector } from '../../../../../src/ldp/operations/metadata/WebAclMetadataCollector'; import type { Operation } from '../../../../../src/ldp/operations/Operation'; +import type { AclPermission } from '../../../../../src/ldp/permissions/AclPermission'; import { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata'; import { ACL, AUTH } from '../../../../../src/util/Vocabularies'; @@ -38,7 +39,7 @@ describe('A WebAclMetadataCollector', (): void => { it('adds corresponding metadata for all permissions present.', async(): Promise => { operation.permissionSet = { - [CredentialGroup.agent]: { read: true, write: true, control: false }, + [CredentialGroup.agent]: { read: true, write: true, control: false } as AclPermission, [CredentialGroup.public]: { read: true, write: false }, }; await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined(); @@ -46,4 +47,15 @@ describe('A WebAclMetadataCollector', (): void => { expect(metadata.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]); expect(metadata.get(AUTH.terms.publicMode)).toEqualRdfTerm(ACL.terms.Read); }); + + it('ignores unknown modes.', async(): Promise => { + operation.permissionSet = { + [CredentialGroup.agent]: { read: true, create: true }, + [CredentialGroup.public]: { read: true }, + }; + await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined(); + expect(metadata.quads()).toHaveLength(2); + expect(metadata.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read ]); + expect(metadata.get(AUTH.terms.publicMode)).toEqualRdfTerm(ACL.terms.Read); + }); }); diff --git a/test/unit/ldp/permissions/AclModesExtractor.test.ts b/test/unit/ldp/permissions/AclModesExtractor.test.ts deleted file mode 100644 index a65eb5236..000000000 --- a/test/unit/ldp/permissions/AclModesExtractor.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { AuxiliaryIdentifierStrategy } from '../../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; -import { AclModesExtractor } from '../../../../src/ldp/permissions/AclModesExtractor'; -import { AccessMode } from '../../../../src/ldp/permissions/Permissions'; -import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; - -describe('An AclModesExtractor', (): void => { - let extractor: AclModesExtractor; - - beforeEach(async(): Promise => { - const aclStrategy = { - isAuxiliaryIdentifier: (id): boolean => id.path.endsWith('.acl'), - } as AuxiliaryIdentifierStrategy; - extractor = new AclModesExtractor(aclStrategy); - }); - - it('can only handle acl files.', async(): Promise => { - await expect(extractor.canHandle({ target: { path: 'http://test.com/foo' }} as any)) - .rejects.toThrow(NotImplementedHttpError); - await expect(extractor.canHandle({ target: { path: 'http://test.com/foo.acl' }} as any)) - .resolves.toBeUndefined(); - }); - - it('returns control permissions.', async(): Promise => { - await expect(extractor.handle()).resolves.toEqual(new Set([ AccessMode.control ])); - }); -}); diff --git a/test/unit/ldp/permissions/MethodModesExtractor.test.ts b/test/unit/ldp/permissions/MethodModesExtractor.test.ts index 7489acadc..1c2a5bf11 100644 --- a/test/unit/ldp/permissions/MethodModesExtractor.test.ts +++ b/test/unit/ldp/permissions/MethodModesExtractor.test.ts @@ -29,11 +29,11 @@ describe('A MethodModesExtractor', (): void => { it('requires write for PUT operations.', async(): Promise => { await expect(extractor.handle({ method: 'PUT' } as Operation)) - .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); + .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write, AccessMode.create, AccessMode.delete ])); }); it('requires write for DELETE operations.', async(): Promise => { await expect(extractor.handle({ method: 'DELETE' } as Operation)) - .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); + .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write, AccessMode.create, AccessMode.delete ])); }); }); diff --git a/test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts b/test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts index 619345f8e..52c744e6c 100644 --- a/test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts +++ b/test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts @@ -58,7 +58,8 @@ describe('A SparqlPatchModesExtractor', (): void => { factory.createPattern(factory.createTerm(''), factory.createTerm('

'), factory.createTerm('')), ]) }, } as unknown as Operation; - await expect(extractor.handle(operation)).resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); + await expect(extractor.handle(operation)) + .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write, AccessMode.create, AccessMode.delete ])); }); it('requires append for composite operations with an insert.', async(): Promise => { @@ -81,6 +82,7 @@ describe('A SparqlPatchModesExtractor', (): void => { factory.createPattern(factory.createTerm(''), factory.createTerm('

'), factory.createTerm('')), ]) ]) }, } as unknown as Operation; - await expect(extractor.handle(operation)).resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); + await expect(extractor.handle(operation)) + .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write, AccessMode.create, AccessMode.delete ])); }); }); diff --git a/test/unit/storage/DataAccessorBasedStore.test.ts b/test/unit/storage/DataAccessorBasedStore.test.ts index 517ccf76b..038b70d61 100644 --- a/test/unit/storage/DataAccessorBasedStore.test.ts +++ b/test/unit/storage/DataAccessorBasedStore.test.ts @@ -91,6 +91,10 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy { this.suffix = suffix; } + public usesOwnAuthorization(): boolean { + return true; + } + public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { return { path: `${identifier.path}${this.suffix}` }; } diff --git a/test/util/AclHelper.ts b/test/util/AclHelper.ts index 557cc414b..f00d9ced2 100644 --- a/test/util/AclHelper.ts +++ b/test/util/AclHelper.ts @@ -1,5 +1,6 @@ -import type { ResourceStore, Permission } from '../../src/'; +import type { ResourceStore } from '../../src/'; import { BasicRepresentation } from '../../src/'; +import type { AclPermission } from '../../src/ldp/permissions/AclPermission'; export class AclHelper { public readonly store: ResourceStore; @@ -11,7 +12,7 @@ export class AclHelper { public async setSimpleAcl( resource: string, options: { - permissions: Partial; + permissions: AclPermission; agentClass?: 'agent' | 'authenticated'; agent?: string; accessTo?: boolean; @@ -32,7 +33,7 @@ export class AclHelper { ]; for (const perm of [ 'Read', 'Append', 'Write', 'Control' ]) { - if (options.permissions[perm.toLowerCase() as keyof Permission]) { + if (options.permissions[perm.toLowerCase() as keyof AclPermission]) { acl.push(`;\n acl:mode acl:${perm}`); } }