feat: Let CredentialsExtractors specify what type of Credentials they generate

This commit is contained in:
Joachim Van Herwegen
2021-09-17 11:17:43 +02:00
parent 34a44d1636
commit c3fa74de78
21 changed files with 115 additions and 81 deletions

View File

@@ -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 { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { AsyncHandler } from '../util/handlers/AsyncHandler';
import type { Authorization } from './Authorization';
/**
* 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 {
export interface AuthorizerInput {
/**
* Credentials of the entity that wants to use the resource.
*/
credentials: Credentials;
credentials: CredentialSet;
/**
* Identifier of the resource that will be read/modified.
*/
@@ -24,3 +18,9 @@ export interface AuthorizerArgs {
*/
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> {}

View File

@@ -2,7 +2,7 @@ import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIden
import { getLoggerFor } from '../logging/LogUtil';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
import type { Authorization } from './Authorization';
import type { AuthorizerArgs } from './Authorizer';
import type { AuthorizerInput } from './Authorizer';
import { Authorizer } from './Authorizer';
/**
@@ -22,24 +22,24 @@ export class AuxiliaryAuthorizer extends Authorizer {
this.auxiliaryStrategy = auxiliaryStrategy;
}
public async canHandle(auxiliaryAuth: AuthorizerArgs): Promise<void> {
public async canHandle(auxiliaryAuth: AuthorizerInput): Promise<void> {
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
return this.resourceAuthorizer.canHandle(resourceAuth);
}
public async handle(auxiliaryAuth: AuthorizerArgs): Promise<Authorization> {
public async handle(auxiliaryAuth: AuthorizerInput): Promise<Authorization> {
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} on ${resourceAuth.identifier.path}`);
return this.resourceAuthorizer.handle(resourceAuth);
}
public async handleSafe(auxiliaryAuth: AuthorizerArgs): Promise<Authorization> {
public async handleSafe(auxiliaryAuth: AuthorizerInput): Promise<Authorization> {
const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth);
this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} to ${resourceAuth.identifier.path}`);
return this.resourceAuthorizer.handleSafe(resourceAuth);
}
private getRequiredAuthorization(auxiliaryAuth: AuthorizerArgs): AuthorizerArgs {
private getRequiredAuthorization(auxiliaryAuth: AuthorizerInput): AuthorizerInput {
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) {
throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.');
}

View File

@@ -1,7 +1,7 @@
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil';
import type { Authorization } from './Authorization';
import type { AuthorizerArgs } from './Authorizer';
import type { AuthorizerInput } from './Authorizer';
import { Authorizer } from './Authorizer';
/**
@@ -23,12 +23,12 @@ export class PathBasedAuthorizer extends Authorizer {
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);
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);
return authorizer.handle(input);
}

View File

@@ -1,6 +1,6 @@
import type { Quad, Term } 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 { PermissionSet } from '../ldp/permissions/PermissionSet';
import type { Representation } from '../ldp/representation/Representation';
@@ -18,7 +18,7 @@ import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy'
import { readableToQuads } from '../util/StreamUtil';
import { ACL, RDF } from '../util/Vocabularies';
import type { AccessChecker } from './access-checkers/AccessChecker';
import type { AuthorizerArgs } from './Authorizer';
import type { AuthorizerInput } from './Authorizer';
import { Authorizer } from './Authorizer';
import { WebAclAuthorization } from './WebAclAuthorization';
@@ -50,7 +50,7 @@ export class WebAclAuthorizer extends Authorizer {
this.accessChecker = accessChecker;
}
public async canHandle({ identifier }: AuthorizerArgs): Promise<void> {
public async canHandle({ identifier }: AuthorizerInput): Promise<void> {
if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) {
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.
* @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
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
const acl = await this.getAclRecursive(identifier);
const authorization = await this.createAuthorization(credentials, acl);
// Verify that the authorization allows all required modes
const agent = credentials.agent ?? credentials.public ?? {};
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;
}
@@ -82,34 +84,45 @@ export class WebAclAuthorizer extends Authorizer {
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
* @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';
}
/**
* 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.
*/
private async createAuthorization(agent: Credentials, acl: Store): Promise<WebAclAuthorization> {
const publicPermissions = await this.determinePermissions({}, acl);
const agentPermissions = await this.determinePermissions(agent, acl);
private async createAuthorization(credentials: CredentialSet, acl: Store):
Promise<WebAclAuthorization> {
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);
}
/**
* 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 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 = {
read: false,
write: false,
append: false,
control: false,
};
if (!credentials) {
return permissions;
}
// Apply all ACL rules
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 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 (this.isAuthenticated(agent)) {
this.logger.warn(`Agent ${agent.webId} has no ${mode} permissions`);

View File

@@ -1,5 +1,5 @@
import type { Store, Term } from 'n3';
import type { Credentials } from '../../authentication/Credentials';
import type { Credential } from '../../authentication/Credentials';
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
/**
@@ -21,5 +21,5 @@ export interface AccessCheckerArgs {
/**
* Credentials of the entity that wants to use the resource.
*/
credentials: Credentials;
credentials: Credential;
}