Skip to content

Commit 7e7a6c7

Browse files
chore: merge part of resource registrar in uma client (#34)
Signed-off-by: Wouter Termont <wouter.termont@ugent.be>
1 parent a90d9be commit 7e7a6c7

File tree

3 files changed

+96
-89
lines changed

3 files changed

+96
-89
lines changed

packages/css/config/uma/default.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
"baseUrl": {
3939
"@id": "urn:solid-server:default:variable:baseUrl"
4040
},
41+
"umaIdStore": {
42+
"@id": "urn:solid-server:default:UmaIdStore",
43+
"@type": "MemoryMapStorage"
44+
},
4145
"keyGen": {
4246
"@id": "urn:solid-server:default:JwkGenerator"
4347
},
@@ -73,10 +77,6 @@
7377
"store": {
7478
"@id": "urn:solid-server:default:ResourceStore"
7579
},
76-
"umaIdStore": {
77-
"@id": "urn:solid-server:default:UmaIdStore",
78-
"@type": "MemoryMapStorage"
79-
},
8080
"ownerUtil": {
8181
"@id": "urn:solid-server:default:OwnerUtil"
8282
},
Lines changed: 11 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,38 @@
1-
import type { ResourceIdentifier, MonitoringStore, KeyValueStorage } from '@solid/community-server';
1+
import type { ResourceIdentifier, MonitoringStore } from '@solid/community-server';
22
import { AS, getLoggerFor, StaticHandler } from '@solid/community-server';
33
import { OwnerUtil } from '../util/OwnerUtil';
4-
import { ResourceDescription } from '@solidlab/uma';
54
import type { UmaClient } from '../uma/UmaClient';
65

76
export class ResourceRegistrar extends StaticHandler {
87
protected readonly logger = getLoggerFor(this);
98

109
public constructor(
1110
protected store: MonitoringStore,
12-
protected umaIdStore: KeyValueStorage<string, string>,
1311
protected ownerUtil: OwnerUtil,
1412
protected umaClient: UmaClient,
1513
) {
1614
super();
1715

1816
store.on(AS.Create, async (resource: ResourceIdentifier): Promise<void> => {
19-
const owners = await this.ownerUtil.findOwners(resource).catch(() => []);
20-
for (const owner of owners) this.createResource(resource, owner);
17+
for (const owner of await this.findOwners(resource)) {
18+
this.umaClient.createResource(resource, await this.findIssuer(owner));
19+
}
2120
});
2221

2322
store.on(AS.Delete, async (resource: ResourceIdentifier): Promise<void> => {
24-
const owners = await this.ownerUtil.findOwners(resource).catch(() => []);
25-
for (const owner of owners) this.deleteResource(resource, owner);
23+
for (const owner of await this.findOwners(resource)) {
24+
this.umaClient.deleteResource(resource, await this.findIssuer(owner));
25+
}
2626
});
2727
}
2828

29-
protected async createResource(resource: ResourceIdentifier, owner: string): Promise<void> {
30-
const issuer = await this.ownerUtil.findIssuer(owner);
31-
32-
if (!issuer) throw new Error(`Could not find UMA AS for resource owner ${owner}`);
33-
34-
const { resource_registration_endpoint: endpoint } = await this.umaClient.fetchUmaConfig(issuer);
35-
36-
const description: ResourceDescription = {
37-
resource_scopes: [
38-
'urn:example:css:modes:read',
39-
'urn:example:css:modes:append',
40-
'urn:example:css:modes:create',
41-
'urn:example:css:modes:delete',
42-
'urn:example:css:modes:write',
43-
]
44-
};
45-
46-
this.logger.info(`Creating resource registration for <${resource.path}> at <${endpoint}>`);
47-
48-
const request = {
49-
url: endpoint,
50-
method: 'POST',
51-
headers: {
52-
'Content-Type': 'application/json',
53-
'Accept': 'application/json',
54-
},
55-
body: JSON.stringify(description),
56-
};
57-
58-
// do not await - registration happens in background to cope with errors etc.
59-
this.umaClient.signedFetch(endpoint, request).then(async resp => {
60-
if (resp.status !== 201) {
61-
throw new Error (`Resource registration request failed. ${await resp.text()}`);
62-
}
63-
64-
const { _id: umaId } = await resp.json();
65-
66-
if (!umaId || typeof umaId !== 'string') {
67-
throw new Error ('Unexpected response from UMA server; no UMA id received.');
68-
}
69-
70-
this.umaIdStore.set(resource.path, umaId);
71-
}).catch(error => {
72-
// TODO: Do something useful on error
73-
this.logger.warn(
74-
`Something went wrong during UMA resource registration to create ${resource.path}: ${(error as Error).message}`
75-
);
76-
});
29+
private async findOwners(resource: ResourceIdentifier): Promise<string[]> {
30+
return await this.ownerUtil.findOwners(resource).catch(() => []);
7731
}
7832

79-
protected async deleteResource(resource: ResourceIdentifier, owner: string): Promise<void> {
33+
private async findIssuer(owner: string): Promise<string> {
8034
const issuer = await this.ownerUtil.findIssuer(owner);
81-
8235
if (!issuer) throw new Error(`Could not find UMA AS for resource owner ${owner}`);
83-
84-
const { resource_registration_endpoint: endpoint } = await this.umaClient.fetchUmaConfig(issuer);
85-
86-
this.logger.info(`Deleting resource registration for <${resource.path}> at <${endpoint}>`);
87-
88-
const umaId = await this.umaIdStore.get(resource.path);
89-
const url = `${endpoint}/${umaId}`;
90-
91-
const request = {
92-
url,
93-
method: 'DELETE',
94-
headers: {}
95-
};
96-
97-
// do not await - registration happens in background to cope with errors etc.
98-
this.umaClient.signedFetch(endpoint, request).then(async _resp => {
99-
if (!umaId) throw new Error('Trying to delete unknown/unregistered resource; no UMA id found.');
100-
101-
await this.umaClient.signedFetch(url, request);
102-
}).catch(error => {
103-
// TODO: Do something useful on error
104-
this.logger.warn(
105-
`Something went wrong during UMA resource registration to delete ${resource.path}: ${(error as Error).message}`
106-
);
107-
});
36+
return issuer;
10837
}
10938
}

packages/css/src/uma/UmaClient.ts

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { AccessMap, getLoggerFor, InternalServerError, JwkGenerator } from "@solid/community-server";
1+
import type { KeyValueStorage, Representation, ResourceIdentifier } from "@solid/community-server";
2+
import { AccessMap, getLoggerFor, InternalServerError, JwkGenerator, NotFoundHttpError } from "@solid/community-server";
23
import { JWTPayload, decodeJwt, createRemoteJWKSet, jwtVerify, JWTVerifyOptions } from "jose";
34
import { httpbis, type SigningKey, type Request as SignRequest } from 'http-message-signatures';
45
import { isString } from '../util/StringGuard';
56
import fetch from 'cross-fetch';
67
import type { Fetcher } from "../util/fetch/Fetcher";
78
import crypto from 'node:crypto';
9+
import type { ResourceDescription } from "@solidlab/uma";
810

911
export interface Claims {
1012
[key: string]: unknown;
@@ -70,8 +72,9 @@ export class UmaClient {
7072
*/
7173
constructor(
7274
protected baseUrl: string,
73-
protected keyGen: JwkGenerator,
7475
protected fetcher: Fetcher,
76+
protected keyGen: JwkGenerator,
77+
protected umaIdStore: KeyValueStorage<string, string>,
7578
protected options: UmaVerificationOptions = {},
7679
) {}
7780

@@ -118,8 +121,10 @@ export class UmaClient {
118121

119122
const body = [];
120123
for (const [ target, modes ] of permissions.entrySets()) {
124+
// const umaId = await this.umaIdStore.get(target.path);
125+
// if (!umaId) throw new NotFoundHttpError();
121126
body.push({
122-
resource_id: target.path,
127+
resource_id: target.path, // TODO: map to umaId ? (but raises problems on creation, discovery ...)
123128
resource_scopes: Array.from(modes).map(mode => `urn:example:css:modes:${mode}`)
124129
});
125130
}
@@ -259,4 +264,77 @@ export class UmaClient {
259264

260265
return configuration;
261266
}
267+
268+
public async createResource(resource: ResourceIdentifier, issuer: string): Promise<void> {
269+
const { resource_registration_endpoint: endpoint } = await this.fetchUmaConfig(issuer);
270+
271+
const description: ResourceDescription = {
272+
resource_scopes: [
273+
'urn:example:css:modes:read',
274+
'urn:example:css:modes:append',
275+
'urn:example:css:modes:create',
276+
'urn:example:css:modes:delete',
277+
'urn:example:css:modes:write',
278+
]
279+
};
280+
281+
this.logger.info(`Creating resource registration for <${resource.path}> at <${endpoint}>`);
282+
283+
const request = {
284+
url: endpoint,
285+
method: 'POST',
286+
headers: {
287+
'Content-Type': 'application/json',
288+
'Accept': 'application/json',
289+
},
290+
body: JSON.stringify(description),
291+
};
292+
293+
// do not await - registration happens in background to cope with errors etc.
294+
this.signedFetch(endpoint, request).then(async resp => {
295+
if (resp.status !== 201) {
296+
throw new Error (`Resource registration request failed. ${await resp.text()}`);
297+
}
298+
299+
const { _id: umaId } = await resp.json();
300+
301+
if (!umaId || typeof umaId !== 'string') {
302+
throw new Error ('Unexpected response from UMA server; no UMA id received.');
303+
}
304+
305+
this.umaIdStore.set(resource.path, umaId);
306+
}).catch(error => {
307+
// TODO: Do something useful on error
308+
this.logger.warn(
309+
`Something went wrong during UMA resource registration to create ${resource.path}: ${(error as Error).message}`
310+
);
311+
});
312+
}
313+
314+
public async deleteResource(resource: ResourceIdentifier, issuer: string): Promise<void> {
315+
const { resource_registration_endpoint: endpoint } = await this.fetchUmaConfig(issuer);
316+
317+
this.logger.info(`Deleting resource registration for <${resource.path}> at <${endpoint}>`);
318+
319+
const umaId = await this.umaIdStore.get(resource.path);
320+
const url = `${endpoint}/${umaId}`;
321+
322+
const request = {
323+
url,
324+
method: 'DELETE',
325+
headers: {}
326+
};
327+
328+
// do not await - registration happens in background to cope with errors etc.
329+
this.signedFetch(endpoint, request).then(async _resp => {
330+
if (!umaId) throw new Error('Trying to delete unknown/unregistered resource; no UMA id found.');
331+
332+
await this.signedFetch(url, request);
333+
}).catch(error => {
334+
// TODO: Do something useful on error
335+
this.logger.warn(
336+
`Something went wrong during UMA resource registration to delete ${resource.path}: ${(error as Error).message}`
337+
);
338+
});
339+
}
262340
}

0 commit comments

Comments
 (0)