Skip to content

Commit

Permalink
feat: Create OperationMetadataCollector to handle operation metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimvh committed Sep 28, 2021
1 parent bf28c83 commit 5104cd5
Show file tree
Hide file tree
Showing 23 changed files with 228 additions and 222 deletions.
9 changes: 3 additions & 6 deletions config/ldp/authorization/allow-all.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
"DO NOT USE IN PRODUCTION. ONLY FOR DEVELOPMENT, TESTING, OR DEBUGGING.",
"Always allows all operations."
],
"@id": "urn:solid-server:default:Authorizer",
"@type": "PermissionBasedAuthorizer",
"reader": {
"@type": "AllStaticReader",
"allow": true
}
"@id": "urn:solid-server:default:PermissionReader",
"@type": "AllStaticReader",
"allow": true
}
]
}
37 changes: 17 additions & 20 deletions config/ldp/authorization/webacl.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,23 @@
"@graph": [
{
"comment": "Uses Web Access Control for authorization.",
"@id": "urn:solid-server:default:Authorizer",
"@type": "PermissionBasedAuthorizer",
"reader": {
"@type": "UnionPermissionReader",
"readers": [
{
"comment": "This PermissionReader will be used to prevent external access to containers used for internal storage.",
"@id": "urn:solid-server:default:PathBasedReader",
"@type": "PathBasedReader",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
},
{
"comment": "This PermissionReader makes sure that for auxiliary resources, the main reader gets called with the associated identifier.",
"@type": "AuxiliaryReader",
"resourceReader": { "@id": "urn:solid-server:default:WebAclReader" },
"auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" }
},
{ "@id": "urn:solid-server:default:WebAclReader" }
]
}
"@id": "urn:solid-server:default:PermissionReader",
"@type": "UnionPermissionReader",
"readers": [
{
"comment": "This PermissionReader will be used to prevent external access to containers used for internal storage.",
"@id": "urn:solid-server:default:PathBasedReader",
"@type": "PathBasedReader",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
},
{
"comment": "This PermissionReader makes sure that for auxiliary resources, the main reader gets called with the associated identifier.",
"@type": "AuxiliaryReader",
"resourceReader": { "@id": "urn:solid-server:default:WebAclReader" },
"auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" }
},
{ "@id": "urn:solid-server:default:WebAclReader" }
]
}
]
}
10 changes: 10 additions & 0 deletions config/ldp/handler/components/authorizer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Matches requested permissions with those available.",
"@id": "urn:solid-server:default:Authorizer",
"@type": "PermissionBasedAuthorizer"
}
]
}
4 changes: 2 additions & 2 deletions config/ldp/handler/components/operation-handler.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"handlers": [
{
"@type": "GetOperationHandler",
"store": { "@id": "urn:solid-server:default:ResourceStore" }
"store": { "@id": "urn:solid-server:default:ResourceStore" },
},
{
"@type": "PostOperationHandler",
Expand All @@ -23,7 +23,7 @@
},
{
"@type": "HeadOperationHandler",
"store": { "@id": "urn:solid-server:default:ResourceStore" }
"store": { "@id": "urn:solid-server:default:ResourceStore" },
},
{
"@type": "PatchOperationHandler",
Expand Down
9 changes: 9 additions & 0 deletions config/ldp/handler/components/operation-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
"@graph": [
{
"@id": "urn:solid-server:default:OperationMetadataCollector",
"@type": "WebAclMetadataCollector"
}
]
}
6 changes: 5 additions & 1 deletion config/ldp/handler/default.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
"import": [
"files-scs:config/ldp/handler/components/authorizer.json",
"files-scs:config/ldp/handler/components/error-handler.json",
"files-scs:config/ldp/handler/components/operation-handler.json",
"files-scs:config/ldp/handler/components/operation-metadata.json",
"files-scs:config/ldp/handler/components/request-parser.json",
"files-scs:config/ldp/handler/components/response-writer.json"
],
Expand All @@ -14,10 +16,12 @@
"args_requestParser": { "@id": "urn:solid-server:default:RequestParser" },
"args_credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
"args_modesExtractor": { "@id": "urn:solid-server:default:ModesExtractor" },
"args_permissionReader": { "@id": "urn:solid-server:default:PermissionReader" },
"args_authorizer": { "@id": "urn:solid-server:default:Authorizer" },
"args_operationHandler": { "@id": "urn:solid-server:default:OperationHandler" },
"args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" },
"args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" }
"args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" },
"args_operationMetadataCollector": { "@id": "urn:solid-server:default:OperationMetadataCollector" }
}
]
}
12 changes: 0 additions & 12 deletions src/authorization/Authorization.ts

This file was deleted.

9 changes: 6 additions & 3 deletions src/authorization/Authorizer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { CredentialSet } from '../authentication/Credentials';
import type { AccessMode } from '../ldp/permissions/Permissions';
import type { AccessMode, PermissionSet } from '../ldp/permissions/Permissions';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { AsyncHandler } from '../util/handlers/AsyncHandler';
import type { Authorization } from './Authorization';

export interface AuthorizerInput {
/**
Expand All @@ -17,10 +16,14 @@ export interface AuthorizerInput {
* Modes that are requested on the resource.
*/
modes: Set<AccessMode>;
/**
* Permissions that are available for the request.
*/
permissionSet: PermissionSet;
}

/**
* Verifies if the credentials provide access with the given permissions on the resource.
* An {@link Error} with the necessary explanation will be thrown when permissions are not granted.
*/
export abstract class Authorizer extends AsyncHandler<AuthorizerInput, Authorization> {}
export abstract class Authorizer extends AsyncHandler<AuthorizerInput> {}
26 changes: 3 additions & 23 deletions src/authorization/PermissionBasedAuthorizer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import type { CredentialSet } from '../authentication/Credentials';
import type { AccessMode, PermissionSet } from '../ldp/permissions/Permissions';

import { getLoggerFor } from '../logging/LogUtil';
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
import { UnauthorizedHttpError } from '../util/errors/UnauthorizedHttpError';
import type { Authorization } from './Authorization';
import type { AuthorizerInput } from './Authorizer';
import { Authorizer } from './Authorizer';
import type { PermissionReader } from './PermissionReader';
import { WebAclAuthorization } from './WebAclAuthorization';

/**
* Authorizer that bases its decision on the output it gets from its PermissionReader.
Expand All @@ -19,32 +15,16 @@ import { WebAclAuthorization } from './WebAclAuthorization';
export class PermissionBasedAuthorizer extends Authorizer {
protected readonly logger = getLoggerFor(this);

private readonly reader: PermissionReader;

public constructor(reader: PermissionReader) {
super();
this.reader = reader;
}

public async canHandle(input: AuthorizerInput): Promise<void> {
return this.reader.canHandle(input);
}

public async handle(input: AuthorizerInput): Promise<Authorization> {
const { credentials, modes, identifier } = input;

// Read out the permissions
const permissions = await this.reader.handle(input);
const authorization = new WebAclAuthorization(permissions.agent ?? {}, permissions.public ?? {});
public async handle(input: AuthorizerInput): Promise<void> {
const { credentials, modes, identifier, permissionSet } = input;

const modeString = [ ...modes ].join(',');
this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modeString} permissions for ${identifier.path}`);

for (const mode of modes) {
this.requireModePermission(credentials, permissions, mode);
this.requireModePermission(credentials, permissionSet, mode);
}
this.logger.debug(`${JSON.stringify(credentials)} has ${modeString} permissions for ${identifier.path}`);
return authorization;
}

/**
Expand Down
36 changes: 0 additions & 36 deletions src/authorization/WebAclAuthorization.ts

This file was deleted.

18 changes: 10 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,22 @@ export * from './authentication/UnionCredentialsExtractor';
export * from './authentication/UnsecureConstantCredentialsExtractor';
export * from './authentication/UnsecureWebIdExtractor';

// Authorization/Access-Checkers
export * from './authorization/access-checkers/AccessChecker';
export * from './authorization/access-checkers/AgentAccessChecker';
export * from './authorization/access-checkers/AgentClassAccessChecker';
export * from './authorization/access-checkers/AgentGroupAccessChecker';

// Authorization
export * from './authorization/AllStaticReader';
export * from './authorization/Authorization';
export * from './authorization/Authorizer';
export * from './authorization/AuxiliaryReader';
export * from './authorization/PathBasedReader';
export * from './authorization/PermissionBasedAuthorizer';
export * from './authorization/PermissionReader';
export * from './authorization/UnionPermissionReader';
export * from './authorization/WebAclAuthorization';
export * from './authorization/WebAclReader';

// Authorization/access-checkers
export * from './authorization/access-checkers/AccessChecker';
export * from './authorization/access-checkers/AgentAccessChecker';
export * from './authorization/access-checkers/AgentClassAccessChecker';
export * from './authorization/access-checkers/AgentGroupAccessChecker';

// Identity/Configuration
export * from './identity/configuration/IdentityProviderFactory';
export * from './identity/configuration/ProviderFactory';
Expand Down Expand Up @@ -141,6 +139,10 @@ export * from './ldp/http/SparqlUpdateBodyParser';
export * from './ldp/http/SparqlUpdatePatch';
export * from './ldp/http/TargetExtractor';

// LDP/Operations/Metadata
export * from './ldp/operations/metadata/OperationMetadataCollector';
export * from './ldp/operations/metadata/WebAclMetadataCollector';

// LDP/Operations
export * from './ldp/operations/DeleteOperationHandler';
export * from './ldp/operations/GetOperationHandler';
Expand Down
30 changes: 26 additions & 4 deletions src/ldp/AuthenticatedLdpHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CredentialSet } from '../authentication/Credentials';
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
import type { Authorizer } from '../authorization/Authorizer';
import type { PermissionReader } from '../authorization/PermissionReader';
import { BaseHttpHandler } from '../server/BaseHttpHandler';
import type { BaseHttpHandlerArgs } from '../server/BaseHttpHandler';
import type { HttpHandlerInput } from '../server/HttpHandler';
Expand All @@ -9,6 +10,7 @@ import type { ErrorHandler } from './http/ErrorHandler';
import type { RequestParser } from './http/RequestParser';
import type { ResponseDescription } from './http/response/ResponseDescription';
import type { ResponseWriter } from './http/ResponseWriter';
import type { OperationMetadataCollector } from './operations/metadata/OperationMetadataCollector';
import type { Operation } from './operations/Operation';
import type { OperationHandler } from './operations/OperationHandler';
import type { ModesExtractor } from './permissions/ModesExtractor';
Expand All @@ -26,6 +28,10 @@ export interface AuthenticatedLdpHandlerArgs extends BaseHttpHandlerArgs {
* Extracts the required modes from the generated Operation.
*/
modesExtractor: ModesExtractor;
/**
* Reads the permissions available for the Operation.
*/
permissionReader: PermissionReader;
/**
* Verifies if the requested operation is allowed.
*/
Expand All @@ -34,6 +40,10 @@ export interface AuthenticatedLdpHandlerArgs extends BaseHttpHandlerArgs {
* Executed the operation.
*/
operationHandler: OperationHandler;
/**
* Generates generic operation metadata that is required for a response.
*/
operationMetadataCollector: OperationMetadataCollector;
}

/**
Expand All @@ -42,8 +52,10 @@ export interface AuthenticatedLdpHandlerArgs extends BaseHttpHandlerArgs {
export class AuthenticatedLdpHandler extends BaseHttpHandler {
private readonly credentialsExtractor: CredentialsExtractor;
private readonly modesExtractor: ModesExtractor;
private readonly permissionReader: PermissionReader;
private readonly authorizer: Authorizer;
private readonly operationHandler: OperationHandler;
private readonly operationMetadataCollector: OperationMetadataCollector;

/**
* Creates the handler.
Expand All @@ -53,8 +65,10 @@ export class AuthenticatedLdpHandler extends BaseHttpHandler {
super(args);
this.credentialsExtractor = args.credentialsExtractor;
this.modesExtractor = args.modesExtractor;
this.permissionReader = args.permissionReader;
this.authorizer = args.authorizer;
this.operationHandler = args.operationHandler;
this.operationMetadataCollector = args.operationMetadataCollector;
}

/**
Expand Down Expand Up @@ -83,16 +97,24 @@ export class AuthenticatedLdpHandler extends BaseHttpHandler {
const modes = await this.modesExtractor.handleSafe(operation);
this.logger.verbose(`Required modes are read: ${[ ...modes ].join(',')}`);

const permissionSet = await this.permissionReader.handleSafe({ credentials, identifier: operation.target });
this.logger.verbose(`Available permissions are ${JSON.stringify(permissionSet)}`);

try {
const authorization = await this.authorizer
.handleSafe({ credentials, identifier: operation.target, modes });
operation.authorization = authorization;
await this.authorizer.handleSafe({ credentials, identifier: operation.target, modes, permissionSet });
operation.permissionSet = permissionSet;
} catch (error: unknown) {
this.logger.verbose(`Authorization failed: ${(error as any).message}`);
throw error;
}

this.logger.verbose(`Authorization succeeded, performing operation`);
return this.operationHandler.handleSafe(operation);
const response = await this.operationHandler.handleSafe(operation);

if (response.metadata) {
await this.operationMetadataCollector.handleSafe({ operation, metadata: response.metadata });
}

return response;
}
}
2 changes: 0 additions & 2 deletions src/ldp/operations/GetOperationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ export class GetOperationHandler extends OperationHandler {
public async handle(input: Operation): Promise<ResponseDescription> {
const body = await this.store.getRepresentation(input.target, input.preferences, input.conditions);

input.authorization?.addMetadata(body.metadata);

return new OkResponseDescription(body.metadata, body.data);
}
}
2 changes: 0 additions & 2 deletions src/ldp/operations/HeadOperationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ export class HeadOperationHandler extends OperationHandler {
// Close the Readable as we will not return it.
body.data.destroy();

input.authorization?.addMetadata(body.metadata);

return new OkResponseDescription(body.metadata);
}
}
Loading

0 comments on commit 5104cd5

Please sign in to comment.