diff --git a/.componentsignore b/.componentsignore index f3993b3bb..f3f405232 100644 --- a/.componentsignore +++ b/.componentsignore @@ -5,6 +5,7 @@ "Error", "EventEmitter", "HttpErrorOptions", + "PermissionSet", "Template", "TemplateEngine", "ValuePreferencesArg", diff --git a/config/default.json b/config/default.json index 1e519ead6..7f23a2036 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 3dc1fc05b..d14f7cc22 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 ea2d1ce56..ab73a9ce3 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 b2cc81153..562e9e59c 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 b8190db4a..0d9b4a8a3 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 9642a64b4..e3d715862 100644 --- a/config/ldp/README.md +++ b/config/ldp/README.md @@ -27,8 +27,8 @@ Contains a list of parsers that will be run on incoming requests to generate met Contains a list of metadata writers that will be run on outgoing responses. * *default*: Contains the default writers. Can be added to when specific parsers are required. -## Permissions -Determines which permissions are needed for requests, +## 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. diff --git a/config/ldp/handler/default.json b/config/ldp/handler/default.json index aca4b94a6..b08820164 100644 --- a/config/ldp/handler/default.json +++ b/config/ldp/handler/default.json @@ -13,7 +13,7 @@ "@type": "AuthenticatedLdpHandler", "args_requestParser": { "@id": "urn:solid-server:default:RequestParser" }, "args_credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, - "args_permissionsExtractor": { "@id": "urn:solid-server:default:PermissionsExtractor" }, + "args_modesExtractor": { "@id": "urn:solid-server:default:ModesExtractor" }, "args_authorizer": { "@id": "urn:solid-server:default:Authorizer" }, "args_operationHandler": { "@id": "urn:solid-server:default:OperationHandler" }, "args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" }, diff --git a/config/ldp/permissions/acl.json b/config/ldp/modes/acl.json similarity index 64% rename from config/ldp/permissions/acl.json rename to config/ldp/modes/acl.json index c447e84ea..0c106bd81 100644 --- a/config/ldp/permissions/acl.json +++ b/config/ldp/modes/acl.json @@ -3,15 +3,15 @@ "@graph": [ { "comment": "Makes sure acl files require control permissions.", - "@id": "urn:solid-server:default:PermissionsExtractor", + "@id": "urn:solid-server:default:ModesExtractor", "@type": "WaterfallHandler", "handlers": [ { - "@type": "AclPermissionsExtractor", + "@type": "AclModesExtractor", "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" } }, - { "@type": "MethodPermissionsExtractor" }, - { "@type": "SparqlPatchPermissionsExtractor" } + { "@type": "MethodModesExtractor" }, + { "@type": "SparqlPatchModesExtractor" } ] } ] diff --git a/config/ldp/permissions/no-acl.json b/config/ldp/modes/no-acl.json similarity index 64% rename from config/ldp/permissions/no-acl.json rename to config/ldp/modes/no-acl.json index a9999029a..9a58553ee 100644 --- a/config/ldp/permissions/no-acl.json +++ b/config/ldp/modes/no-acl.json @@ -3,11 +3,11 @@ "@graph": [ { "comment": "Extracts the required permissions based on the HTTP method.", - "@id": "urn:solid-server:default:PermissionsExtractor", + "@id": "urn:solid-server:default:ModesExtractor", "@type": "WaterfallHandler", "handlers": [ - { "@type": "MethodPermissionsExtractor" }, - { "@type": "SparqlPatchPermissionsExtractor" } + { "@type": "MethodModesExtractor" }, + { "@type": "SparqlPatchModesExtractor" } ] } ] diff --git a/config/memory-subdomains.json b/config/memory-subdomains.json index a60168ccd..0f4268f02 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 8cca10147..0a2c08452 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 15ce12c11..7b7f2cd8e 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 5d82ac86b..89074ffab 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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/src/authorization/Authorizer.ts b/src/authorization/Authorizer.ts index cba83bc76..148f8cfeb 100644 --- a/src/authorization/Authorizer.ts +++ b/src/authorization/Authorizer.ts @@ -1,5 +1,5 @@ import type { CredentialSet } from '../authentication/Credentials'; -import type { PermissionSet } from '../ldp/permissions/PermissionSet'; +import type { AccessMode } from '../ldp/permissions/PermissionSet'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; import { AsyncHandler } from '../util/handlers/AsyncHandler'; import type { Authorization } from './Authorization'; @@ -14,9 +14,9 @@ export interface AuthorizerInput { */ identifier: ResourceIdentifier; /** - * Permissions that are requested on the resource. + * Modes that are requested on the resource. */ - permissions: PermissionSet; + modes: Set; } /** diff --git a/src/authorization/WebAclAuthorizer.ts b/src/authorization/WebAclAuthorizer.ts index 369c78e00..d6093004c 100644 --- a/src/authorization/WebAclAuthorizer.ts +++ b/src/authorization/WebAclAuthorizer.ts @@ -3,6 +3,7 @@ import { Store } from 'n3'; import type { Credential, CredentialSet } from '../authentication/Credentials'; import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy'; import type { PermissionSet } from '../ldp/permissions/PermissionSet'; +import { AccessMode } from '../ldp/permissions/PermissionSet'; import type { Representation } from '../ldp/representation/Representation'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; @@ -22,11 +23,11 @@ import type { AuthorizerInput } from './Authorizer'; import { Authorizer } from './Authorizer'; import { WebAclAuthorization } from './WebAclAuthorization'; -const modesMap: Record = { - [ACL.Read]: 'read', - [ACL.Write]: 'write', - [ACL.Append]: 'append', - [ACL.Control]: 'control', +const modesMap: Record = { + [ACL.Read]: AccessMode.read, + [ACL.Write]: AccessMode.write, + [ACL.Append]: AccessMode.append, + [ACL.Control]: AccessMode.control, } as const; /** @@ -61,11 +62,10 @@ export class WebAclAuthorizer extends Authorizer { * Will throw an error if this is not the case. * @param input - Relevant data needed to check if access can be granted. */ - public async handle({ identifier, permissions, credentials }: AuthorizerInput): + public async handle({ identifier, modes, credentials }: AuthorizerInput): Promise { - // Determine the required access modes - const modes = (Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]); - this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modes.join()} permissions for ${identifier.path}`); + const modeString = [ ...modes ].join(','); + this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modeString} permissions for ${identifier.path}`); // Determine the full authorization for the agent granted by the applicable ACL const acl = await this.getAclRecursive(identifier); @@ -76,7 +76,7 @@ export class WebAclAuthorizer extends Authorizer { for (const mode of modes) { this.requirePermission(agent, authorization, mode); } - this.logger.debug(`${agent.webId} has ${modes.join()} permissions for ${identifier.path}`); + this.logger.debug(`${agent.webId} has ${modeString} permissions for ${identifier.path}`); return authorization; } diff --git a/src/index.ts b/src/index.ts index 424a1bc64..c79d96797 100644 --- a/src/index.ts +++ b/src/index.ts @@ -150,11 +150,11 @@ export * from './ldp/operations/PostOperationHandler'; export * from './ldp/operations/PutOperationHandler'; // LDP/Permissions -export * from './ldp/permissions/AclPermissionsExtractor'; +export * from './ldp/permissions/AclModesExtractor'; export * from './ldp/permissions/PermissionSet'; -export * from './ldp/permissions/PermissionsExtractor'; -export * from './ldp/permissions/MethodPermissionsExtractor'; -export * from './ldp/permissions/SparqlPatchPermissionsExtractor'; +export * from './ldp/permissions/ModesExtractor'; +export * from './ldp/permissions/MethodModesExtractor'; +export * from './ldp/permissions/SparqlPatchModesExtractor'; // LDP/Representation export * from './ldp/representation/BasicRepresentation'; diff --git a/src/ldp/AuthenticatedLdpHandler.ts b/src/ldp/AuthenticatedLdpHandler.ts index 70f723882..dc39c509f 100644 --- a/src/ldp/AuthenticatedLdpHandler.ts +++ b/src/ldp/AuthenticatedLdpHandler.ts @@ -11,8 +11,7 @@ import type { ResponseDescription } from './http/response/ResponseDescription'; import type { ResponseWriter } from './http/ResponseWriter'; import type { Operation } from './operations/Operation'; import type { OperationHandler } from './operations/OperationHandler'; -import type { PermissionSet } from './permissions/PermissionSet'; -import type { PermissionsExtractor } from './permissions/PermissionsExtractor'; +import type { ModesExtractor } from './permissions/ModesExtractor'; export interface AuthenticatedLdpHandlerArgs extends BaseHttpHandlerArgs { // Workaround for https://github.com/LinkedSoftwareDependencies/Components-Generator.js/issues/73 @@ -24,9 +23,9 @@ export interface AuthenticatedLdpHandlerArgs extends BaseHttpHandlerArgs { */ credentialsExtractor: CredentialsExtractor; /** - * Extracts the required permissions from the generated Operation. + * Extracts the required modes from the generated Operation. */ - permissionsExtractor: PermissionsExtractor; + modesExtractor: ModesExtractor; /** * Verifies if the requested operation is allowed. */ @@ -42,7 +41,7 @@ export interface AuthenticatedLdpHandlerArgs extends BaseHttpHandlerArgs { */ export class AuthenticatedLdpHandler extends BaseHttpHandler { private readonly credentialsExtractor: CredentialsExtractor; - private readonly permissionsExtractor: PermissionsExtractor; + private readonly modesExtractor: ModesExtractor; private readonly authorizer: Authorizer; private readonly operationHandler: OperationHandler; @@ -53,7 +52,7 @@ export class AuthenticatedLdpHandler extends BaseHttpHandler { public constructor(args: AuthenticatedLdpHandlerArgs) { super(args); this.credentialsExtractor = args.credentialsExtractor; - this.permissionsExtractor = args.permissionsExtractor; + this.modesExtractor = args.modesExtractor; this.authorizer = args.authorizer; this.operationHandler = args.operationHandler; } @@ -81,13 +80,12 @@ export class AuthenticatedLdpHandler extends BaseHttpHandler { const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request); this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`); - const permissions: PermissionSet = await this.permissionsExtractor.handleSafe(operation); - const { read, write, append } = permissions; - this.logger.verbose(`Required permissions are read: ${read}, write: ${write}, append: ${append}`); + const modes = await this.modesExtractor.handleSafe(operation); + this.logger.verbose(`Required modes are read: ${[ ...modes ].join(',')}`); try { const authorization = await this.authorizer - .handleSafe({ credentials, identifier: operation.target, permissions }); + .handleSafe({ credentials, identifier: operation.target, modes }); operation.authorization = authorization; } catch (error: unknown) { this.logger.verbose(`Authorization failed: ${(error as any).message}`); diff --git a/src/ldp/permissions/AclModesExtractor.ts b/src/ldp/permissions/AclModesExtractor.ts new file mode 100644 index 000000000..6b4cee956 --- /dev/null +++ b/src/ldp/permissions/AclModesExtractor.ts @@ -0,0 +1,24 @@ +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 './PermissionSet'; + +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/AclPermissionsExtractor.ts b/src/ldp/permissions/AclPermissionsExtractor.ts deleted file mode 100644 index 4744fa50c..000000000 --- a/src/ldp/permissions/AclPermissionsExtractor.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; -import type { AuxiliaryIdentifierStrategy } from '../auxiliary/AuxiliaryIdentifierStrategy'; -import type { Operation } from '../operations/Operation'; -import type { PermissionSet } from './PermissionSet'; -import { PermissionsExtractor } from './PermissionsExtractor'; - -/** - * PermissionsExtractor specifically for acl resources. - * - * Solid, ยง4.3.3: "To discover, read, create, or modify an ACL auxiliary resource, an acl:agent MUST have - * acl:Control privileges per the ACL inheritance algorithm on the resource directly associated with it." - * https://solid.github.io/specification/protocol#auxiliary-resources-reserved - */ -export class AclPermissionsExtractor extends PermissionsExtractor { - 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 { - read: false, - write: false, - append: false, - control: true, - }; - } -} diff --git a/src/ldp/permissions/MethodPermissionsExtractor.ts b/src/ldp/permissions/MethodModesExtractor.ts similarity index 56% rename from src/ldp/permissions/MethodPermissionsExtractor.ts rename to src/ldp/permissions/MethodModesExtractor.ts index d4a0f5cce..c21c75bb6 100644 --- a/src/ldp/permissions/MethodPermissionsExtractor.ts +++ b/src/ldp/permissions/MethodModesExtractor.ts @@ -1,7 +1,7 @@ import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; import type { Operation } from '../operations/Operation'; -import type { PermissionSet } from './PermissionSet'; -import { PermissionsExtractor } from './PermissionsExtractor'; +import { ModesExtractor } from './ModesExtractor'; +import { AccessMode } from './PermissionSet'; const READ_METHODS = new Set([ 'GET', 'HEAD' ]); const WRITE_METHODS = new Set([ 'PUT', 'DELETE' ]); @@ -12,18 +12,24 @@ const SUPPORTED_METHODS = new Set([ ...READ_METHODS, ...WRITE_METHODS, ...APPEND * Generates permissions for the base set of methods that always require the same permissions. * Specifically: GET, HEAD, POST, PUT and DELETE. */ -export class MethodPermissionsExtractor extends PermissionsExtractor { +export class MethodModesExtractor extends ModesExtractor { public async canHandle({ method }: Operation): Promise { if (!SUPPORTED_METHODS.has(method)) { throw new NotImplementedHttpError(`Cannot determine permissions of ${method}`); } } - public async handle({ method }: Operation): Promise { - const read = READ_METHODS.has(method); - const write = WRITE_METHODS.has(method); - const append = write || APPEND_METHODS.has(method); - const control = false; - return { read, write, append, control }; + public async handle({ method }: Operation): Promise> { + const result = new Set(); + if (READ_METHODS.has(method)) { + result.add(AccessMode.read); + } + if (WRITE_METHODS.has(method)) { + result.add(AccessMode.write); + result.add(AccessMode.append); + } else if (APPEND_METHODS.has(method)) { + result.add(AccessMode.append); + } + return result; } } diff --git a/src/ldp/permissions/ModesExtractor.ts b/src/ldp/permissions/ModesExtractor.ts new file mode 100644 index 000000000..f039b9331 --- /dev/null +++ b/src/ldp/permissions/ModesExtractor.ts @@ -0,0 +1,5 @@ +import { AsyncHandler } from '../../util/handlers/AsyncHandler'; +import type { Operation } from '../operations/Operation'; +import type { AccessMode } from './PermissionSet'; + +export abstract class ModesExtractor extends AsyncHandler> {} diff --git a/src/ldp/permissions/PermissionSet.ts b/src/ldp/permissions/PermissionSet.ts index fcd8db0e2..7140f4c94 100644 --- a/src/ldp/permissions/PermissionSet.ts +++ b/src/ldp/permissions/PermissionSet.ts @@ -1,9 +1,14 @@ +/** + * Different modes that require permission. + */ +export enum AccessMode { + read = 'read', + append = 'append', + write = 'write', + control = 'control', +} + /** * A data interface indicating which permissions are required (based on the context). */ -export interface PermissionSet { - read: boolean; - append: boolean; - write: boolean; - control: boolean; -} +export type PermissionSet = Record; diff --git a/src/ldp/permissions/PermissionsExtractor.ts b/src/ldp/permissions/PermissionsExtractor.ts deleted file mode 100644 index 168ad919d..000000000 --- a/src/ldp/permissions/PermissionsExtractor.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AsyncHandler } from '../../util/handlers/AsyncHandler'; -import type { Operation } from '../operations/Operation'; -import type { PermissionSet } from './PermissionSet'; - -/** - * Verifies which permissions are requested on a given {@link Operation}. - */ -export abstract class PermissionsExtractor extends AsyncHandler {} diff --git a/src/ldp/permissions/SparqlPatchPermissionsExtractor.ts b/src/ldp/permissions/SparqlPatchModesExtractor.ts similarity index 79% rename from src/ldp/permissions/SparqlPatchPermissionsExtractor.ts rename to src/ldp/permissions/SparqlPatchModesExtractor.ts index 6a29e064f..121028928 100644 --- a/src/ldp/permissions/SparqlPatchPermissionsExtractor.ts +++ b/src/ldp/permissions/SparqlPatchModesExtractor.ts @@ -3,15 +3,10 @@ import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpErr import type { SparqlUpdatePatch } from '../http/SparqlUpdatePatch'; import type { Operation } from '../operations/Operation'; import type { Representation } from '../representation/Representation'; -import type { PermissionSet } from './PermissionSet'; -import { PermissionsExtractor } from './PermissionsExtractor'; +import { ModesExtractor } from './ModesExtractor'; +import { AccessMode } from './PermissionSet'; -/** - * Generates permissions for a SPARQL DELETE/INSERT patch. - * Updates with only an INSERT can be done with just append permissions, - * while DELETEs require write permissions as well. - */ -export class SparqlPatchPermissionsExtractor extends PermissionsExtractor { +export class SparqlPatchModesExtractor extends ModesExtractor { public async canHandle({ method, body }: Operation): Promise { if (method !== 'PATCH') { throw new NotImplementedHttpError(`Cannot determine permissions of ${method}, only PATCH.`); @@ -27,16 +22,19 @@ export class SparqlPatchPermissionsExtractor extends PermissionsExtractor { } } - public async handle({ body }: Operation): Promise { + public async handle({ body }: Operation): Promise> { // Verified in `canHandle` call const update = (body as SparqlUpdatePatch).algebra as Algebra.DeleteInsert; + const result = new Set(); // Since `append` is a specific type of write, it is true if `write` is true. - const read = false; - const write = this.needsWrite(update); - const append = write || this.needsAppend(update); - const control = false; - return { read, write, append, control }; + if (this.needsWrite(update)) { + result.add(AccessMode.write); + result.add(AccessMode.append); + } else if (this.needsAppend(update)) { + result.add(AccessMode.append); + } + return result; } private isSparql(data: Representation): data is SparqlUpdatePatch { diff --git a/test/integration/config/ldp-with-auth.json b/test/integration/config/ldp-with-auth.json index 8437d4fba..27ac73f7f 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 164ada566..dff6ab4b5 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 6157cde8a..e790ca999 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 f4f997e5e..0e11939b7 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 a98c87113..b7ecaada0 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 f69143e43..740eb20c7 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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 7b9334ef1..a5d31deec 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/permissions/acl.json", + "files-scs:config/ldp/modes/acl.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/authentication/UnionCredentialsExtractor.test.ts b/test/unit/authentication/UnionCredentialsExtractor.test.ts index 30f180c74..1b69cb886 100644 --- a/test/unit/authentication/UnionCredentialsExtractor.test.ts +++ b/test/unit/authentication/UnionCredentialsExtractor.test.ts @@ -1,12 +1,12 @@ import { CredentialGroup } from '../../../src/authentication/Credentials'; -import type { Credentials } from '../../../src/authentication/Credentials'; +import type { CredentialSet } from '../../../src/authentication/Credentials'; import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor'; import { UnionCredentialsExtractor } from '../../../src/authentication/UnionCredentialsExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; describe('A UnionCredentialsExtractor', (): void => { - const agent: Credentials = { [CredentialGroup.agent]: { webId: 'http://test.com/#me' }}; - const everyone: Credentials = { [CredentialGroup.public]: {}}; + const agent: CredentialSet = { [CredentialGroup.agent]: { webId: 'http://test.com/#me' }}; + const everyone: CredentialSet = { [CredentialGroup.public]: {}}; const request: HttpRequest = {} as any; let extractors: jest.Mocked[]; let extractor: UnionCredentialsExtractor; diff --git a/test/unit/authorization/AuxiliaryAuthorizer.test.ts b/test/unit/authorization/AuxiliaryAuthorizer.test.ts index 8b430e420..a0db82737 100644 --- a/test/unit/authorization/AuxiliaryAuthorizer.test.ts +++ b/test/unit/authorization/AuxiliaryAuthorizer.test.ts @@ -1,7 +1,7 @@ import type { Authorizer } from '../../../src/authorization/Authorizer'; import { AuxiliaryAuthorizer } from '../../../src/authorization/AuxiliaryAuthorizer'; import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; -import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet'; +import { AccessMode } from '../../../src/ldp/permissions/PermissionSet'; import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; @@ -10,18 +10,13 @@ describe('An AuxiliaryAuthorizer', (): void => { const credentials = {}; const associatedIdentifier = { path: 'http://test.com/foo' }; const auxiliaryIdentifier = { path: 'http://test.com/foo.dummy' }; - let permissions: PermissionSet; + let modes: Set; let source: Authorizer; let strategy: AuxiliaryIdentifierStrategy; let authorizer: AuxiliaryAuthorizer; beforeEach(async(): Promise => { - permissions = { - read: true, - write: true, - append: true, - control: false, - }; + modes = new Set([ AccessMode.read, AccessMode.write, AccessMode.append ]); source = { canHandle: jest.fn(), @@ -38,39 +33,39 @@ describe('An AuxiliaryAuthorizer', (): void => { }); it('can handle auxiliary resources if the source supports the associated resource.', async(): Promise => { - await expect(authorizer.canHandle({ identifier: auxiliaryIdentifier, credentials, permissions })) + await expect(authorizer.canHandle({ identifier: auxiliaryIdentifier, credentials, modes })) .resolves.toBeUndefined(); expect(source.canHandle).toHaveBeenLastCalledWith( - { identifier: associatedIdentifier, credentials, permissions }, + { identifier: associatedIdentifier, credentials, modes }, ); - await expect(authorizer.canHandle({ identifier: associatedIdentifier, credentials, permissions })) + await expect(authorizer.canHandle({ identifier: associatedIdentifier, credentials, modes })) .rejects.toThrow(NotImplementedHttpError); source.canHandle = jest.fn().mockRejectedValue(new Error('no source support')); - await expect(authorizer.canHandle({ identifier: auxiliaryIdentifier, credentials, permissions })) + await expect(authorizer.canHandle({ identifier: auxiliaryIdentifier, credentials, modes })) .rejects.toThrow('no source support'); }); it('handles resources by sending the updated parameters to the source.', async(): Promise => { - await expect(authorizer.handle({ identifier: auxiliaryIdentifier, credentials, permissions })) + await expect(authorizer.handle({ identifier: auxiliaryIdentifier, credentials, modes })) .resolves.toBeUndefined(); expect(source.handle).toHaveBeenLastCalledWith( - { identifier: associatedIdentifier, credentials, permissions }, + { identifier: associatedIdentifier, credentials, modes }, ); // Safety checks are not present when calling `handle` - await expect(authorizer.handle({ identifier: associatedIdentifier, credentials, permissions })) + await expect(authorizer.handle({ identifier: associatedIdentifier, credentials, modes })) .rejects.toThrow(NotImplementedHttpError); }); it('combines both checking and handling when calling handleSafe.', async(): Promise => { - await expect(authorizer.handleSafe({ identifier: auxiliaryIdentifier, credentials, permissions })) + await expect(authorizer.handleSafe({ identifier: auxiliaryIdentifier, credentials, modes })) .resolves.toBeUndefined(); expect(source.handleSafe).toHaveBeenLastCalledWith( - { identifier: associatedIdentifier, credentials, permissions }, + { identifier: associatedIdentifier, credentials, modes }, ); - await expect(authorizer.handleSafe({ identifier: associatedIdentifier, credentials, permissions })) + await expect(authorizer.handleSafe({ identifier: associatedIdentifier, credentials, modes })) .rejects.toThrow(NotImplementedHttpError); source.handleSafe = jest.fn().mockRejectedValue(new Error('no source support')); - await expect(authorizer.handleSafe({ identifier: auxiliaryIdentifier, credentials, permissions })) + await expect(authorizer.handleSafe({ identifier: auxiliaryIdentifier, credentials, modes })) .rejects.toThrow('no source support'); }); }); diff --git a/test/unit/authorization/PathBasedAuthorizer.test.ts b/test/unit/authorization/PathBasedAuthorizer.test.ts index d9a7b3348..c62f875e5 100644 --- a/test/unit/authorization/PathBasedAuthorizer.test.ts +++ b/test/unit/authorization/PathBasedAuthorizer.test.ts @@ -1,5 +1,6 @@ import type { Authorizer, AuthorizerInput } from '../../../src/authorization/Authorizer'; import { PathBasedAuthorizer } from '../../../src/authorization/PathBasedAuthorizer'; +import { AccessMode } from '../../../src/ldp/permissions/PermissionSet'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; describe('A PathBasedAuthorizer', (): void => { @@ -11,7 +12,7 @@ describe('A PathBasedAuthorizer', (): void => { beforeEach(async(): Promise => { input = { identifier: { path: `${baseUrl}first` }, - permissions: { read: true, append: false, write: false, control: false }, + modes: new Set([ AccessMode.read ]), credentials: {}, }; diff --git a/test/unit/authorization/WebAclAuthorizer.test.ts b/test/unit/authorization/WebAclAuthorizer.test.ts index 177ba1f88..a0bedf7ae 100644 --- a/test/unit/authorization/WebAclAuthorizer.test.ts +++ b/test/unit/authorization/WebAclAuthorizer.test.ts @@ -5,7 +5,7 @@ import type { AccessChecker } from '../../../src/authorization/access-checkers/A import { WebAclAuthorization } from '../../../src/authorization/WebAclAuthorization'; import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer'; import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; -import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet'; +import { AccessMode } from '../../../src/ldp/permissions/PermissionSet'; import type { Representation } from '../../../src/ldp/representation/Representation'; import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; import type { ResourceStore } from '../../../src/storage/ResourceStore'; @@ -31,19 +31,14 @@ describe('A WebAclAuthorizer', (): void => { } as any; let store: jest.Mocked; const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/'); - let permissions: PermissionSet; + let modes: Set; let credentials: CredentialSet; let identifier: ResourceIdentifier; let authorization: WebAclAuthorization; let accessChecker: jest.Mocked; beforeEach(async(): Promise => { - permissions = { - read: true, - append: false, - write: true, - control: false, - }; + modes = new Set([ AccessMode.read, AccessMode.write ]); credentials = { [CredentialGroup.public]: {}, [CredentialGroup.agent]: {}}; identifier = { path: 'http://test.com/foo' }; authorization = new WebAclAuthorization( @@ -73,7 +68,7 @@ describe('A WebAclAuthorizer', (): void => { }); it('handles all non-acl inputs.', async(): Promise => { - await expect(authorizer.canHandle({ identifier, credentials, permissions })).resolves.toBeUndefined(); + await expect(authorizer.canHandle({ identifier, credentials, modes })).resolves.toBeUndefined(); await expect(authorizer.canHandle({ identifier: aclStrategy.getAuxiliaryIdentifier(identifier) } as any)) .rejects.toThrow(NotImplementedHttpError); }); @@ -89,7 +84,7 @@ describe('A WebAclAuthorizer', (): void => { ]) } as Representation); Object.assign(authorization.everyone, { read: true, write: true, append: true, control: false }); Object.assign(authorization.user, { read: true, write: true, append: true, control: false }); - await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toEqual(authorization); + await expect(authorizer.handle({ identifier, modes, credentials })).resolves.toEqual(authorization); }); it('allows access if the acl file allows all agents.', async(): Promise => { @@ -102,7 +97,7 @@ describe('A WebAclAuthorizer', (): void => { ]) } as Representation); Object.assign(authorization.everyone, { read: true, write: true, append: true }); Object.assign(authorization.user, { read: true, write: true, append: true }); - await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toEqual(authorization); + await expect(authorizer.handle({ identifier, modes, credentials })).resolves.toEqual(authorization); }); it('allows access if there is a parent acl file allowing all agents.', async(): Promise => { @@ -122,7 +117,7 @@ describe('A WebAclAuthorizer', (): void => { }); Object.assign(authorization.everyone, { read: true, write: true, append: true }); Object.assign(authorization.user, { read: true, write: true, append: true }); - await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toEqual(authorization); + await expect(authorizer.handle({ identifier, modes, credentials })).resolves.toEqual(authorization); }); it('throws a ForbiddenHttpError if access is not granted and credentials have a WebID.', async(): Promise => { @@ -132,7 +127,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), ]) } as Representation); credentials.agent = { webId: 'http://test.com/alice/profile/card#me' }; - await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError); + await expect(authorizer.handle({ identifier, modes, credentials })).rejects.toThrow(ForbiddenHttpError); }); it('throws an UnauthorizedHttpError if access is not granted there are no credentials.', async(): Promise => { @@ -142,30 +137,25 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), ]) } as Representation); - await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(UnauthorizedHttpError); + await expect(authorizer.handle({ identifier, modes, credentials })).rejects.toThrow(UnauthorizedHttpError); }); it('re-throws ResourceStore errors as internal errors.', async(): Promise => { store.getRepresentation.mockRejectedValue(new Error('TEST!')); - const promise = authorizer.handle({ identifier, permissions, credentials }); + const promise = authorizer.handle({ identifier, modes, credentials }); await expect(promise).rejects.toThrow(`Error reading ACL for ${identifier.path}: TEST!`); await expect(promise).rejects.toThrow(InternalServerError); }); it('errors if the root container has no corresponding acl document.', async(): Promise => { store.getRepresentation.mockRejectedValue(new NotFoundHttpError()); - const promise = authorizer.handle({ identifier, permissions, credentials }); + const promise = authorizer.handle({ identifier, modes, credentials }); await expect(promise).rejects.toThrow('No ACL document found for root container'); await expect(promise).rejects.toThrow(ForbiddenHttpError); }); it('allows an agent to append if they have write access.', async(): Promise => { - permissions = { - read: false, - write: false, - append: true, - control: false, - }; + modes = new Set([ AccessMode.append ]); store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([ quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), @@ -173,6 +163,6 @@ describe('A WebAclAuthorizer', (): void => { ]) } as Representation); Object.assign(authorization.everyone, { write: true, append: true }); Object.assign(authorization.user, { write: true, append: true }); - await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toEqual(authorization); + await expect(authorizer.handle({ identifier, modes, credentials })).resolves.toEqual(authorization); }); }); diff --git a/test/unit/ldp/AuthenticatedLdpHandler.test.ts b/test/unit/ldp/AuthenticatedLdpHandler.test.ts index 6a4ab6db0..56fb5a14f 100644 --- a/test/unit/ldp/AuthenticatedLdpHandler.test.ts +++ b/test/unit/ldp/AuthenticatedLdpHandler.test.ts @@ -5,7 +5,7 @@ import { AuthenticatedLdpHandler } from '../../../src/ldp/AuthenticatedLdpHandle import { ResetResponseDescription } from '../../../src/ldp/http/response/ResetResponseDescription'; import type { ResponseDescription } from '../../../src/ldp/http/response/ResponseDescription'; import type { Operation } from '../../../src/ldp/operations/Operation'; -import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet'; +import { AccessMode } from '../../../src/ldp/permissions/PermissionSet'; import type { RepresentationPreferences } from '../../../src/ldp/representation/RepresentationPreferences'; import * as LogUtil from '../../../src/logging/LogUtil'; import type { HttpRequest } from '../../../src/server/HttpRequest'; @@ -17,7 +17,7 @@ describe('An AuthenticatedLdpHandler', (): void => { const preferences: RepresentationPreferences = { type: { 'text/turtle': 0.9 }}; let operation: Operation; const credentials: CredentialSet = {}; - const permissions: PermissionSet = { read: true, write: false, append: false, control: false }; + const modes: Set = new Set([ AccessMode.read ]); const authorization: Authorization = { addMetadata: jest.fn() }; const result: ResponseDescription = new ResetResponseDescription(); const errorResult: ResponseDescription = { statusCode: 500 }; @@ -32,7 +32,7 @@ describe('An AuthenticatedLdpHandler', (): void => { handleSafe: jest.fn().mockResolvedValue(operation), } as any, credentialsExtractor: { handleSafe: jest.fn().mockResolvedValue(credentials) } as any, - permissionsExtractor: { handleSafe: jest.fn().mockResolvedValue(permissions) } as any, + modesExtractor: { handleSafe: jest.fn().mockResolvedValue(modes) } as any, authorizer: { handleSafe: jest.fn().mockResolvedValue(authorization) } as any, operationHandler: { handleSafe: jest.fn().mockResolvedValue(result) } as any, errorHandler: { handleSafe: jest.fn().mockResolvedValue(errorResult) } as any, @@ -63,11 +63,11 @@ describe('An AuthenticatedLdpHandler', (): void => { expect(args.requestParser.handleSafe).toHaveBeenLastCalledWith(request); expect(args.credentialsExtractor.handleSafe).toHaveBeenCalledTimes(1); expect(args.credentialsExtractor.handleSafe).toHaveBeenLastCalledWith(request); - expect(args.permissionsExtractor.handleSafe).toHaveBeenCalledTimes(1); - expect(args.permissionsExtractor.handleSafe).toHaveBeenLastCalledWith(operation); + expect(args.modesExtractor.handleSafe).toHaveBeenCalledTimes(1); + expect(args.modesExtractor.handleSafe).toHaveBeenLastCalledWith(operation); expect(args.authorizer.handleSafe).toHaveBeenCalledTimes(1); expect(args.authorizer.handleSafe) - .toHaveBeenLastCalledWith({ credentials, identifier: { path: 'identifier' }, permissions }); + .toHaveBeenLastCalledWith({ credentials, identifier: { path: 'identifier' }, modes }); expect(operation.authorization).toBe(authorization); expect(args.operationHandler.handleSafe).toHaveBeenCalledTimes(1); expect(args.operationHandler.handleSafe).toHaveBeenLastCalledWith(operation); diff --git a/test/unit/ldp/permissions/AclPermissionsExtractor.test.ts b/test/unit/ldp/permissions/AclModesExtractor.test.ts similarity index 66% rename from test/unit/ldp/permissions/AclPermissionsExtractor.test.ts rename to test/unit/ldp/permissions/AclModesExtractor.test.ts index 3b97b6001..2ea41e95f 100644 --- a/test/unit/ldp/permissions/AclPermissionsExtractor.test.ts +++ b/test/unit/ldp/permissions/AclModesExtractor.test.ts @@ -1,15 +1,16 @@ import type { AuxiliaryIdentifierStrategy } from '../../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; -import { AclPermissionsExtractor } from '../../../../src/ldp/permissions/AclPermissionsExtractor'; +import { AclModesExtractor } from '../../../../src/ldp/permissions/AclModesExtractor'; +import { AccessMode } from '../../../../src/ldp/permissions/PermissionSet'; import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; -describe('An AclPermissionsExtractor', (): void => { - let extractor: AclPermissionsExtractor; +describe('An AclModesExtractor', (): void => { + let extractor: AclModesExtractor; beforeEach(async(): Promise => { const aclStrategy = { isAuxiliaryIdentifier: (id): boolean => id.path.endsWith('.acl'), } as AuxiliaryIdentifierStrategy; - extractor = new AclPermissionsExtractor(aclStrategy); + extractor = new AclModesExtractor(aclStrategy); }); it('can only handle acl files.', async(): Promise => { @@ -20,11 +21,6 @@ describe('An AclPermissionsExtractor', (): void => { }); it('returns control permissions.', async(): Promise => { - await expect(extractor.handle()).resolves.toEqual({ - read: false, - write: false, - append: false, - control: true, - }); + await expect(extractor.handle()).resolves.toEqual(new Set([ AccessMode.control ])); }); }); diff --git a/test/unit/ldp/permissions/MethodPermissionsExtractor.test.ts b/test/unit/ldp/permissions/MethodModesExtractor.test.ts similarity index 67% rename from test/unit/ldp/permissions/MethodPermissionsExtractor.test.ts rename to test/unit/ldp/permissions/MethodModesExtractor.test.ts index f339f9ab6..02ed93364 100644 --- a/test/unit/ldp/permissions/MethodPermissionsExtractor.test.ts +++ b/test/unit/ldp/permissions/MethodModesExtractor.test.ts @@ -1,9 +1,10 @@ import type { Operation } from '../../../../src/ldp/operations/Operation'; -import { MethodPermissionsExtractor } from '../../../../src/ldp/permissions/MethodPermissionsExtractor'; +import { MethodModesExtractor } from '../../../../src/ldp/permissions/MethodModesExtractor'; +import { AccessMode } from '../../../../src/ldp/permissions/PermissionSet'; import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; -describe('A MethodPermissionsExtractor', (): void => { - const extractor = new MethodPermissionsExtractor(); +describe('A MethodModesExtractor', (): void => { + const extractor = new MethodModesExtractor(); it('can handle HEAD/GET/POST/PUT/DELETE.', async(): Promise => { await expect(extractor.canHandle({ method: 'HEAD' } as Operation)).resolves.toBeUndefined(); @@ -15,47 +16,24 @@ describe('A MethodPermissionsExtractor', (): void => { }); it('requires read for HEAD operations.', async(): Promise => { - await expect(extractor.handle({ method: 'HEAD' } as Operation)).resolves.toEqual({ - read: true, - append: false, - write: false, - control: false, - }); + await expect(extractor.handle({ method: 'HEAD' } as Operation)).resolves.toEqual(new Set([ AccessMode.read ])); }); it('requires read for GET operations.', async(): Promise => { - await expect(extractor.handle({ method: 'GET' } as Operation)).resolves.toEqual({ - read: true, - append: false, - write: false, - control: false, - }); + await expect(extractor.handle({ method: 'GET' } as Operation)).resolves.toEqual(new Set([ AccessMode.read ])); }); it('requires append for POST operations.', async(): Promise => { - await expect(extractor.handle({ method: 'POST' } as Operation)).resolves.toEqual({ - read: false, - append: true, - write: false, - control: false, - }); + await expect(extractor.handle({ method: 'POST' } as Operation)).resolves.toEqual(new Set([ AccessMode.append ])); }); it('requires write for PUT operations.', async(): Promise => { - await expect(extractor.handle({ method: 'PUT' } as Operation)).resolves.toEqual({ - read: false, - append: true, - write: true, - control: false, - }); + await expect(extractor.handle({ method: 'PUT' } as Operation)) + .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); }); it('requires write for DELETE operations.', async(): Promise => { - await expect(extractor.handle({ method: 'DELETE' } as Operation)).resolves.toEqual({ - read: false, - append: true, - write: true, - control: false, - }); + await expect(extractor.handle({ method: 'DELETE' } as Operation)) + .resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); }); }); diff --git a/test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts b/test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts similarity index 78% rename from test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts rename to test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts index 6f9e8672d..d7373f5e5 100644 --- a/test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts +++ b/test/unit/ldp/permissions/SparqlPatchModesExtractor.test.ts @@ -1,11 +1,12 @@ import { Factory } from 'sparqlalgebrajs'; import type { SparqlUpdatePatch } from '../../../../src/ldp/http/SparqlUpdatePatch'; import type { Operation } from '../../../../src/ldp/operations/Operation'; -import { SparqlPatchPermissionsExtractor } from '../../../../src/ldp/permissions/SparqlPatchPermissionsExtractor'; +import { AccessMode } from '../../../../src/ldp/permissions/PermissionSet'; +import { SparqlPatchModesExtractor } from '../../../../src/ldp/permissions/SparqlPatchModesExtractor'; import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; -describe('A SparqlPatchPermissionsExtractor', (): void => { - const extractor = new SparqlPatchPermissionsExtractor(); +describe('A SparqlPatchModesExtractor', (): void => { + const extractor = new SparqlPatchModesExtractor(); const factory = new Factory(); it('can only handle (composite) SPARQL DELETE/INSERT PATCH operations.', async(): Promise => { @@ -37,12 +38,7 @@ describe('A SparqlPatchPermissionsExtractor', (): void => { method: 'PATCH', body: { algebra: factory.createNop() }, } as unknown as Operation; - await expect(extractor.handle(operation)).resolves.toEqual({ - read: false, - append: false, - write: false, - control: false, - }); + await expect(extractor.handle(operation)).resolves.toEqual(new Set()); }); it('requires append for INSERT operations.', async(): Promise => { @@ -52,12 +48,7 @@ describe('A SparqlPatchPermissionsExtractor', (): void => { factory.createPattern(factory.createTerm(''), factory.createTerm('

'), factory.createTerm('')), ]) }, } as unknown as Operation; - await expect(extractor.handle(operation)).resolves.toEqual({ - read: false, - append: true, - write: false, - control: false, - }); + await expect(extractor.handle(operation)).resolves.toEqual(new Set([ AccessMode.append ])); }); it('requires write for DELETE operations.', async(): Promise => { @@ -67,12 +58,7 @@ describe('A SparqlPatchPermissionsExtractor', (): void => { factory.createPattern(factory.createTerm(''), factory.createTerm('

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

'), factory.createTerm('')), ]) ]) }, } as unknown as Operation; - await expect(extractor.handle(operation)).resolves.toEqual({ - read: false, - append: true, - write: false, - control: false, - }); + await expect(extractor.handle(operation)).resolves.toEqual(new Set([ AccessMode.append ])); }); it('requires write for composite operations with a delete.', async(): Promise => { @@ -100,11 +81,6 @@ describe('A SparqlPatchPermissionsExtractor', (): void => { factory.createPattern(factory.createTerm(''), factory.createTerm('

'), factory.createTerm('')), ]) ]) }, } as unknown as Operation; - await expect(extractor.handle(operation)).resolves.toEqual({ - read: false, - append: true, - write: true, - control: false, - }); + await expect(extractor.handle(operation)).resolves.toEqual(new Set([ AccessMode.append, AccessMode.write ])); }); });