mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Replace getParentContainer util function with ContainerManager
This commit is contained in:
parent
9c080c2101
commit
f0db9e501f
@ -13,6 +13,9 @@
|
||||
},
|
||||
"WebAclAuthorizer:_resourceStore": {
|
||||
"@id": "urn:solid-server:default:ResourceStore"
|
||||
},
|
||||
"WebAclAuthorizer:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -6,6 +6,17 @@
|
||||
"@type": "MonitoringStore",
|
||||
"MonitoringStore:_source": {
|
||||
"@id": "urn:solid-server:default:ResourceStore_Patching"
|
||||
},
|
||||
"MonitoringStore:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy",
|
||||
"@type": "SingleRootIdentifierStrategy",
|
||||
"SingleRootIdentifierStrategy:_baseUrl": {
|
||||
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -28,6 +28,9 @@
|
||||
},
|
||||
"DataAccessorBasedStore:_base": {
|
||||
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||
},
|
||||
"DataAccessorBasedStore:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -16,6 +16,9 @@
|
||||
},
|
||||
"DataAccessorBasedStore:_base": {
|
||||
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||
},
|
||||
"DataAccessorBasedStore:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -9,6 +9,9 @@
|
||||
},
|
||||
"SparqlDataAccessor:_base": {
|
||||
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||
},
|
||||
"SparqlDataAccessor:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
},
|
||||
|
||||
@ -20,6 +23,9 @@
|
||||
},
|
||||
"DataAccessorBasedStore:_base": {
|
||||
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||
},
|
||||
"DataAccessorBasedStore:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -8,9 +8,10 @@ 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 { getParentContainer } from '../util/PathUtil';
|
||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||
import { ACL, FOAF } from '../util/UriConstants';
|
||||
import type { AclManager } from './AclManager';
|
||||
import type { AuthorizerArgs } from './Authorizer';
|
||||
@ -26,11 +27,13 @@ export class WebAclAuthorizer extends Authorizer {
|
||||
|
||||
private readonly aclManager: AclManager;
|
||||
private readonly resourceStore: ResourceStore;
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
|
||||
public constructor(aclManager: AclManager, resourceStore: ResourceStore) {
|
||||
public constructor(aclManager: AclManager, resourceStore: ResourceStore, identifierStrategy: IdentifierStrategy) {
|
||||
super();
|
||||
this.aclManager = aclManager;
|
||||
this.resourceStore = resourceStore;
|
||||
this.identifierStrategy = identifierStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +137,11 @@ export class WebAclAuthorizer extends Authorizer {
|
||||
}
|
||||
|
||||
this.logger.debug(`Traversing to the parent of ${id.path}`);
|
||||
const parent = getParentContainer(id);
|
||||
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');
|
||||
}
|
||||
const parent = this.identifierStrategy.getParentContainer(id);
|
||||
return this.getAclRecursive(parent, true);
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,9 @@ import { ConflictHttpError } from '../util/errors/ConflictHttpError';
|
||||
import { MethodNotAllowedHttpError } from '../util/errors/MethodNotAllowedHttpError';
|
||||
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||
import {
|
||||
ensureTrailingSlash,
|
||||
getParentContainer,
|
||||
isContainerIdentifier,
|
||||
isContainerPath,
|
||||
trimTrailingSlashes,
|
||||
@ -51,10 +51,12 @@ import type { ResourceStore } from './ResourceStore';
|
||||
export class DataAccessorBasedStore implements ResourceStore {
|
||||
private readonly accessor: DataAccessor;
|
||||
private readonly base: string;
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
|
||||
public constructor(accessor: DataAccessor, base: string) {
|
||||
public constructor(accessor: DataAccessor, base: string, identifierStrategy: IdentifierStrategy) {
|
||||
this.accessor = accessor;
|
||||
this.base = ensureTrailingSlash(base);
|
||||
this.identifierStrategy = identifierStrategy;
|
||||
}
|
||||
|
||||
public async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {
|
||||
@ -219,7 +221,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
}
|
||||
|
||||
if (createContainers) {
|
||||
await this.createRecursiveContainers(getParentContainer(identifier));
|
||||
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(identifier));
|
||||
}
|
||||
|
||||
// Make sure the metadata has the correct identifier and correct type quads
|
||||
@ -354,7 +356,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof NotFoundHttpError) {
|
||||
// Make sure the parent exists first
|
||||
await this.createRecursiveContainers(getParentContainer(container));
|
||||
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
|
||||
await this.writeData(container, this.getEmptyContainerRepresentation(container), true);
|
||||
} else {
|
||||
throw error;
|
||||
|
@ -3,7 +3,7 @@ import type { Patch } from '../ldp/http/Patch';
|
||||
import type { Representation } from '../ldp/representation/Representation';
|
||||
import type { RepresentationPreferences } from '../ldp/representation/RepresentationPreferences';
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import { getParentContainer } from '../util/PathUtil';
|
||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
|
||||
@ -14,10 +14,12 @@ import type { ResourceStore } from './ResourceStore';
|
||||
export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
||||
extends EventEmitter implements ResourceStore {
|
||||
private readonly source: T;
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
|
||||
public constructor(source: T) {
|
||||
public constructor(source: T, identifierStrategy: IdentifierStrategy) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.identifierStrategy = identifierStrategy;
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
@ -35,11 +37,9 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
||||
await this.source.deleteResource(identifier, conditions);
|
||||
|
||||
// Both the container contents and the resource itself have changed
|
||||
try {
|
||||
const container = getParentContainer(identifier);
|
||||
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||
this.emit('changed', container);
|
||||
} catch {
|
||||
// Parent container not found
|
||||
}
|
||||
this.emit('changed', identifier);
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpErr
|
||||
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
|
||||
import { guardStream } from '../../util/GuardedStream';
|
||||
import type { Guarded } from '../../util/GuardedStream';
|
||||
import { ensureTrailingSlash, getParentContainer, isContainerIdentifier } from '../../util/PathUtil';
|
||||
import type { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy';
|
||||
import { ensureTrailingSlash, isContainerIdentifier } from '../../util/PathUtil';
|
||||
import { generateResourceQuads } from '../../util/ResourceUtil';
|
||||
import { CONTENT_TYPE, LDP } from '../../util/UriConstants';
|
||||
import { toNamedNode } from '../../util/UriUtil';
|
||||
@ -49,12 +50,14 @@ export class SparqlDataAccessor implements DataAccessor {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
private readonly endpoint: string;
|
||||
private readonly base: string;
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
private readonly fetcher: SparqlEndpointFetcher;
|
||||
private readonly generator: SparqlGenerator;
|
||||
|
||||
public constructor(endpoint: string, base: string) {
|
||||
public constructor(endpoint: string, base: string, identifierStrategy: IdentifierStrategy) {
|
||||
this.endpoint = endpoint;
|
||||
this.base = ensureTrailingSlash(base);
|
||||
this.identifierStrategy = identifierStrategy;
|
||||
this.fetcher = new SparqlEndpointFetcher();
|
||||
this.generator = new Generator();
|
||||
}
|
||||
@ -149,7 +152,7 @@ export class SparqlDataAccessor implements DataAccessor {
|
||||
* Helper function to get named nodes corresponding to the identifier and its parent container.
|
||||
*/
|
||||
private getRelatedNames(identifier: ResourceIdentifier): { name: NamedNode; parent: NamedNode } {
|
||||
const parentIdentifier = getParentContainer(identifier);
|
||||
const parentIdentifier = this.identifierStrategy.getParentContainer(identifier);
|
||||
const name = namedNode(identifier.path);
|
||||
const parent = namedNode(parentIdentifier.path);
|
||||
return { name, parent };
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import { InternalServerError } from './errors/InternalServerError';
|
||||
|
||||
/**
|
||||
* Makes sure the input path has exactly 1 slash at the end.
|
||||
@ -39,26 +38,6 @@ export const decodeUriPathComponents = (path: string): string => path.split('/')
|
||||
*/
|
||||
export const encodeUriPathComponents = (path: string): string => path.split('/').map(encodeURIComponent).join('/');
|
||||
|
||||
/**
|
||||
* Finds the container containing the given resource.
|
||||
* This does not ensure either the container or resource actually exist.
|
||||
*
|
||||
* @param id - Identifier to find container of.
|
||||
*
|
||||
* @returns The identifier of the container this resource is in.
|
||||
*/
|
||||
export const getParentContainer = (id: ResourceIdentifier): ResourceIdentifier => {
|
||||
// Trailing slash is necessary for URL library
|
||||
const parentPath = new URL('..', ensureTrailingSlash(id.path)).toString();
|
||||
|
||||
// This probably means there is an issue with the root
|
||||
if (parentPath === id.path) {
|
||||
throw new InternalServerError(`Resource ${id.path} does not have a parent container`);
|
||||
}
|
||||
|
||||
return { path: parentPath };
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the path corresponds to a container path (ending in a /).
|
||||
* @param path - Path to check.
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
RawBodyParser,
|
||||
RepresentationConvertingStore,
|
||||
SequenceHandler,
|
||||
SingleRootIdentifierStrategy,
|
||||
SingleThreadedResourceLocker,
|
||||
SlugParser,
|
||||
SparqlUpdatePatchHandler,
|
||||
@ -60,7 +61,7 @@ export const getRootFilePath = (subfolder: string): string => join(__dirname, '.
|
||||
* @returns The data accessor based store.
|
||||
*/
|
||||
export const getDataAccessorStore = (base: string, dataAccessor: DataAccessor): DataAccessorBasedStore =>
|
||||
new DataAccessorBasedStore(dataAccessor, base);
|
||||
new DataAccessorBasedStore(dataAccessor, base, new SingleRootIdentifierStrategy(base));
|
||||
|
||||
/**
|
||||
* Gives an in memory resource store based on (default) base url.
|
||||
@ -175,7 +176,7 @@ export const getBasicRequestParser = (bodyParsers: BodyParser[] = []): BasicRequ
|
||||
* @returns The acl authorizer.
|
||||
*/
|
||||
export const getWebAclAuthorizer = (store: ResourceStore, aclManager = new UrlBasedAclManager()): WebAclAuthorizer =>
|
||||
new WebAclAuthorizer(aclManager, store);
|
||||
new WebAclAuthorizer(aclManager, store, new SingleRootIdentifierStrategy(BASE));
|
||||
|
||||
/**
|
||||
* Returns a component instantiated from a Components.js configuration.
|
||||
|
@ -6,6 +6,7 @@ import { DataAccessorBasedStore } from '../../src/storage/DataAccessorBasedStore
|
||||
import { LockingResourceStore } from '../../src/storage/LockingResourceStore';
|
||||
import type { ResourceStore } from '../../src/storage/ResourceStore';
|
||||
import { APPLICATION_OCTET_STREAM } from '../../src/util/ContentTypes';
|
||||
import { SingleRootIdentifierStrategy } from '../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
import type { ExpiringResourceLocker } from '../../src/util/locking/ExpiringResourceLocker';
|
||||
import type { ResourceLocker } from '../../src/util/locking/ResourceLocker';
|
||||
import { SingleThreadedResourceLocker } from '../../src/util/locking/SingleThreadedResourceLocker';
|
||||
@ -25,7 +26,7 @@ describe('A LockingResourceStore', (): void => {
|
||||
|
||||
const base = 'http://test.com/';
|
||||
path = `${base}path`;
|
||||
source = new DataAccessorBasedStore(new InMemoryDataAccessor(base), base);
|
||||
source = new DataAccessorBasedStore(new InMemoryDataAccessor(base), base, new SingleRootIdentifierStrategy(base));
|
||||
|
||||
locker = new SingleThreadedResourceLocker();
|
||||
expiringLocker = new WrappedExpiringResourceLocker(locker, 1000);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { SparqlDataAccessor } from '../../src/storage/accessors/SparqlDataAccessor';
|
||||
import { INTERNAL_QUADS } from '../../src/util/ContentTypes';
|
||||
import { SingleRootIdentifierStrategy } from '../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
import { DataAccessorBasedConfig } from '../configs/DataAccessorBasedConfig';
|
||||
import { BASE } from '../configs/Util';
|
||||
import { describeIf, FileTestHelper } from '../util/TestHelpers';
|
||||
@ -7,7 +8,7 @@ import { describeIf, FileTestHelper } from '../util/TestHelpers';
|
||||
describeIf('docker', 'a server with a SPARQL endpoint as storage', (): void => {
|
||||
describe('without acl', (): void => {
|
||||
const config = new DataAccessorBasedConfig(BASE,
|
||||
new SparqlDataAccessor('http://localhost:4000/sparql', BASE),
|
||||
new SparqlDataAccessor('http://localhost:4000/sparql', BASE, new SingleRootIdentifierStrategy(BASE)),
|
||||
INTERNAL_QUADS);
|
||||
const handler = config.getHttpHandler();
|
||||
const fileHelper = new FileTestHelper(handler, new URL(BASE));
|
||||
|
@ -8,9 +8,10 @@ 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 { getParentContainer } from '../../../src/util/PathUtil';
|
||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
|
||||
const nn = namedNode;
|
||||
|
||||
@ -25,6 +26,8 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
getAclConstrainedResource: async(id: ResourceIdentifier): Promise<ResourceIdentifier> =>
|
||||
!id.path.endsWith('.acl') ? id : { path: id.path.slice(0, -4) },
|
||||
};
|
||||
let store: ResourceStore;
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');
|
||||
let permissions: PermissionSet;
|
||||
let credentials: Credentials;
|
||||
let identifier: ResourceIdentifier;
|
||||
@ -37,132 +40,119 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
};
|
||||
credentials = {};
|
||||
identifier = { path: 'http://test.com/foo' };
|
||||
|
||||
store = {
|
||||
getRepresentation: jest.fn(),
|
||||
} as any;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store, identifierStrategy);
|
||||
});
|
||||
|
||||
it('handles all inputs.', async(): Promise<void> => {
|
||||
authorizer = new WebAclAuthorizer(aclManager, null as any);
|
||||
authorizer = new WebAclAuthorizer(aclManager, null as any, identifierStrategy);
|
||||
await expect(authorizer.canHandle({} as any)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows access if the acl file allows all agents.', async(): Promise<void> => {
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows access if there is a parent acl file allowing all agents.', async(): Promise<void> => {
|
||||
const store = {
|
||||
async getRepresentation(id: ResourceIdentifier): Promise<Representation> {
|
||||
if (id.path.endsWith('foo.acl')) {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
return {
|
||||
data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
|
||||
quad(nn('auth'), nn(`${acl}default`), nn(getParentContainer(identifier).path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]),
|
||||
} as Representation;
|
||||
},
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(id: ResourceIdentifier): Promise<Representation> => {
|
||||
if (id.path.endsWith('foo.acl')) {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
return {
|
||||
data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
|
||||
quad(nn('auth'), nn(`${acl}default`), nn(identifierStrategy.getParentContainer(identifier).path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]),
|
||||
} as Representation;
|
||||
};
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows access to authorized agents if the acl files allows all authorized users.', async(): Promise<void> => {
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
credentials.webId = 'http://test.com/user';
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('errors if authorization is required but the agent is not authorized.', async(): Promise<void> => {
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(UnauthorizedHttpError);
|
||||
});
|
||||
|
||||
it('allows access to specific agents if the acl files identifies them.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('errors if a specific agents wants to access files not assigned to them.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn('http://test.com/differentUser')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn('http://test.com/differentUser')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
|
||||
it('allows access to the acl file if control is allowed.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
identifier.path = 'http://test.com/foo';
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)),
|
||||
]) } as Representation);
|
||||
const aclIdentifier = await aclManager.getAclDocument(identifier);
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
await expect(authorizer.handle({ identifier: aclIdentifier, permissions, credentials })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('errors if an agent tries to edit the acl file without control permissions.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
identifier.path = 'http://test.com/foo';
|
||||
const store = {
|
||||
getRepresentation: async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation),
|
||||
} as unknown as ResourceStore;
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
identifier = await aclManager.getAclDocument(identifier);
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
|
||||
it('passes errors of the ResourceStore along.', async(): Promise<void> => {
|
||||
const store = {
|
||||
async getRepresentation(): Promise<Representation> {
|
||||
throw new Error('TEST!');
|
||||
},
|
||||
} as unknown as ResourceStore;
|
||||
authorizer = new WebAclAuthorizer(aclManager, store);
|
||||
store.getRepresentation = async(): Promise<Representation> => {
|
||||
throw new Error('TEST!');
|
||||
};
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow('TEST!');
|
||||
});
|
||||
|
||||
it('errors if the root container has no corresponding acl document.', async(): Promise<void> => {
|
||||
store.getRepresentation = async(): Promise<Representation> => {
|
||||
throw new NotFoundHttpError();
|
||||
};
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@ -14,6 +14,7 @@ import { MethodNotAllowedHttpError } from '../../../src/util/errors/MethodNotAll
|
||||
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
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';
|
||||
@ -71,6 +72,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
let store: DataAccessorBasedStore;
|
||||
let accessor: SimpleDataAccessor;
|
||||
const root = 'http://test.com/';
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy(root);
|
||||
let containerMetadata: RepresentationMetadata;
|
||||
let representation: Representation;
|
||||
const resourceData = 'text';
|
||||
@ -78,7 +80,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
beforeEach(async(): Promise<void> => {
|
||||
accessor = new SimpleDataAccessor();
|
||||
|
||||
store = new DataAccessorBasedStore(accessor, root);
|
||||
store = new DataAccessorBasedStore(accessor, root, identifierStrategy);
|
||||
|
||||
containerMetadata = new RepresentationMetadata(
|
||||
{ [RDF.type]: [ DataFactory.namedNode(LDP.Container), DataFactory.namedNode(LDP.BasicContainer) ]},
|
||||
|
@ -2,10 +2,12 @@ import type { Patch } from '../../../src/ldp/http/Patch';
|
||||
import type { Representation } from '../../../src/ldp/representation/Representation';
|
||||
import { MonitoringStore } from '../../../src/storage/MonitoringStore';
|
||||
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
|
||||
describe('A MonitoringStore', (): void => {
|
||||
let store: MonitoringStore;
|
||||
let source: ResourceStore;
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://example.org/');
|
||||
let changedCallback: () => void;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
@ -16,7 +18,7 @@ describe('A MonitoringStore', (): void => {
|
||||
deleteResource: jest.fn(async(): Promise<any> => undefined),
|
||||
modifyResource: jest.fn(async(): Promise<any> => undefined),
|
||||
};
|
||||
store = new MonitoringStore(source);
|
||||
store = new MonitoringStore(source, identifierStrategy);
|
||||
changedCallback = jest.fn();
|
||||
store.on('changed', changedCallback);
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ import { ConflictHttpError } from '../../../../src/util/errors/ConflictHttpError
|
||||
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
||||
import type { Guarded } from '../../../../src/util/GuardedStream';
|
||||
import { SingleRootIdentifierStrategy } from '../../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
import { guardedStreamFrom } from '../../../../src/util/StreamUtil';
|
||||
import { CONTENT_TYPE, LDP, RDF } from '../../../../src/util/UriConstants';
|
||||
import { toNamedNode } from '../../../../src/util/UriUtil';
|
||||
@ -30,6 +31,7 @@ const simplifyQuery = (query: string | string[]): string => {
|
||||
describe('A SparqlDataAccessor', (): void => {
|
||||
const endpoint = 'http://test.com/sparql';
|
||||
const base = 'http://test.com/';
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy(base);
|
||||
let accessor: SparqlDataAccessor;
|
||||
let metadata: RepresentationMetadata;
|
||||
let data: Guarded<Readable>;
|
||||
@ -64,7 +66,7 @@ describe('A SparqlDataAccessor', (): void => {
|
||||
}));
|
||||
|
||||
// This needs to be last so the fetcher can be mocked first
|
||||
accessor = new SparqlDataAccessor(endpoint, base);
|
||||
accessor = new SparqlDataAccessor(endpoint, base, identifierStrategy);
|
||||
});
|
||||
|
||||
it('can only handle quad data.', async(): Promise<void> => {
|
||||
|
@ -2,7 +2,6 @@ import {
|
||||
decodeUriPathComponents,
|
||||
encodeUriPathComponents,
|
||||
ensureTrailingSlash,
|
||||
getParentContainer,
|
||||
toCanonicalUriPath,
|
||||
} from '../../../src/util/PathUtil';
|
||||
|
||||
@ -29,16 +28,4 @@ describe('PathUtil', (): void => {
|
||||
expect(encodeUriPathComponents('/a%20path&/name')).toEqual('/a%2520path%26/name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getParentContainer', (): void => {
|
||||
it('returns the parent URl for a single call.', async(): Promise<void> => {
|
||||
expect(getParentContainer({ path: 'http://test.com/foo/bar' })).toEqual({ path: 'http://test.com/foo/' });
|
||||
expect(getParentContainer({ path: 'http://test.com/foo/bar/' })).toEqual({ path: 'http://test.com/foo/' });
|
||||
});
|
||||
|
||||
it('errors when the root of an URl is reached that does not match the input root.', async(): Promise<void> => {
|
||||
expect((): any => getParentContainer({ path: 'http://test.com/' }))
|
||||
.toThrow('Resource http://test.com/ does not have a parent container');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user