Skip to content

Commit

Permalink
fix: Prevent deletion of root storage containers
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimvh committed Dec 18, 2020
1 parent 2443f2c commit 39a79db
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 8 deletions.
18 changes: 14 additions & 4 deletions src/storage/DataAccessorBasedStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 storage’s 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.');
}
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 6 additions & 4 deletions test/unit/storage/DataAccessorBasedStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> => {
Expand Down

0 comments on commit 39a79db

Please sign in to comment.