mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create PathBasedAuthorizer
This commit is contained in:
parent
d95db60fe1
commit
f4833d2534
52
src/authorization/PathBasedAuthorizer.ts
Normal file
52
src/authorization/PathBasedAuthorizer.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil';
|
||||
import type { Authorization } from './Authorization';
|
||||
import type { AuthorizerArgs } from './Authorizer';
|
||||
import { Authorizer } from './Authorizer';
|
||||
|
||||
/**
|
||||
* Redirects requests to specific authorizers based on their identifier.
|
||||
* The keys in the input map will be converted to regular expressions.
|
||||
* The regular expressions should all start with a slash
|
||||
* and will be evaluated relative to the base URL.
|
||||
*
|
||||
* Will error if no match is found.
|
||||
*/
|
||||
export class PathBasedAuthorizer extends Authorizer {
|
||||
private readonly baseUrl: string;
|
||||
private readonly paths: Map<RegExp, Authorizer>;
|
||||
|
||||
public constructor(baseUrl: string, paths: Record<string, Authorizer>) {
|
||||
super();
|
||||
this.baseUrl = ensureTrailingSlash(baseUrl);
|
||||
const entries = Object.entries(paths).map(([ key, val ]): [RegExp, Authorizer] => [ new RegExp(key, 'u'), val ]);
|
||||
this.paths = new Map(entries);
|
||||
}
|
||||
|
||||
public async canHandle(input: AuthorizerArgs): Promise<void> {
|
||||
const authorizer = this.findAuthorizer(input.identifier.path);
|
||||
await authorizer.canHandle(input);
|
||||
}
|
||||
|
||||
public async handle(input: AuthorizerArgs): Promise<Authorization> {
|
||||
const authorizer = this.findAuthorizer(input.identifier.path);
|
||||
return authorizer.handle(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the authorizer corresponding to the given path.
|
||||
* Errors if there is no match.
|
||||
*/
|
||||
private findAuthorizer(path: string): Authorizer {
|
||||
if (path.startsWith(this.baseUrl)) {
|
||||
// We want to keep the leading slash
|
||||
const relative = path.slice(trimTrailingSlashes(this.baseUrl).length);
|
||||
for (const [ regex, authorizer ] of this.paths) {
|
||||
if (regex.test(relative)) {
|
||||
return authorizer;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NotImplementedHttpError('No regex matches the given path.');
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ export * from './authorization/Authorization';
|
||||
export * from './authorization/Authorizer';
|
||||
export * from './authorization/AuxiliaryAuthorizer';
|
||||
export * from './authorization/DenyAllAuthorizer';
|
||||
export * from './authorization/PathBasedAuthorizer';
|
||||
export * from './authorization/WebAclAuthorization';
|
||||
export * from './authorization/WebAclAuthorizer';
|
||||
|
||||
|
51
test/unit/authorization/PathBasedAuthorizer.test.ts
Normal file
51
test/unit/authorization/PathBasedAuthorizer.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { Authorizer, AuthorizerArgs } from '../../../src/authorization/Authorizer';
|
||||
import { PathBasedAuthorizer } from '../../../src/authorization/PathBasedAuthorizer';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
describe('A PathBasedAuthorizer', (): void => {
|
||||
const baseUrl = 'http://test.com/foo/';
|
||||
let input: AuthorizerArgs;
|
||||
let authorizers: jest.Mocked<Authorizer>[];
|
||||
let authorizer: PathBasedAuthorizer;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
input = {
|
||||
identifier: { path: `${baseUrl}first` },
|
||||
permissions: { read: true, append: false, write: false, control: false },
|
||||
credentials: { webId: 'http://alice.test.com/card#me' },
|
||||
};
|
||||
|
||||
authorizers = [
|
||||
{ canHandle: jest.fn(), handle: jest.fn() },
|
||||
{ canHandle: jest.fn(), handle: jest.fn() },
|
||||
] as any;
|
||||
const paths = {
|
||||
'/first': authorizers[0],
|
||||
'/second': authorizers[1],
|
||||
};
|
||||
authorizer = new PathBasedAuthorizer(baseUrl, paths);
|
||||
});
|
||||
|
||||
it('can only handle requests with a matching path.', async(): Promise<void> => {
|
||||
input.identifier.path = 'http://wrongsite/';
|
||||
await expect(authorizer.canHandle(input)).rejects.toThrow(NotImplementedHttpError);
|
||||
input.identifier.path = `${baseUrl}third`;
|
||||
await expect(authorizer.canHandle(input)).rejects.toThrow(NotImplementedHttpError);
|
||||
input.identifier.path = `${baseUrl}first`;
|
||||
await expect(authorizer.canHandle(input)).resolves.toBeUndefined();
|
||||
input.identifier.path = `${baseUrl}second`;
|
||||
await expect(authorizer.canHandle(input)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('can only handle requests supported by the stored authorizers.', async(): Promise<void> => {
|
||||
await expect(authorizer.canHandle(input)).resolves.toBeUndefined();
|
||||
authorizers[0].canHandle.mockRejectedValueOnce(new Error('not supported'));
|
||||
await expect(authorizer.canHandle(input)).rejects.toThrow('not supported');
|
||||
});
|
||||
|
||||
it('passes the handle requests to the matching authorizer.', async(): Promise<void> => {
|
||||
await expect(authorizer.handle(input)).resolves.toBeUndefined();
|
||||
expect(authorizers[0].handle).toHaveBeenCalledTimes(1);
|
||||
expect(authorizers[0].handle).toHaveBeenLastCalledWith(input);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user