mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create SubdomainIdentifierStrategy
This strategy interprets all subdomains of the base to also be root containers.
This commit is contained in:
parent
bdb3621ee3
commit
29df380396
@ -216,8 +216,10 @@ export * from './util/handlers/UnsupportedAsyncHandler';
|
|||||||
export * from './util/handlers/WaterfallHandler';
|
export * from './util/handlers/WaterfallHandler';
|
||||||
|
|
||||||
// Util/Identifiers
|
// Util/Identifiers
|
||||||
|
export * from './util/identifiers/BaseIdentifierStrategy';
|
||||||
export * from './util/identifiers/IdentifierStrategy';
|
export * from './util/identifiers/IdentifierStrategy';
|
||||||
export * from './util/identifiers/SingleRootIdentifierStrategy';
|
export * from './util/identifiers/SingleRootIdentifierStrategy';
|
||||||
|
export * from './util/identifiers/SubdomainIdentifierStrategy';
|
||||||
|
|
||||||
// Util/Locking
|
// Util/Locking
|
||||||
export * from './util/locking/ExpiringReadWriteLocker';
|
export * from './util/locking/ExpiringReadWriteLocker';
|
||||||
|
29
src/util/identifiers/BaseIdentifierStrategy.ts
Normal file
29
src/util/identifiers/BaseIdentifierStrategy.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
|
import { InternalServerError } from '../errors/InternalServerError';
|
||||||
|
import { ensureTrailingSlash } from '../PathUtil';
|
||||||
|
import type { IdentifierStrategy } from './IdentifierStrategy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a default implementation for `getParentContainer`
|
||||||
|
* which checks if the identifier is supported and not a root container.
|
||||||
|
* If not, the last part before the first relevant slash will be removed to find the parent.
|
||||||
|
*/
|
||||||
|
export abstract class BaseIdentifierStrategy implements IdentifierStrategy {
|
||||||
|
public abstract supportsIdentifier(identifier: ResourceIdentifier): boolean;
|
||||||
|
|
||||||
|
public getParentContainer(identifier: ResourceIdentifier): ResourceIdentifier {
|
||||||
|
if (!this.supportsIdentifier(identifier)) {
|
||||||
|
throw new InternalServerError(`${identifier.path} is not supported`);
|
||||||
|
}
|
||||||
|
if (this.isRootContainer(identifier)) {
|
||||||
|
throw new InternalServerError(`${identifier.path} is a root container and has no parent`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing slash is necessary for URL library
|
||||||
|
const parentPath = new URL('..', ensureTrailingSlash(identifier.path)).href;
|
||||||
|
|
||||||
|
return { path: parentPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract isRootContainer(identifier: ResourceIdentifier): boolean;
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
import { getLoggerFor } from '../../logging/LogUtil';
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
import { InternalServerError } from '../errors/InternalServerError';
|
|
||||||
import { ensureTrailingSlash } from '../PathUtil';
|
import { ensureTrailingSlash } from '../PathUtil';
|
||||||
import type { IdentifierStrategy } from './IdentifierStrategy';
|
import { BaseIdentifierStrategy } from './BaseIdentifierStrategy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An IdentifierStrategy that assumes there is only 1 root and all other identifiers are made by appending to that root.
|
* An IdentifierStrategy that assumes there is only 1 root and all other identifiers are made by appending to that root.
|
||||||
*/
|
*/
|
||||||
export class SingleRootIdentifierStrategy implements IdentifierStrategy {
|
export class SingleRootIdentifierStrategy extends BaseIdentifierStrategy {
|
||||||
private readonly baseUrl: string;
|
private readonly baseUrl: string;
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
public constructor(baseUrl: string) {
|
public constructor(baseUrl: string) {
|
||||||
|
super();
|
||||||
this.baseUrl = ensureTrailingSlash(baseUrl);
|
this.baseUrl = ensureTrailingSlash(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,20 +23,6 @@ export class SingleRootIdentifierStrategy implements IdentifierStrategy {
|
|||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getParentContainer(identifier: ResourceIdentifier): ResourceIdentifier {
|
|
||||||
if (!this.supportsIdentifier(identifier)) {
|
|
||||||
throw new InternalServerError(`${identifier.path} is not supported`);
|
|
||||||
}
|
|
||||||
if (this.isRootContainer(identifier)) {
|
|
||||||
throw new InternalServerError(`${identifier.path} is a root container and has no parent`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trailing slash is necessary for URL library
|
|
||||||
const parentPath = new URL('..', ensureTrailingSlash(identifier.path)).href;
|
|
||||||
|
|
||||||
return { path: parentPath };
|
|
||||||
}
|
|
||||||
|
|
||||||
public isRootContainer(identifier: ResourceIdentifier): boolean {
|
public isRootContainer(identifier: ResourceIdentifier): boolean {
|
||||||
return identifier.path === this.baseUrl;
|
return identifier.path === this.baseUrl;
|
||||||
}
|
}
|
||||||
|
32
src/util/identifiers/SubdomainIdentifierStrategy.ts
Normal file
32
src/util/identifiers/SubdomainIdentifierStrategy.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
|
import { ensureTrailingSlash, createSubdomainRegexp } from '../PathUtil';
|
||||||
|
import { BaseIdentifierStrategy } from './BaseIdentifierStrategy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IdentifierStrategy that interprets all subdomains of the given base URL as roots.
|
||||||
|
*/
|
||||||
|
export class SubdomainIdentifierStrategy extends BaseIdentifierStrategy {
|
||||||
|
private readonly baseUrl: string;
|
||||||
|
private readonly regex: RegExp;
|
||||||
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
|
public constructor(baseUrl: string) {
|
||||||
|
super();
|
||||||
|
this.baseUrl = ensureTrailingSlash(baseUrl);
|
||||||
|
this.regex = createSubdomainRegexp(this.baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public supportsIdentifier(identifier: ResourceIdentifier): boolean {
|
||||||
|
const supported = this.regex.test(identifier.path);
|
||||||
|
this.logger.debug(supported ?
|
||||||
|
`Identifier ${identifier.path} is part of ${this.baseUrl}` :
|
||||||
|
`Identifier ${identifier.path} is not part of ${this.baseUrl}`);
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRootContainer(identifier: ResourceIdentifier): boolean {
|
||||||
|
const match = this.regex.exec(identifier.path);
|
||||||
|
return Array.isArray(match) && match[0].length === identifier.path.length;
|
||||||
|
}
|
||||||
|
}
|
31
test/unit/util/identifiers/BaseIdentifierStrategy.test.ts
Normal file
31
test/unit/util/identifiers/BaseIdentifierStrategy.test.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
|
||||||
|
import { BaseIdentifierStrategy } from '../../../../src/util/identifiers/BaseIdentifierStrategy';
|
||||||
|
|
||||||
|
class DummyStrategy extends BaseIdentifierStrategy {
|
||||||
|
public supportsIdentifier(identifier: ResourceIdentifier): boolean {
|
||||||
|
return !identifier.path.endsWith('unsupported');
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRootContainer(identifier: ResourceIdentifier): boolean {
|
||||||
|
return identifier.path.endsWith('root');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('A BaseIdentifierStrategy', (): void => {
|
||||||
|
const strategy = new DummyStrategy();
|
||||||
|
|
||||||
|
it('returns the parent identifier.', async(): Promise<void> => {
|
||||||
|
expect(strategy.getParentContainer({ path: 'http://test.com/foo/bar' })).toEqual({ path: 'http://test.com/foo/' });
|
||||||
|
expect(strategy.getParentContainer({ path: 'http://test.com/foo/bar/' })).toEqual({ path: 'http://test.com/foo/' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when attempting to get the parent of an unsupported identifier.', async(): Promise<void> => {
|
||||||
|
expect((): any => strategy.getParentContainer({ path: '/unsupported' }))
|
||||||
|
.toThrow('/unsupported is not supported');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when attempting to get the parent of a root container.', async(): Promise<void> => {
|
||||||
|
expect((): any => strategy.getParentContainer({ path: 'http://test.com/root' }))
|
||||||
|
.toThrow('http://test.com/root is a root container and has no parent');
|
||||||
|
});
|
||||||
|
});
|
@ -2,32 +2,17 @@ import { SingleRootIdentifierStrategy } from '../../../../src/util/identifiers/S
|
|||||||
|
|
||||||
describe('A SingleRootIdentifierStrategy', (): void => {
|
describe('A SingleRootIdentifierStrategy', (): void => {
|
||||||
const baseUrl = 'http://test.com/';
|
const baseUrl = 'http://test.com/';
|
||||||
const manager = new SingleRootIdentifierStrategy(baseUrl);
|
const strategy = new SingleRootIdentifierStrategy(baseUrl);
|
||||||
|
|
||||||
it('verifies if identifiers are in its domain.', async(): Promise<void> => {
|
it('verifies if identifiers are in its domain.', async(): Promise<void> => {
|
||||||
expect(manager.supportsIdentifier({ path: 'http://notest.com/' })).toBe(false);
|
expect(strategy.supportsIdentifier({ path: 'http://notest.com/' })).toBe(false);
|
||||||
expect(manager.supportsIdentifier({ path: baseUrl })).toBe(true);
|
expect(strategy.supportsIdentifier({ path: baseUrl })).toBe(true);
|
||||||
expect(manager.supportsIdentifier({ path: `${baseUrl}foo/bar` })).toBe(true);
|
expect(strategy.supportsIdentifier({ path: `${baseUrl}foo/bar` })).toBe(true);
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the parent identifier.', async(): Promise<void> => {
|
|
||||||
expect(manager.getParentContainer({ path: 'http://test.com/foo/bar' })).toEqual({ path: 'http://test.com/foo/' });
|
|
||||||
expect(manager.getParentContainer({ path: 'http://test.com/foo/bar/' })).toEqual({ path: 'http://test.com/foo/' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('errors when attempting to get the parent of an unsupported identifier.', async(): Promise<void> => {
|
|
||||||
expect((): any => manager.getParentContainer({ path: 'http://nottest.com/' }))
|
|
||||||
.toThrow('http://nottest.com/ is not supported');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('errors when attempting to get the parent of a root container.', async(): Promise<void> => {
|
|
||||||
expect((): any => manager.getParentContainer({ path: 'http://test.com/' }))
|
|
||||||
.toThrow('http://test.com/ is a root container and has no parent');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks for the root container by comparing with the base URL.', async(): Promise<void> => {
|
it('checks for the root container by comparing with the base URL.', async(): Promise<void> => {
|
||||||
expect(manager.isRootContainer({ path: 'http://notest.com/' })).toBe(false);
|
expect(strategy.isRootContainer({ path: 'http://notest.com/' })).toBe(false);
|
||||||
expect(manager.isRootContainer({ path: baseUrl })).toBe(true);
|
expect(strategy.isRootContainer({ path: baseUrl })).toBe(true);
|
||||||
expect(manager.isRootContainer({ path: `${baseUrl}foo/bar` })).toBe(false);
|
expect(strategy.isRootContainer({ path: `${baseUrl}foo/bar` })).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { SubdomainIdentifierStrategy } from '../../../../src/util/identifiers/SubdomainIdentifierStrategy';
|
||||||
|
|
||||||
|
describe('A SubdomainIdentifierStrategy', (): void => {
|
||||||
|
const baseUrl = 'http://test.com/foo/';
|
||||||
|
const strategy = new SubdomainIdentifierStrategy(baseUrl);
|
||||||
|
|
||||||
|
it('supports URLs in its domain.', async(): Promise<void> => {
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://test.com/foo/' })).toBe(true);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://alice.test.com/foo/' })).toBe(true);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://a.b.c.test.com/foo/' })).toBe(true);
|
||||||
|
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://test.com/foo/bar' })).toBe(true);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://alice.test.com/foo/bar' })).toBe(true);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://a.b.c.test.com/foo/bar' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not support URLs outside of its domain.', async(): Promise<void> => {
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://fake.com/http://test.com/foo/' })).toBe(false);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://fake.com/test.com/foo/' })).toBe(false);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://faketest.com/foo/' })).toBe(false);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'http://test.com/foo' })).toBe(false);
|
||||||
|
expect(strategy.supportsIdentifier({ path: 'ftp://test.com/foo/' })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifiers the base and all subdomains as root containers.', async(): Promise<void> => {
|
||||||
|
expect(strategy.isRootContainer({ path: 'http://test.com/foo/' })).toBe(true);
|
||||||
|
expect(strategy.isRootContainer({ path: 'http://alice.test.com/foo/' })).toBe(true);
|
||||||
|
|
||||||
|
expect(strategy.isRootContainer({ path: 'http://test.com/foo/bar' })).toBe(false);
|
||||||
|
expect(strategy.isRootContainer({ path: 'http://alice.test.com/foo/bar' })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user