mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
fix: Hide internal data by making it auxiliary
This commit is contained in:
parent
e31cd38bc5
commit
0271133d33
@ -56,6 +56,19 @@
|
|||||||
"PathBasedAuthorizer:_paths_value": { "@type": "DenyAllAuthorizer" }
|
"PathBasedAuthorizer:_paths_value": { "@type": "DenyAllAuthorizer" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "Marks the /.internal/ storage container as an auxiliary resource, thereby hiding it from container representations.",
|
||||||
|
"@id": "urn:solid-server:default:AuxiliaryStrategy",
|
||||||
|
"RoutingAuxiliaryStrategy:_sources": [
|
||||||
|
{
|
||||||
|
"@type": "ComposedAuxiliaryStrategy",
|
||||||
|
"identifierStrategy": {
|
||||||
|
"@type": "SuffixAuxiliaryIdentifierStrategy",
|
||||||
|
"suffix": "/.internal/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"@type": "RdfValidator",
|
"@type": "RdfValidator",
|
||||||
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" }
|
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" }
|
||||||
},
|
},
|
||||||
"isRootRequired": true
|
"requiredInRoot": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:AclIdentifierStrategy",
|
"@id": "urn:solid-server:default:AclIdentifierStrategy",
|
||||||
|
@ -10,11 +10,11 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy'
|
|||||||
*/
|
*/
|
||||||
export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
|
export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
|
||||||
/**
|
/**
|
||||||
* Whether this auxiliary resource in a root storage container.
|
* Whether the root storage container requires this auxiliary resource to be present.
|
||||||
* If yes, this means they can't be deleted individually from such a container.
|
* If yes, this means they can't be deleted individually from such a container.
|
||||||
* @param identifier - Identifier of the auxiliary resource.
|
* @param identifier - Identifier of the auxiliary resource.
|
||||||
*/
|
*/
|
||||||
isRootRequired: (identifier: ResourceIdentifier) => boolean;
|
isRequiredInRoot: (identifier: ResourceIdentifier) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds metadata related to this auxiliary resource,
|
* Adds metadata related to this auxiliary resource,
|
||||||
|
@ -14,14 +14,14 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
|
|||||||
private readonly identifierStrategy: AuxiliaryIdentifierStrategy;
|
private readonly identifierStrategy: AuxiliaryIdentifierStrategy;
|
||||||
private readonly metadataGenerator?: MetadataGenerator;
|
private readonly metadataGenerator?: MetadataGenerator;
|
||||||
private readonly validator?: Validator;
|
private readonly validator?: Validator;
|
||||||
private readonly rootRequired: boolean;
|
private readonly requiredInRoot: boolean;
|
||||||
|
|
||||||
public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator,
|
public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator,
|
||||||
validator?: Validator, isRootRequired = false) {
|
validator?: Validator, requiredInRoot = false) {
|
||||||
this.identifierStrategy = identifierStrategy;
|
this.identifierStrategy = identifierStrategy;
|
||||||
this.metadataGenerator = metadataGenerator;
|
this.metadataGenerator = metadataGenerator;
|
||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
this.rootRequired = isRootRequired;
|
this.requiredInRoot = requiredInRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
|
public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
|
||||||
@ -40,8 +40,8 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
|
|||||||
return this.identifierStrategy.getAssociatedIdentifier(identifier);
|
return this.identifierStrategy.getAssociatedIdentifier(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isRootRequired(): boolean {
|
public isRequiredInRoot(): boolean {
|
||||||
return this.rootRequired;
|
return this.requiredInRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
||||||
|
@ -18,9 +18,9 @@ export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy
|
|||||||
super(sources);
|
super(sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isRootRequired(identifier: ResourceIdentifier): boolean {
|
public isRequiredInRoot(identifier: ResourceIdentifier): boolean {
|
||||||
const source = this.getMatchingSource(identifier);
|
const source = this.getMatchingSource(identifier);
|
||||||
return source.isRootRequired(identifier);
|
return source.isRequiredInRoot(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
||||||
|
@ -235,7 +235,8 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
if (this.isRootStorage(metadata)) {
|
if (this.isRootStorage(metadata)) {
|
||||||
throw new MethodNotAllowedHttpError('Cannot delete a root storage container.');
|
throw new MethodNotAllowedHttpError('Cannot delete a root storage container.');
|
||||||
}
|
}
|
||||||
if (this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) && this.auxiliaryStrategy.isRootRequired(identifier)) {
|
if (this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) &&
|
||||||
|
this.auxiliaryStrategy.isRequiredInRoot(identifier)) {
|
||||||
const associatedIdentifier = this.auxiliaryStrategy.getAssociatedIdentifier(identifier);
|
const associatedIdentifier = this.auxiliaryStrategy.getAssociatedIdentifier(identifier);
|
||||||
const parentMetadata = await this.accessor.getMetadata(associatedIdentifier);
|
const parentMetadata = await this.accessor.getMetadata(associatedIdentifier);
|
||||||
if (this.isRootStorage(parentMetadata)) {
|
if (this.isRootStorage(parentMetadata)) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import { DataFactory, Parser } from 'n3';
|
import { DataFactory, Parser, Store } from 'n3';
|
||||||
import { joinFilePath, PIM, RDF } from '../../src/';
|
import { joinFilePath, PIM, RDF } from '../../src/';
|
||||||
import type { App } from '../../src/';
|
import type { App } from '../../src/';
|
||||||
import { LDP } from '../../src/util/Vocabularies';
|
import { LDP } from '../../src/util/Vocabularies';
|
||||||
@ -90,9 +90,11 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC
|
|||||||
|
|
||||||
const parser = new Parser({ baseIRI: baseUrl });
|
const parser = new Parser({ baseIRI: baseUrl });
|
||||||
const quads = parser.parse(await response.text());
|
const quads = parser.parse(await response.text());
|
||||||
expect(quads.some((entry): boolean => entry.equals(
|
const store = new Store(quads);
|
||||||
quad(namedNode(baseUrl), RDF.terms.type, LDP.terms.Container),
|
expect(store.countQuads(namedNode(baseUrl), RDF.terms.type, LDP.terms.Container, null)).toBe(1);
|
||||||
))).toBe(true);
|
const contains = store.getObjects(namedNode(baseUrl), LDP.terms.contains, null);
|
||||||
|
expect(contains).toHaveLength(1);
|
||||||
|
expect(contains[0].value).toBe(`${baseUrl}index.html`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add a document to the store, read it and delete it.', async(): Promise<void> => {
|
it('can add a document to the store, read it and delete it.', async(): Promise<void> => {
|
||||||
|
@ -45,8 +45,8 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
|
|||||||
expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier);
|
expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the injected value for isRootRequired.', async(): Promise<void> => {
|
it('returns the injected value for isRequiredInRoot.', async(): Promise<void> => {
|
||||||
expect(strategy.isRootRequired()).toBe(true);
|
expect(strategy.isRequiredInRoot()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds metadata through the MetadataGenerator.', async(): Promise<void> => {
|
it('adds metadata through the MetadataGenerator.', async(): Promise<void> => {
|
||||||
@ -63,9 +63,9 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
|
|||||||
expect(validator.handleSafe).toHaveBeenLastCalledWith(representation);
|
expect(validator.handleSafe).toHaveBeenLastCalledWith(representation);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults isRootRequired to false.', async(): Promise<void> => {
|
it('defaults isRequiredInRoot to false.', async(): Promise<void> => {
|
||||||
strategy = new ComposedAuxiliaryStrategy(identifierStrategy, metadataGenerator, validator);
|
strategy = new ComposedAuxiliaryStrategy(identifierStrategy, metadataGenerator, validator);
|
||||||
expect(strategy.isRootRequired()).toBe(false);
|
expect(strategy.isRequiredInRoot()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not add metadata or validate if the corresponding classes are not injected.', async(): Promise<void> => {
|
it('does not add metadata or validate if the corresponding classes are not injected.', async(): Promise<void> => {
|
||||||
|
@ -27,7 +27,7 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy {
|
|||||||
return { path: identifier.path.slice(0, -this.suffix.length) };
|
return { path: identifier.path.slice(0, -this.suffix.length) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public isRootRequired(): boolean {
|
public isRequiredInRoot(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +77,13 @@ describe('A RoutingAuxiliaryStrategy', (): void => {
|
|||||||
expect(sources[1].addMetadata).toHaveBeenLastCalledWith(metadata);
|
expect(sources[1].addMetadata).toHaveBeenLastCalledWith(metadata);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#isRootRequired returns the result of the correct source.', async(): Promise<void> => {
|
it('#isRequiredInRoot returns the result of the correct source.', async(): Promise<void> => {
|
||||||
sources[0].isRootRequired = jest.fn();
|
sources[0].isRequiredInRoot = jest.fn();
|
||||||
sources[1].isRootRequired = jest.fn();
|
sources[1].isRequiredInRoot = jest.fn();
|
||||||
strategy.isRootRequired(dummy2Id);
|
strategy.isRequiredInRoot(dummy2Id);
|
||||||
expect(sources[0].isRootRequired).toHaveBeenCalledTimes(0);
|
expect(sources[0].isRequiredInRoot).toHaveBeenCalledTimes(0);
|
||||||
expect(sources[1].isRootRequired).toHaveBeenCalledTimes(1);
|
expect(sources[1].isRequiredInRoot).toHaveBeenCalledTimes(1);
|
||||||
expect(sources[1].isRootRequired).toHaveBeenLastCalledWith(dummy2Id);
|
expect(sources[1].isRequiredInRoot).toHaveBeenLastCalledWith(dummy2Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#validates using the correct validator.', async(): Promise<void> => {
|
it('#validates using the correct validator.', async(): Promise<void> => {
|
||||||
|
@ -107,7 +107,7 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy {
|
|||||||
return { path: identifier.path.slice(0, -this.suffix.length) };
|
return { path: identifier.path.slice(0, -this.suffix.length) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public isRootRequired(): boolean {
|
public isRequiredInRoot(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,7 +602,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
storageMetadata.add(RDF.type, PIM.terms.Storage);
|
storageMetadata.add(RDF.type, PIM.terms.Storage);
|
||||||
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
||||||
accessor.data[`${root}container/.dummy`] = representation;
|
accessor.data[`${root}container/.dummy`] = representation;
|
||||||
auxiliaryStrategy.isRootRequired = jest.fn().mockReturnValue(true);
|
auxiliaryStrategy.isRequiredInRoot = jest.fn().mockReturnValue(true);
|
||||||
const result = store.deleteResource({ path: `${root}container/.dummy` });
|
const result = store.deleteResource({ path: `${root}container/.dummy` });
|
||||||
await expect(result).rejects.toThrow(MethodNotAllowedHttpError);
|
await expect(result).rejects.toThrow(MethodNotAllowedHttpError);
|
||||||
await expect(result).rejects.toThrow(
|
await expect(result).rejects.toThrow(
|
||||||
@ -648,7 +648,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
const storageMetadata = new RepresentationMetadata(representation.metadata);
|
const storageMetadata = new RepresentationMetadata(representation.metadata);
|
||||||
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
||||||
accessor.data[`${root}container/.dummy`] = representation;
|
accessor.data[`${root}container/.dummy`] = representation;
|
||||||
auxiliaryStrategy.isRootRequired = jest.fn().mockReturnValue(true);
|
auxiliaryStrategy.isRequiredInRoot = jest.fn().mockReturnValue(true);
|
||||||
await expect(store.deleteResource({ path: `${root}container/.dummy` })).resolves.toEqual([
|
await expect(store.deleteResource({ path: `${root}container/.dummy` })).resolves.toEqual([
|
||||||
{ path: `${root}container/.dummy` },
|
{ path: `${root}container/.dummy` },
|
||||||
{ path: `${root}container/` },
|
{ path: `${root}container/` },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user