docs: Add comments referencing the Solid spec where relevant

Also some minor changes to completely match the spec there.
This commit is contained in:
Joachim Van Herwegen
2021-01-14 10:01:56 +01:00
parent 50e3cf5036
commit ee50f40062
9 changed files with 39 additions and 5 deletions

View File

@@ -2,6 +2,10 @@ import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifie
/**
* Handles where acl resources are stored.
*
* Solid, §4.3.3: "A given Solid resource MUST NOT be directly associated with more than one ACL auxiliary resource.
* A given ACL auxiliary resource MUST NOT be directly associated with more than one Solid resource."
* https://solid.github.io/specification/protocol#auxiliary-resources-reserved
*/
export interface AclManager {
/**

View File

@@ -8,7 +8,6 @@ import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore';
import { INTERNAL_QUADS } from '../util/ContentTypes';
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
import { InternalServerError } from '../util/errors/InternalServerError';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { UnauthorizedHttpError } from '../util/errors/UnauthorizedHttpError';
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
@@ -44,6 +43,9 @@ export class WebAclAuthorizer extends Authorizer {
public async handle(input: AuthorizerArgs): Promise<void> {
const store = await this.getAclRecursive(input.identifier);
if (await this.aclManager.isAclDocument(input.identifier)) {
// 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
this.checkPermission(input.credentials, store, 'control');
} else {
(Object.keys(input.permissions) as (keyof PermissionSet)[]).forEach((key): void => {
@@ -77,6 +79,9 @@ export class WebAclAuthorizer extends Authorizer {
this.logger.warn(`Agent ${agent.webId} has no ${mode} permissions`);
throw new ForbiddenHttpError();
} else {
// Solid, §2.1: "When a client does not provide valid credentials when requesting a resource that requires it,
// the data pod MUST send a response with a 401 status code (unless 404 is preferred for security reasons)."
// https://solid.github.io/specification/protocol#http-server
this.logger.warn(`Unauthenticated agent has no ${mode} permissions`);
throw new UnauthorizedHttpError();
}
@@ -154,7 +159,9 @@ export class WebAclAuthorizer extends Authorizer {
this.logger.debug(`Traversing to the parent of ${id.path}`);
if (this.identifierStrategy.isRootContainer(id)) {
this.logger.error(`No ACL document found for root container ${id.path}`);
throw new InternalServerError('No ACL document found for root container');
// Solid, §10.1: "In the event that a server cant apply an ACL to a resource, it MUST deny access."
// https://solid.github.io/specification/protocol#web-access-control
throw new ForbiddenHttpError('No ACL document found for root container');
}
const parent = this.identifierStrategy.getParentContainer(id);
return this.getAclRecursive(parent, true);

View File

@@ -51,6 +51,10 @@ export class AclInitializer extends Initializer {
// Set up ACL so everything can still be done by default
// Note that this will need to be adapted to go through all the correct channels later on
//
// Solid, §4.1: "The root container (pim:Storage) MUST have an ACL auxiliary resource directly associated to it.
// The associated ACL document MUST include an authorization policy with acl:Control access privilege."
// https://solid.github.io/specification/protocol#storage
protected async setRootAclDocument(rootAcl: ResourceIdentifier): Promise<void> {
const acl = `@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.

View File

@@ -14,6 +14,10 @@ import namedNode = DataFactory.namedNode;
/**
* Initializes ResourceStores by creating a root container if it didn't exist yet.
*
* Solid, §4.1: "When a server supports a data pod, it MUST provide one or more storages (pim:Storage)
* a space of URIs in which data can be accessed. A storage is the root container for all of its contained resources."
* https://solid.github.io/specification/protocol#storage
*/
export class RootContainerInitializer extends Initializer {
protected readonly logger = getLoggerFor(this);
@@ -59,6 +63,9 @@ export class RootContainerInitializer extends Initializer {
// Make sure the root container is a pim:Storage
// This prevents deletion of the root container as storage root containers can not be deleted
// Solid, §4.1: "Servers exposing the storage resource MUST advertise by including the HTTP Link header
// with rel="type" targeting http://www.w3.org/ns/pim/space#Storage when responding to storages request URI."
// https://solid.github.io/specification/protocol#storage
metadata.add(RDF.type, PIM.terms.Storage);
this.logger.debug(`Creating root container at ${this.baseId.path}`);

View File

@@ -22,6 +22,11 @@ interface SimpleCorsOptions {
/**
* Handler that sets CORS options on the response.
*
* Solid, §7.1: "A data pod MUST implement the CORS protocol [FETCH] such that, to the extent possible,
* the browser allows Solid apps to send any request and combination of request headers to the data pod,
* and the Solid app can read any response and response headers received from the data pod."
* Full details: https://solid.github.io/specification/protocol#cors-server
*/
export class CorsHandler extends HttpHandler {
private readonly corsHandler: RequestHandler;

View File

@@ -57,6 +57,11 @@ export interface DataAccessor {
/**
* Deletes the resource and its corresponding metadata.
*
* Solid, §5.4: "When a contained resource is deleted, the server MUST also remove the corresponding containment
* triple, which has the effect of removing the deleted resource from the containing container."
* https://solid.github.io/specification/protocol#deleting-resources
*
* @param identifier - Resource to delete.
*/
deleteResource: (identifier: ResourceIdentifier) => Promise<void>;

View File

@@ -92,7 +92,8 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
});
this.logger.debug(`${store.size} quads in ${identifier.path}.`);
} catch (error: unknown) {
// In case the resource does not exist yet we want to create it
// Solid, §5.1: "Clients who want to assign a URI to a resource, MUST use PUT and PATCH requests."
// https://solid.github.io/specification/protocol#resource-type-heuristics
if (!(error instanceof NotFoundHttpError)) {
throw error;
}

View File

@@ -88,6 +88,8 @@ export function encodeUriPathComponents(path: string): string {
* @param path - Path to check.
*/
export function isContainerPath(path: string): boolean {
// Solid, §3.1: "Paths ending with a slash denote a container resource."
// https://solid.github.io/specification/protocol#uri-slash-semantics
return path.endsWith('/');
}

View File

@@ -8,7 +8,6 @@ import type { Representation } from '../../../src/ldp/representation/Representat
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
import type { ResourceStore } from '../../../src/storage/ResourceStore';
import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError';
import { InternalServerError } from '../../../src/util/errors/InternalServerError';
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
import { UnauthorizedHttpError } from '../../../src/util/errors/UnauthorizedHttpError';
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
@@ -153,7 +152,7 @@ describe('A WebAclAuthorizer', (): void => {
};
const promise = authorizer.handle({ identifier, permissions, credentials });
await expect(promise).rejects.toThrow('No ACL document found for root container');
await expect(promise).rejects.toThrow(InternalServerError);
await expect(promise).rejects.toThrow(ForbiddenHttpError);
});
it('allows an agent to append if they have write access.', async(): Promise<void> => {