mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Let CredentialsExtractors specify what type of Credentials they generate
This commit is contained in:
parent
34a44d1636
commit
c3fa74de78
@ -4,12 +4,10 @@ import { getLoggerFor } from '../logging/LogUtil';
|
|||||||
import type { HttpRequest } from '../server/HttpRequest';
|
import type { HttpRequest } from '../server/HttpRequest';
|
||||||
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
||||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||||
import type { Credentials } from './Credentials';
|
import { CredentialGroup } from './Credentials';
|
||||||
|
import type { CredentialSet } from './Credentials';
|
||||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||||
|
|
||||||
/**
|
|
||||||
* Credentials extractor that extracts a WebID from a Bearer access token.
|
|
||||||
*/
|
|
||||||
export class BearerWebIdExtractor extends CredentialsExtractor {
|
export class BearerWebIdExtractor extends CredentialsExtractor {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
private readonly verify: SolidTokenVerifierFunction;
|
private readonly verify: SolidTokenVerifierFunction;
|
||||||
@ -26,13 +24,13 @@ export class BearerWebIdExtractor extends CredentialsExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(request: HttpRequest): Promise<Credentials> {
|
public async handle(request: HttpRequest): Promise<CredentialSet> {
|
||||||
const { headers: { authorization }} = request;
|
const { headers: { authorization }} = request;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { webid: webId } = await this.verify(authorization!);
|
const { webid: webId } = await this.verify(authorization!);
|
||||||
this.logger.info(`Verified WebID via Bearer access token: ${webId}`);
|
this.logger.info(`Verified WebID via Bearer access token: ${webId}`);
|
||||||
return { webId };
|
return { [CredentialGroup.agent]: { webId }};
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = `Error verifying WebID via Bearer access token: ${(error as Error).message}`;
|
const message = `Error verifying WebID via Bearer access token: ${(error as Error).message}`;
|
||||||
this.logger.warn(message);
|
this.logger.warn(message);
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Credentials identifying an entity accessing or owning data.
|
* Credentials identifying an entity accessing or owning data.
|
||||||
*/
|
*/
|
||||||
export interface Credentials {
|
export interface Credential {
|
||||||
webId?: string;
|
webId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific groups that can have credentials.
|
||||||
|
*/
|
||||||
|
export enum CredentialGroup {
|
||||||
|
public = 'public',
|
||||||
|
agent = 'agent',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A combination of multiple credentials, where their group is specified by the key.
|
||||||
|
*/
|
||||||
|
export type CredentialSet = Partial<Record<CredentialGroup, Credential>>;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type { HttpRequest } from '../server/HttpRequest';
|
import type { HttpRequest } from '../server/HttpRequest';
|
||||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||||
import type { Credentials } from './Credentials';
|
import type { CredentialSet } from './Credentials';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for extracting credentials from an incoming request.
|
* Responsible for extracting credentials from an incoming request.
|
||||||
*/
|
*/
|
||||||
export abstract class CredentialsExtractor extends AsyncHandler<HttpRequest, Credentials> {}
|
export abstract class CredentialsExtractor extends AsyncHandler<HttpRequest, CredentialSet> {}
|
||||||
|
@ -5,7 +5,8 @@ import { getLoggerFor } from '../logging/LogUtil';
|
|||||||
import type { HttpRequest } from '../server/HttpRequest';
|
import type { HttpRequest } from '../server/HttpRequest';
|
||||||
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
||||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||||
import type { Credentials } from './Credentials';
|
import { CredentialGroup } from './Credentials';
|
||||||
|
import type { CredentialSet } from './Credentials';
|
||||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,7 +32,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(request: HttpRequest): Promise<Credentials> {
|
public async handle(request: HttpRequest): Promise<CredentialSet> {
|
||||||
const { headers: { authorization, dpop }, method } = request;
|
const { headers: { authorization, dpop }, method } = request;
|
||||||
if (!dpop) {
|
if (!dpop) {
|
||||||
throw new BadRequestHttpError('No DPoP header specified.');
|
throw new BadRequestHttpError('No DPoP header specified.');
|
||||||
@ -53,7 +54,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
this.logger.info(`Verified WebID via DPoP-bound access token: ${webId}`);
|
this.logger.info(`Verified WebID via DPoP-bound access token: ${webId}`);
|
||||||
return { webId };
|
return { [CredentialGroup.agent]: { webId }};
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = `Error verifying WebID via DPoP-bound access token: ${(error as Error).message}`;
|
const message = `Error verifying WebID via DPoP-bound access token: ${(error as Error).message}`;
|
||||||
this.logger.warn(message);
|
this.logger.warn(message);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { HttpRequest } from '../server/HttpRequest';
|
import type { HttpRequest } from '../server/HttpRequest';
|
||||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||||
import type { Credentials } from './Credentials';
|
import { CredentialGroup } from './Credentials';
|
||||||
|
import type { CredentialSet } from './Credentials';
|
||||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,7 +15,7 @@ export class EmptyCredentialsExtractor extends CredentialsExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(): Promise<Credentials> {
|
public async handle(): Promise<CredentialSet> {
|
||||||
return {};
|
return { [CredentialGroup.public]: {}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import type { Credentials } from './Credentials';
|
import { CredentialGroup } from './Credentials';
|
||||||
|
import type { Credential, CredentialSet } from './Credentials';
|
||||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,18 +8,18 @@ import { CredentialsExtractor } from './CredentialsExtractor';
|
|||||||
* (useful for development or debugging purposes).
|
* (useful for development or debugging purposes).
|
||||||
*/
|
*/
|
||||||
export class UnsecureConstantCredentialsExtractor extends CredentialsExtractor {
|
export class UnsecureConstantCredentialsExtractor extends CredentialsExtractor {
|
||||||
private readonly agent: Credentials;
|
private readonly credentials: CredentialSet;
|
||||||
private readonly logger = getLoggerFor(this);
|
private readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
public constructor(agent: string);
|
public constructor(agent: string);
|
||||||
public constructor(agent: Credentials);
|
public constructor(agent: Credential);
|
||||||
public constructor(agent: string | Credentials) {
|
public constructor(agent: string | Credential) {
|
||||||
super();
|
super();
|
||||||
this.agent = typeof agent === 'string' ? { webId: agent } : agent;
|
this.credentials = { [CredentialGroup.agent]: typeof agent === 'string' ? { webId: agent } : agent };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(): Promise<Credentials> {
|
public async handle(): Promise<CredentialSet> {
|
||||||
this.logger.info(`Agent unsecurely claims to be ${this.agent.webId}`);
|
this.logger.info(`Agent unsecurely claims to be ${this.credentials.agent!.webId}`);
|
||||||
return this.agent;
|
return this.credentials;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import type { HttpRequest } from '../server/HttpRequest';
|
import type { HttpRequest } from '../server/HttpRequest';
|
||||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||||
import type { Credentials } from './Credentials';
|
import { CredentialGroup } from './Credentials';
|
||||||
|
import type { CredentialSet } from './Credentials';
|
||||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,9 +18,9 @@ export class UnsecureWebIdExtractor extends CredentialsExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle({ headers }: HttpRequest): Promise<Credentials> {
|
public async handle({ headers }: HttpRequest): Promise<CredentialSet> {
|
||||||
const webId = /^WebID\s+(.*)/u.exec(headers.authorization!)![1];
|
const webId = /^WebID\s+(.*)/u.exec(headers.authorization!)![1];
|
||||||
this.logger.info(`Agent unsecurely claims to be ${webId}`);
|
this.logger.info(`Agent unsecurely claims to be ${webId}`);
|
||||||
return { webId };
|
return { [CredentialGroup.agent]: { webId }};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
import type { Credentials } from '../authentication/Credentials';
|
import type { CredentialSet } from '../authentication/Credentials';
|
||||||
import type { PermissionSet } from '../ldp/permissions/PermissionSet';
|
import type { PermissionSet } from '../ldp/permissions/PermissionSet';
|
||||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||||
import type { Authorization } from './Authorization';
|
import type { Authorization } from './Authorization';
|
||||||
|
|
||||||
/**
|
export interface AuthorizerInput {
|
||||||
* Verifies if the given credentials have access to the given permissions on the given resource.
|
|
||||||
* An {@link Error} with the necessary explanation will be thrown when permissions are not granted.
|
|
||||||
*/
|
|
||||||
export abstract class Authorizer extends AsyncHandler<AuthorizerArgs, Authorization> {}
|
|
||||||
|
|
||||||
export interface AuthorizerArgs {
|
|
||||||
/**
|
/**
|
||||||
* Credentials of the entity that wants to use the resource.
|
* Credentials of the entity that wants to use the resource.
|
||||||
*/
|
*/
|
||||||
credentials: Credentials;
|
credentials: CredentialSet;
|
||||||
/**
|
/**
|
||||||
* Identifier of the resource that will be read/modified.
|
* Identifier of the resource that will be read/modified.
|
||||||
*/
|
*/
|
||||||
@ -24,3 +18,9 @@ export interface AuthorizerArgs {
|
|||||||
*/
|
*/
|
||||||
permissions: PermissionSet;
|
permissions: PermissionSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the given credentials have access to the given permissions on the given resource.
|
||||||
|
* An {@link Error} with the necessary explanation will be thrown when permissions are not granted.
|
||||||
|
*/
|
||||||
|
export abstract class Authorizer extends AsyncHandler<AuthorizerInput, Authorization> {}
|
||||||
|
@ -2,7 +2,7 @@ import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIden
|
|||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||||
import type { Authorization } from './Authorization';
|
import type { Authorization } from './Authorization';
|
||||||
import type { AuthorizerArgs } from './Authorizer';
|
import type { AuthorizerInput } from './Authorizer';
|
||||||
import { Authorizer } from './Authorizer';
|
import { Authorizer } from './Authorizer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,24 +22,24 @@ export class AuxiliaryAuthorizer extends Authorizer {
|
|||||||
this.auxiliaryStrategy = auxiliaryStrategy;
|
this.auxiliaryStrategy = auxiliaryStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle(auxiliaryAuth: AuthorizerArgs): Promise<void> {
|
public async canHandle(auxiliaryAuth: AuthorizerInput): Promise<void> {
|
||||||
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
|
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
|
||||||
return this.resourceAuthorizer.canHandle(resourceAuth);
|
return this.resourceAuthorizer.canHandle(resourceAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(auxiliaryAuth: AuthorizerArgs): Promise<Authorization> {
|
public async handle(auxiliaryAuth: AuthorizerInput): Promise<Authorization> {
|
||||||
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
|
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
|
||||||
this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} on ${resourceAuth.identifier.path}`);
|
this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} on ${resourceAuth.identifier.path}`);
|
||||||
return this.resourceAuthorizer.handle(resourceAuth);
|
return this.resourceAuthorizer.handle(resourceAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleSafe(auxiliaryAuth: AuthorizerArgs): Promise<Authorization> {
|
public async handleSafe(auxiliaryAuth: AuthorizerInput): Promise<Authorization> {
|
||||||
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
|
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
|
||||||
this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} to ${resourceAuth.identifier.path}`);
|
this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} to ${resourceAuth.identifier.path}`);
|
||||||
return this.resourceAuthorizer.handleSafe(resourceAuth);
|
return this.resourceAuthorizer.handleSafe(resourceAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRequiredAuthorization(auxiliaryAuth: AuthorizerArgs): AuthorizerArgs {
|
private getRequiredAuthorization(auxiliaryAuth: AuthorizerInput): AuthorizerInput {
|
||||||
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) {
|
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) {
|
||||||
throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.');
|
throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.');
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||||
import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil';
|
import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil';
|
||||||
import type { Authorization } from './Authorization';
|
import type { Authorization } from './Authorization';
|
||||||
import type { AuthorizerArgs } from './Authorizer';
|
import type { AuthorizerInput } from './Authorizer';
|
||||||
import { Authorizer } from './Authorizer';
|
import { Authorizer } from './Authorizer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,12 +23,12 @@ export class PathBasedAuthorizer extends Authorizer {
|
|||||||
this.paths = new Map(entries);
|
this.paths = new Map(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle(input: AuthorizerArgs): Promise<void> {
|
public async canHandle(input: AuthorizerInput): Promise<void> {
|
||||||
const authorizer = this.findAuthorizer(input.identifier.path);
|
const authorizer = this.findAuthorizer(input.identifier.path);
|
||||||
await authorizer.canHandle(input);
|
await authorizer.canHandle(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(input: AuthorizerArgs): Promise<Authorization> {
|
public async handle(input: AuthorizerInput): Promise<Authorization> {
|
||||||
const authorizer = this.findAuthorizer(input.identifier.path);
|
const authorizer = this.findAuthorizer(input.identifier.path);
|
||||||
return authorizer.handle(input);
|
return authorizer.handle(input);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Quad, Term } from 'n3';
|
import type { Quad, Term } from 'n3';
|
||||||
import { Store } from 'n3';
|
import { Store } from 'n3';
|
||||||
import type { Credentials } from '../authentication/Credentials';
|
import type { Credential, CredentialSet } from '../authentication/Credentials';
|
||||||
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import type { PermissionSet } from '../ldp/permissions/PermissionSet';
|
import type { PermissionSet } from '../ldp/permissions/PermissionSet';
|
||||||
import type { Representation } from '../ldp/representation/Representation';
|
import type { Representation } from '../ldp/representation/Representation';
|
||||||
@ -18,7 +18,7 @@ import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy'
|
|||||||
import { readableToQuads } from '../util/StreamUtil';
|
import { readableToQuads } from '../util/StreamUtil';
|
||||||
import { ACL, RDF } from '../util/Vocabularies';
|
import { ACL, RDF } from '../util/Vocabularies';
|
||||||
import type { AccessChecker } from './access-checkers/AccessChecker';
|
import type { AccessChecker } from './access-checkers/AccessChecker';
|
||||||
import type { AuthorizerArgs } from './Authorizer';
|
import type { AuthorizerInput } from './Authorizer';
|
||||||
import { Authorizer } from './Authorizer';
|
import { Authorizer } from './Authorizer';
|
||||||
import { WebAclAuthorization } from './WebAclAuthorization';
|
import { WebAclAuthorization } from './WebAclAuthorization';
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ export class WebAclAuthorizer extends Authorizer {
|
|||||||
this.accessChecker = accessChecker;
|
this.accessChecker = accessChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle({ identifier }: AuthorizerArgs): Promise<void> {
|
public async canHandle({ identifier }: AuthorizerInput): Promise<void> {
|
||||||
if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) {
|
if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||||
throw new NotImplementedHttpError('WebAclAuthorizer does not support permissions on auxiliary resources.');
|
throw new NotImplementedHttpError('WebAclAuthorizer does not support permissions on auxiliary resources.');
|
||||||
}
|
}
|
||||||
@ -61,20 +61,22 @@ export class WebAclAuthorizer extends Authorizer {
|
|||||||
* Will throw an error if this is not the case.
|
* Will throw an error if this is not the case.
|
||||||
* @param input - Relevant data needed to check if access can be granted.
|
* @param input - Relevant data needed to check if access can be granted.
|
||||||
*/
|
*/
|
||||||
public async handle({ identifier, permissions, credentials }: AuthorizerArgs): Promise<WebAclAuthorization> {
|
public async handle({ identifier, permissions, credentials }: AuthorizerInput):
|
||||||
|
Promise<WebAclAuthorization> {
|
||||||
// Determine the required access modes
|
// Determine the required access modes
|
||||||
const modes = (Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]);
|
const modes = (Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]);
|
||||||
this.logger.debug(`Checking if ${credentials.webId} has ${modes.join()} permissions for ${identifier.path}`);
|
this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modes.join()} permissions for ${identifier.path}`);
|
||||||
|
|
||||||
// Determine the full authorization for the agent granted by the applicable ACL
|
// Determine the full authorization for the agent granted by the applicable ACL
|
||||||
const acl = await this.getAclRecursive(identifier);
|
const acl = await this.getAclRecursive(identifier);
|
||||||
const authorization = await this.createAuthorization(credentials, acl);
|
const authorization = await this.createAuthorization(credentials, acl);
|
||||||
|
|
||||||
// Verify that the authorization allows all required modes
|
// Verify that the authorization allows all required modes
|
||||||
|
const agent = credentials.agent ?? credentials.public ?? {};
|
||||||
for (const mode of modes) {
|
for (const mode of modes) {
|
||||||
this.requirePermission(credentials, authorization, mode);
|
this.requirePermission(agent, authorization, mode);
|
||||||
}
|
}
|
||||||
this.logger.debug(`${credentials.webId} has ${modes.join()} permissions for ${identifier.path}`);
|
this.logger.debug(`${agent.webId} has ${modes.join()} permissions for ${identifier.path}`);
|
||||||
return authorization;
|
return authorization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,34 +84,45 @@ export class WebAclAuthorizer extends Authorizer {
|
|||||||
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
|
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
|
||||||
* @param agent - Agent whose credentials will be checked.
|
* @param agent - Agent whose credentials will be checked.
|
||||||
*/
|
*/
|
||||||
private isAuthenticated(agent: Credentials): agent is ({ webId: string }) {
|
private isAuthenticated(agent: Credential): agent is ({ webId: string }) {
|
||||||
return typeof agent.webId === 'string';
|
return typeof agent.webId === 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Authorization object based on the quads found in the ACL.
|
* Creates an Authorization object based on the quads found in the ACL.
|
||||||
* @param agent - Agent whose credentials will be used for the `user` field.
|
* @param credentials - Credentials to check permissions for.
|
||||||
* @param acl - Store containing all relevant authorization triples.
|
* @param acl - Store containing all relevant authorization triples.
|
||||||
*/
|
*/
|
||||||
private async createAuthorization(agent: Credentials, acl: Store): Promise<WebAclAuthorization> {
|
private async createAuthorization(credentials: CredentialSet, acl: Store):
|
||||||
const publicPermissions = await this.determinePermissions({}, acl);
|
Promise<WebAclAuthorization> {
|
||||||
const agentPermissions = await this.determinePermissions(agent, acl);
|
const publicPermissions = await this.determinePermissions(acl, credentials.public);
|
||||||
|
const agentPermissions = await this.determinePermissions(acl, credentials.agent);
|
||||||
|
|
||||||
|
// Agent at least has the public permissions
|
||||||
|
// This can be relevant when no agent is provided
|
||||||
|
for (const [ key, value ] of Object.entries(agentPermissions) as [keyof PermissionSet, boolean][]) {
|
||||||
|
agentPermissions[key] = value || publicPermissions[key];
|
||||||
|
}
|
||||||
|
|
||||||
return new WebAclAuthorization(agentPermissions, publicPermissions);
|
return new WebAclAuthorization(agentPermissions, publicPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the available permissions for the given credentials.
|
* Determines the available permissions for the given credentials.
|
||||||
* @param credentials - Credentials to find the permissions for.
|
* Will deny all permissions if credentials are not defined
|
||||||
* @param acl - Store containing all relevant authorization triples.
|
* @param acl - Store containing all relevant authorization triples.
|
||||||
|
* @param credentials - Credentials to find the permissions for.
|
||||||
*/
|
*/
|
||||||
private async determinePermissions(credentials: Credentials, acl: Store): Promise<PermissionSet> {
|
private async determinePermissions(acl: Store, credentials?: Credential): Promise<PermissionSet> {
|
||||||
const permissions = {
|
const permissions = {
|
||||||
read: false,
|
read: false,
|
||||||
write: false,
|
write: false,
|
||||||
append: false,
|
append: false,
|
||||||
control: false,
|
control: false,
|
||||||
};
|
};
|
||||||
|
if (!credentials) {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply all ACL rules
|
// Apply all ACL rules
|
||||||
const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null);
|
const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null);
|
||||||
@ -142,7 +155,7 @@ export class WebAclAuthorizer extends Authorizer {
|
|||||||
* @param authorization - An Authorization containing the permissions the agent has on the resource.
|
* @param authorization - An Authorization containing the permissions the agent has on the resource.
|
||||||
* @param mode - Which mode is requested.
|
* @param mode - Which mode is requested.
|
||||||
*/
|
*/
|
||||||
private requirePermission(agent: Credentials, authorization: WebAclAuthorization, mode: keyof PermissionSet): void {
|
private requirePermission(agent: Credential, authorization: WebAclAuthorization, mode: keyof PermissionSet): void {
|
||||||
if (!authorization.user[mode]) {
|
if (!authorization.user[mode]) {
|
||||||
if (this.isAuthenticated(agent)) {
|
if (this.isAuthenticated(agent)) {
|
||||||
this.logger.warn(`Agent ${agent.webId} has no ${mode} permissions`);
|
this.logger.warn(`Agent ${agent.webId} has no ${mode} permissions`);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { Store, Term } from 'n3';
|
import type { Store, Term } from 'n3';
|
||||||
import type { Credentials } from '../../authentication/Credentials';
|
import type { Credential } from '../../authentication/Credentials';
|
||||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,5 +21,5 @@ export interface AccessCheckerArgs {
|
|||||||
/**
|
/**
|
||||||
* Credentials of the entity that wants to use the resource.
|
* Credentials of the entity that wants to use the resource.
|
||||||
*/
|
*/
|
||||||
credentials: Credentials;
|
credentials: Credential;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Credentials } from '../authentication/Credentials';
|
import type { CredentialSet } from '../authentication/Credentials';
|
||||||
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
||||||
import type { Authorizer } from '../authorization/Authorizer';
|
import type { Authorizer } from '../authorization/Authorizer';
|
||||||
import { BaseHttpHandler } from '../server/BaseHttpHandler';
|
import { BaseHttpHandler } from '../server/BaseHttpHandler';
|
||||||
@ -78,8 +78,8 @@ export class AuthenticatedLdpHandler extends BaseHttpHandler {
|
|||||||
* - Executing the operation.
|
* - Executing the operation.
|
||||||
*/
|
*/
|
||||||
protected async handleOperation(operation: Operation, request: HttpRequest): Promise<ResponseDescription> {
|
protected async handleOperation(operation: Operation, request: HttpRequest): Promise<ResponseDescription> {
|
||||||
const credentials: Credentials = await this.credentialsExtractor.handleSafe(request);
|
const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request);
|
||||||
this.logger.verbose(`Extracted credentials: ${credentials.webId}`);
|
this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`);
|
||||||
|
|
||||||
const permissions: PermissionSet = await this.permissionsExtractor.handleSafe(operation);
|
const permissions: PermissionSet = await this.permissionsExtractor.handleSafe(operation);
|
||||||
const { read, write, append } = permissions;
|
const { read, write, append } = permissions;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
|
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
|
||||||
import { BearerWebIdExtractor } from '../../../src/authentication/BearerWebIdExtractor';
|
import { BearerWebIdExtractor } from '../../../src/authentication/BearerWebIdExtractor';
|
||||||
|
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||||
@ -57,7 +58,7 @@ describe('A BearerWebIdExtractor', (): void => {
|
|||||||
|
|
||||||
it('returns the extracted WebID.', async(): Promise<void> => {
|
it('returns the extracted WebID.', async(): Promise<void> => {
|
||||||
const result = webIdExtractor.handleSafe(request);
|
const result = webIdExtractor.handleSafe(request);
|
||||||
await expect(result).resolves.toEqual({ webId: 'http://alice.example/card#me' });
|
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
|
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
|
||||||
|
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||||
import { DPoPWebIdExtractor } from '../../../src/authentication/DPoPWebIdExtractor';
|
import { DPoPWebIdExtractor } from '../../../src/authentication/DPoPWebIdExtractor';
|
||||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||||
@ -85,7 +86,7 @@ describe('A DPoPWebIdExtractor', (): void => {
|
|||||||
|
|
||||||
it('returns the extracted WebID.', async(): Promise<void> => {
|
it('returns the extracted WebID.', async(): Promise<void> => {
|
||||||
const result = webIdExtractor.handleSafe(request);
|
const result = webIdExtractor.handleSafe(request);
|
||||||
await expect(result).resolves.toEqual({ webId: 'http://alice.example/card#me' });
|
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||||
import { EmptyCredentialsExtractor } from '../../../src/authentication/EmptyCredentialsExtractor';
|
import { EmptyCredentialsExtractor } from '../../../src/authentication/EmptyCredentialsExtractor';
|
||||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||||
@ -15,6 +16,6 @@ describe('An EmptyCredentialsExtractor', (): void => {
|
|||||||
it('returns the empty credentials.', async(): Promise<void> => {
|
it('returns the empty credentials.', async(): Promise<void> => {
|
||||||
const headers = {};
|
const headers = {};
|
||||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||||
await expect(result).resolves.toEqual({});
|
await expect(result).resolves.toEqual({ [CredentialGroup.public]: {}});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
|
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||||
import { UnsecureConstantCredentialsExtractor } from '../../../src/authentication/UnsecureConstantCredentialsExtractor';
|
import { UnsecureConstantCredentialsExtractor } from '../../../src/authentication/UnsecureConstantCredentialsExtractor';
|
||||||
|
|
||||||
describe('An UnsecureConstantCredentialsExtractor', (): void => {
|
describe('An UnsecureConstantCredentialsExtractor', (): void => {
|
||||||
it('extracts a constant WebID.', async(): Promise<void> => {
|
it('extracts a constant WebID.', async(): Promise<void> => {
|
||||||
const agent = 'http://alice.example/card#me';
|
const agent = 'http://alice.example/card#me';
|
||||||
const extractor = new UnsecureConstantCredentialsExtractor(agent);
|
const extractor = new UnsecureConstantCredentialsExtractor(agent);
|
||||||
await expect(extractor.handle()).resolves.toEqual({ webId: agent });
|
await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: { webId: agent }});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('extracts constant credentials.', async(): Promise<void> => {
|
it('extracts constant credentials.', async(): Promise<void> => {
|
||||||
const agent = {};
|
const agent = {};
|
||||||
const extractor = new UnsecureConstantCredentialsExtractor(agent);
|
const extractor = new UnsecureConstantCredentialsExtractor(agent);
|
||||||
await expect(extractor.handle()).resolves.toBe(agent);
|
await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: agent });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||||
import { UnsecureWebIdExtractor } from '../../../src/authentication/UnsecureWebIdExtractor';
|
import { UnsecureWebIdExtractor } from '../../../src/authentication/UnsecureWebIdExtractor';
|
||||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||||
@ -22,6 +23,6 @@ describe('An UnsecureWebIdExtractor', (): void => {
|
|||||||
it('returns the authorization header as WebID if there is one.', async(): Promise<void> => {
|
it('returns the authorization header as WebID if there is one.', async(): Promise<void> => {
|
||||||
const headers = { authorization: 'WebID http://alice.example/card#me' };
|
const headers = { authorization: 'WebID http://alice.example/card#me' };
|
||||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||||
await expect(result).resolves.toEqual({ webId: 'http://alice.example/card#me' });
|
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import type { Authorizer, AuthorizerArgs } from '../../../src/authorization/Authorizer';
|
import type { Authorizer, AuthorizerInput } from '../../../src/authorization/Authorizer';
|
||||||
import { PathBasedAuthorizer } from '../../../src/authorization/PathBasedAuthorizer';
|
import { PathBasedAuthorizer } from '../../../src/authorization/PathBasedAuthorizer';
|
||||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||||
|
|
||||||
describe('A PathBasedAuthorizer', (): void => {
|
describe('A PathBasedAuthorizer', (): void => {
|
||||||
const baseUrl = 'http://test.com/foo/';
|
const baseUrl = 'http://test.com/foo/';
|
||||||
let input: AuthorizerArgs;
|
let input: AuthorizerInput;
|
||||||
let authorizers: jest.Mocked<Authorizer>[];
|
let authorizers: jest.Mocked<Authorizer>[];
|
||||||
let authorizer: PathBasedAuthorizer;
|
let authorizer: PathBasedAuthorizer;
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ describe('A PathBasedAuthorizer', (): void => {
|
|||||||
input = {
|
input = {
|
||||||
identifier: { path: `${baseUrl}first` },
|
identifier: { path: `${baseUrl}first` },
|
||||||
permissions: { read: true, append: false, write: false, control: false },
|
permissions: { read: true, append: false, write: false, control: false },
|
||||||
credentials: { webId: 'http://alice.test.com/card#me' },
|
credentials: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
authorizers = [
|
authorizers = [
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { namedNode, quad } from '@rdfjs/data-model';
|
import { namedNode, quad } from '@rdfjs/data-model';
|
||||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||||
|
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||||
import type { AccessChecker } from '../../../src/authorization/access-checkers/AccessChecker';
|
import type { AccessChecker } from '../../../src/authorization/access-checkers/AccessChecker';
|
||||||
import { WebAclAuthorization } from '../../../src/authorization/WebAclAuthorization';
|
import { WebAclAuthorization } from '../../../src/authorization/WebAclAuthorization';
|
||||||
import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer';
|
import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer';
|
||||||
@ -31,7 +32,7 @@ describe('A WebAclAuthorizer', (): void => {
|
|||||||
let store: jest.Mocked<ResourceStore>;
|
let store: jest.Mocked<ResourceStore>;
|
||||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');
|
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');
|
||||||
let permissions: PermissionSet;
|
let permissions: PermissionSet;
|
||||||
let credentials: Credentials;
|
let credentials: CredentialSet;
|
||||||
let identifier: ResourceIdentifier;
|
let identifier: ResourceIdentifier;
|
||||||
let authorization: WebAclAuthorization;
|
let authorization: WebAclAuthorization;
|
||||||
let accessChecker: jest.Mocked<AccessChecker>;
|
let accessChecker: jest.Mocked<AccessChecker>;
|
||||||
@ -43,7 +44,7 @@ describe('A WebAclAuthorizer', (): void => {
|
|||||||
write: true,
|
write: true,
|
||||||
control: false,
|
control: false,
|
||||||
};
|
};
|
||||||
credentials = {};
|
credentials = { [CredentialGroup.public]: {}, [CredentialGroup.agent]: {}};
|
||||||
identifier = { path: 'http://test.com/foo' };
|
identifier = { path: 'http://test.com/foo' };
|
||||||
authorization = new WebAclAuthorization(
|
authorization = new WebAclAuthorization(
|
||||||
{
|
{
|
||||||
@ -72,14 +73,13 @@ describe('A WebAclAuthorizer', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles all non-acl inputs.', async(): Promise<void> => {
|
it('handles all non-acl inputs.', async(): Promise<void> => {
|
||||||
authorizer = new WebAclAuthorizer(aclStrategy, null as any, identifierStrategy, accessChecker);
|
await expect(authorizer.canHandle({ identifier, credentials, permissions })).resolves.toBeUndefined();
|
||||||
await expect(authorizer.canHandle({ identifier } as any)).resolves.toBeUndefined();
|
|
||||||
await expect(authorizer.canHandle({ identifier: aclStrategy.getAuxiliaryIdentifier(identifier) } as any))
|
await expect(authorizer.canHandle({ identifier: aclStrategy.getAuxiliaryIdentifier(identifier) } as any))
|
||||||
.rejects.toThrow(NotImplementedHttpError);
|
.rejects.toThrow(NotImplementedHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles all valid modes and ignores other ones.', async(): Promise<void> => {
|
it('handles all valid modes and ignores other ones.', async(): Promise<void> => {
|
||||||
credentials.webId = 'http://test.com/user';
|
credentials.agent = { webId: 'http://test.com/user' };
|
||||||
store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([
|
store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([
|
||||||
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
||||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||||
@ -131,11 +131,12 @@ describe('A WebAclAuthorizer', (): void => {
|
|||||||
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
||||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||||
]) } as Representation);
|
]) } as Representation);
|
||||||
credentials.webId = 'http://test.com/alice/profile/card#me';
|
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, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an UnauthorizedHttpError if access is not granted there are no credentials.', async(): Promise<void> => {
|
it('throws an UnauthorizedHttpError if access is not granted there are no credentials.', async(): Promise<void> => {
|
||||||
|
credentials = {};
|
||||||
accessChecker.handleSafe.mockResolvedValue(false);
|
accessChecker.handleSafe.mockResolvedValue(false);
|
||||||
store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([
|
store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([
|
||||||
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||||
import type { Authorization } from '../../../src/authorization/Authorization';
|
import type { Authorization } from '../../../src/authorization/Authorization';
|
||||||
import type { AuthenticatedLdpHandlerArgs } from '../../../src/ldp/AuthenticatedLdpHandler';
|
import type { AuthenticatedLdpHandlerArgs } from '../../../src/ldp/AuthenticatedLdpHandler';
|
||||||
import { AuthenticatedLdpHandler } from '../../../src/ldp/AuthenticatedLdpHandler';
|
import { AuthenticatedLdpHandler } from '../../../src/ldp/AuthenticatedLdpHandler';
|
||||||
@ -16,7 +16,7 @@ describe('An AuthenticatedLdpHandler', (): void => {
|
|||||||
const response: HttpResponse = {} as any;
|
const response: HttpResponse = {} as any;
|
||||||
const preferences: RepresentationPreferences = { type: { 'text/turtle': 0.9 }};
|
const preferences: RepresentationPreferences = { type: { 'text/turtle': 0.9 }};
|
||||||
let operation: Operation;
|
let operation: Operation;
|
||||||
const credentials: Credentials = {};
|
const credentials: CredentialSet = {};
|
||||||
const permissions: PermissionSet = { read: true, write: false, append: false, control: false };
|
const permissions: PermissionSet = { read: true, write: false, append: false, control: false };
|
||||||
const authorization: Authorization = { addMetadata: jest.fn() };
|
const authorization: Authorization = { addMetadata: jest.fn() };
|
||||||
const result: ResponseDescription = new ResetResponseDescription();
|
const result: ResponseDescription = new ResetResponseDescription();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user