mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
docs: Add comments referencing the Solid spec where relevant
Also some minor changes to completely match the spec there.
This commit is contained in:
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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 can’t 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);
|
||||
|
||||
@@ -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/>.
|
||||
|
||||
@@ -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 storage’s 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}`);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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('/');
|
||||
}
|
||||
|
||||
|
||||
@@ -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> => {
|
||||
|
||||
Reference in New Issue
Block a user