mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create RootInitializer to set up root resources
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
import { createReadStream } from 'fs';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { ResourceStore } from '../storage/ResourceStore';
|
||||
import { TEXT_TURTLE } from '../util/ContentTypes';
|
||||
import { createErrorMessage } from '../util/errors/ErrorUtil';
|
||||
import { InternalServerError } from '../util/errors/InternalServerError';
|
||||
import { ensureTrailingSlash, joinFilePath } from '../util/PathUtil';
|
||||
import { Initializer } from './Initializer';
|
||||
|
||||
const DEFAULT_ACL_PATH = joinFilePath(__dirname, '../../templates/root/.acl');
|
||||
|
||||
/**
|
||||
* Ensures that a root ACL is present.
|
||||
*/
|
||||
export class AclInitializer extends Initializer {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
private readonly store: ResourceStore;
|
||||
private readonly aclStrategy: AuxiliaryIdentifierStrategy;
|
||||
private readonly root: ResourceIdentifier;
|
||||
private readonly aclPath: string;
|
||||
|
||||
public constructor(settings: {
|
||||
store: ResourceStore;
|
||||
aclStrategy: AuxiliaryIdentifierStrategy;
|
||||
baseUrl: string;
|
||||
aclPath?: string;
|
||||
}) {
|
||||
super();
|
||||
this.store = settings.store;
|
||||
this.aclStrategy = settings.aclStrategy;
|
||||
this.root = { path: ensureTrailingSlash(settings.baseUrl) };
|
||||
this.aclPath = settings.aclPath ?? DEFAULT_ACL_PATH;
|
||||
}
|
||||
|
||||
// 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
|
||||
public async handle(): Promise<void> {
|
||||
const rootAcl = this.aclStrategy.getAuxiliaryIdentifier(this.root);
|
||||
if (await this.store.resourceExists(rootAcl)) {
|
||||
this.logger.debug(`Existing root ACL document found at ${rootAcl.path}`);
|
||||
} else {
|
||||
this.logger.debug(`Installing root ACL document at ${rootAcl.path}`);
|
||||
const aclDocument = createReadStream(this.aclPath, 'utf8');
|
||||
try {
|
||||
await this.store.setRepresentation(rootAcl, new BasicRepresentation(aclDocument, rootAcl, TEXT_TURTLE));
|
||||
} catch (error: unknown) {
|
||||
const message = `Issue initializing the root ACL resource: ${createErrorMessage(error)}`;
|
||||
this.logger.error(message);
|
||||
throw new InternalServerError(message, { cause: error });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
||||
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { ResourceStore } from '../storage/ResourceStore';
|
||||
import { TEXT_TURTLE } from '../util/ContentTypes';
|
||||
import { ensureTrailingSlash } from '../util/PathUtil';
|
||||
import { addResourceMetadata } from '../util/ResourceUtil';
|
||||
import { PIM, RDF } from '../util/Vocabularies';
|
||||
import { Initializer } from './Initializer';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
private readonly store: ResourceStore;
|
||||
private readonly baseId: ResourceIdentifier;
|
||||
|
||||
public constructor(settings: { store: ResourceStore; baseUrl: string }) {
|
||||
super();
|
||||
this.store = settings.store;
|
||||
this.baseId = { path: ensureTrailingSlash(settings.baseUrl) };
|
||||
}
|
||||
|
||||
public async handle(): Promise<void> {
|
||||
this.logger.debug(`Checking for root container at ${this.baseId.path}`);
|
||||
if (!await this.store.resourceExists(this.baseId)) {
|
||||
await this.createRootContainer();
|
||||
} else {
|
||||
this.logger.debug(`Existing root container found at ${this.baseId.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a root container in a ResourceStore.
|
||||
*/
|
||||
protected async createRootContainer(): Promise<void> {
|
||||
const metadata = new RepresentationMetadata(this.baseId, TEXT_TURTLE);
|
||||
addResourceMetadata(metadata, true);
|
||||
|
||||
// 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}`);
|
||||
await this.store.setRepresentation(this.baseId, new BasicRepresentation([], metadata));
|
||||
}
|
||||
}
|
||||
73
src/init/RootInitializer.ts
Normal file
73
src/init/RootInitializer.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { ResourcesGenerator } from '../pods/generate/ResourcesGenerator';
|
||||
import type { ResourceStore } from '../storage/ResourceStore';
|
||||
import { createErrorMessage } from '../util/errors/ErrorUtil';
|
||||
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
||||
import { ensureTrailingSlash } from '../util/PathUtil';
|
||||
import { PIM, RDF } from '../util/Vocabularies';
|
||||
import { Initializer } from './Initializer';
|
||||
|
||||
/**
|
||||
* Initializer that sets up the root container.
|
||||
* Will copy all the files and folders in the given path to the corresponding documents and containers.
|
||||
* This will always happen when the server starts unless the following 2 conditions are both fulfilled:
|
||||
* * The container already exists.
|
||||
* * The container has metadata indicating it is a pim:Storage.
|
||||
*
|
||||
* It is important that the ResourcesGenerator generates a `<> a pim:Storage` triple for the root container:
|
||||
* 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
|
||||
*/
|
||||
export class RootInitializer extends Initializer {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly store: ResourceStore;
|
||||
private readonly baseId: ResourceIdentifier;
|
||||
private readonly generator: ResourcesGenerator;
|
||||
|
||||
public constructor(baseUrl: string, store: ResourceStore, generator: ResourcesGenerator) {
|
||||
super();
|
||||
this.baseId = { path: ensureTrailingSlash(baseUrl) };
|
||||
this.store = store;
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
public async handle(): Promise<void> {
|
||||
this.logger.debug(`Checking for valid root container at ${this.baseId.path}`);
|
||||
if (!await this.rootContainerIsValid()) {
|
||||
this.logger.info(`Root container not found; initializing it.`);
|
||||
const resources = this.generator.generate(this.baseId, {});
|
||||
let count = 0;
|
||||
for await (const { identifier: resourceId, representation } of resources) {
|
||||
try {
|
||||
await this.store.setRepresentation(resourceId, representation);
|
||||
count += 1;
|
||||
} catch (error: unknown) {
|
||||
this.logger.warn(`Failed to create resource ${resourceId.path}: ${createErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
this.logger.info(`Initialized root container with ${count} resources.`);
|
||||
} else {
|
||||
this.logger.debug(`Valid root container found at ${this.baseId.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the root container already exists and has the pim:Storage type.
|
||||
*/
|
||||
private async rootContainerIsValid(): Promise<boolean> {
|
||||
try {
|
||||
const representation = await this.store.getRepresentation(this.baseId, {});
|
||||
representation.data.destroy();
|
||||
return representation.metadata.getAll(RDF.terms.type).some((term): boolean => term.equals(PIM.terms.Storage));
|
||||
} catch (error: unknown) {
|
||||
if (NotFoundHttpError.isInstance(error)) {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user