feat!: remove caching from AgentGroupAccessChecker

This commit is contained in:
Jasper Vaneessen 2022-08-09 11:15:37 +02:00 committed by Joachim Van Herwegen
parent 4d1bd93e94
commit 3c43d046ef
3 changed files with 10 additions and 54 deletions

View File

@ -4,23 +4,7 @@
{
"comment": "Checks if the agent belongs to a group that has access.",
"@id": "urn:solid-server:default:AgentGroupAccessChecker",
"@type": "AgentGroupAccessChecker",
"cache": {
"@id": "urn:solid-server:default:ExpiringAclCache",
"@type": "WrappedExpiringStorage",
"source": { "@type": "MemoryMapStorage" }
}
},
{
"comment": "Makes sure the expiring storage cleanup timer is stopped when the application needs to stop.",
"@id": "urn:solid-server:default:Finalizer",
"@type": "ParallelHandler",
"handlers": [
{
"@type": "FinalizableHandler",
"finalizable": { "@id": "urn:solid-server:default:ExpiringAclCache" }
}
]
"@type": "AgentGroupAccessChecker"
}
]
}

View File

@ -1,6 +1,5 @@
import type { Store, Term } from 'n3';
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import type { ExpiringStorage } from '../../storage/keyvalue/ExpiringStorage';
import { fetchDataset } from '../../util/FetchUtil';
import { promiseSome } from '../../util/PromiseUtil';
import { readableToQuads } from '../../util/StreamUtil';
@ -11,20 +10,10 @@ import { AccessChecker } from './AccessChecker';
/**
* Checks if the given WebID belongs to a group that has access.
* Implements the behaviour of groups from the WAC specification.
*
* Fetched results will be stored in an ExpiringStorage.
*
* Requires a storage that can store JS objects.
* `expiration` parameter is how long entries in the cache should be stored in seconds, defaults to 3600.
*/
export class AgentGroupAccessChecker extends AccessChecker {
private readonly cache: ExpiringStorage<string, Promise<Store>>;
private readonly expiration: number;
public constructor(cache: ExpiringStorage<string, Promise<Store>>, expiration = 3600) {
public constructor() {
super();
this.cache = cache;
this.expiration = expiration * 1000;
}
public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise<boolean> {
@ -49,24 +38,18 @@ export class AgentGroupAccessChecker extends AccessChecker {
const groupDocument: ResourceIdentifier = { path: /^[^#]*/u.exec(group.value)![0] };
// Fetch the required vCard group file
const quads = await this.fetchCachedQuads(groupDocument.path);
const quads = await this.fetchQuads(groupDocument.path);
return quads.countQuads(group, VCARD.terms.hasMember, webId, null) !== 0;
}
/**
* Fetches quads from the given URL.
* Will cache the values for later re-use.
*/
private async fetchCachedQuads(url: string): Promise<Store> {
let result = await this.cache.get(url);
if (!result) {
private async fetchQuads(url: string): Promise<Store> {
const prom = (async(): Promise<Store> => {
const representation = await fetchDataset(url);
return readableToQuads(representation.data);
})();
await this.cache.set(url, prom, this.expiration);
result = await prom;
}
return result;
return await prom;
}
}

View File

@ -3,7 +3,6 @@ import type { AccessCheckerArgs } from '../../../../src/authorization/access/Acc
import { AgentGroupAccessChecker } from '../../../../src/authorization/access/AgentGroupAccessChecker';
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
import type { Representation } from '../../../../src/http/representation/Representation';
import type { ExpiringStorage } from '../../../../src/storage/keyvalue/ExpiringStorage';
import { INTERNAL_QUADS } from '../../../../src/util/ContentTypes';
import * as fetchUtil from '../../../../src/util/FetchUtil';
import { ACL, VCARD } from '../../../../src/util/Vocabularies';
@ -17,7 +16,6 @@ describe('An AgentGroupAccessChecker', (): void => {
acl.addQuad(namedNode('noMatch'), ACL.terms.agentGroup, namedNode('badGroup'));
let fetchMock: jest.SpyInstance;
let representation: Representation;
let cache: ExpiringStorage<string, Promise<Store>>;
let checker: AgentGroupAccessChecker;
beforeEach(async(): Promise<void> => {
@ -27,9 +25,7 @@ describe('An AgentGroupAccessChecker', (): void => {
fetchMock.mockResolvedValue(representation);
fetchMock.mockClear();
cache = new Map() as any;
checker = new AgentGroupAccessChecker(cache);
checker = new AgentGroupAccessChecker();
});
it('can handle all requests.', async(): Promise<void> => {
@ -50,11 +46,4 @@ describe('An AgentGroupAccessChecker', (): void => {
const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credential: {}};
await expect(checker.handle(input)).resolves.toBe(false);
});
it('caches fetched results.', async(): Promise<void> => {
const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credential: { webId }};
await expect(checker.handle(input)).resolves.toBe(true);
await expect(checker.handle(input)).resolves.toBe(true);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});