fix: Prevent deletion of root storage containers

This commit is contained in:
Joachim Van Herwegen 2020-12-15 10:40:24 +01:00
parent 2443f2c755
commit 39a79dbcb2
2 changed files with 20 additions and 8 deletions

View File

@ -22,7 +22,7 @@ import {
import { parseQuads } from '../util/QuadUtil';
import { generateResourceQuads } from '../util/ResourceUtil';
import { guardedStreamFrom } from '../util/StreamUtil';
import { CONTENT_TYPE, HTTP, LDP, RDF } from '../util/UriConstants';
import { CONTENT_TYPE, HTTP, LDP, PIM, RDF } from '../util/UriConstants';
import type { DataAccessor } from './accessors/DataAccessor';
import type { ResourceStore } from './ResourceStore';
@ -148,10 +148,13 @@ export class DataAccessorBasedStore implements ResourceStore {
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
this.validateIdentifier(identifier);
if (this.identifierStrategy.isRootContainer(identifier)) {
throw new MethodNotAllowedHttpError('Cannot delete root container.');
}
const metadata = await this.accessor.getMetadata(identifier);
// "When a DELETE request targets storages root container or its associated ACL resource,
// the server MUST respond with the 405 status code."
// https://solid.github.io/specification/#deleting-resources
if (this.isRootStorage(metadata)) {
throw new MethodNotAllowedHttpError('Cannot delete a root storage container.');
}
if (metadata.getAll(LDP.contains).length > 0) {
throw new ConflictHttpError('Can only delete empty containers.');
}
@ -348,6 +351,13 @@ export class DataAccessorBasedStore implements ResourceStore {
return types.some((type): boolean => type.value === LDP.Container || type.value === LDP.BasicContainer);
}
/**
* Verifies if this is the metadata of a root storage container.
*/
protected isRootStorage(metadata: RepresentationMetadata): boolean {
return metadata.getAll(RDF.type).some((term): boolean => term.value === PIM.Storage);
}
/**
* Create containers starting from the root until the given identifier corresponds to an existing container.
* Will throw errors if the identifier of the last existing "container" corresponds to an existing document.

View File

@ -18,7 +18,7 @@ import type { Guarded } from '../../../src/util/GuardedStream';
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
import * as quadUtil from '../../../src/util/QuadUtil';
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
import { CONTENT_TYPE, HTTP, LDP, RDF } from '../../../src/util/UriConstants';
import { CONTENT_TYPE, HTTP, LDP, PIM, RDF } from '../../../src/util/UriConstants';
import { toNamedNode } from '../../../src/util/UriUtil';
import quad = DataFactory.quad;
import namedNode = DataFactory.namedNode;
@ -389,9 +389,11 @@ describe('A DataAccessorBasedStore', (): void => {
.rejects.toThrow(NotFoundHttpError);
});
it('will error when deleting the root.', async(): Promise<void> => {
await expect(store.deleteResource({ path: root }))
.rejects.toThrow(new MethodNotAllowedHttpError('Cannot delete root container.'));
it('will error when deleting a root storage container.', async(): Promise<void> => {
representation.metadata.add(RDF.type, toNamedNode(PIM.Storage));
accessor.data[`${root}container`] = representation;
await expect(store.deleteResource({ path: `${root}container` }))
.rejects.toThrow(new MethodNotAllowedHttpError('Cannot delete a root storage container.'));
});
it('will error when deleting non-empty containers.', async(): Promise<void> => {