|
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"; |
2 | 3 | import { JWTPayload, decodeJwt, createRemoteJWKSet, jwtVerify, JWTVerifyOptions } from "jose"; |
3 | 4 | import { httpbis, type SigningKey, type Request as SignRequest } from 'http-message-signatures'; |
4 | 5 | import { isString } from '../util/StringGuard'; |
5 | 6 | import fetch from 'cross-fetch'; |
6 | 7 | import type { Fetcher } from "../util/fetch/Fetcher"; |
7 | 8 | import crypto from 'node:crypto'; |
| 9 | +import type { ResourceDescription } from "@solidlab/uma"; |
8 | 10 |
|
9 | 11 | export interface Claims { |
10 | 12 | [key: string]: unknown; |
@@ -70,8 +72,9 @@ export class UmaClient { |
70 | 72 | */ |
71 | 73 | constructor( |
72 | 74 | protected baseUrl: string, |
73 | | - protected keyGen: JwkGenerator, |
74 | 75 | protected fetcher: Fetcher, |
| 76 | + protected keyGen: JwkGenerator, |
| 77 | + protected umaIdStore: KeyValueStorage<string, string>, |
75 | 78 | protected options: UmaVerificationOptions = {}, |
76 | 79 | ) {} |
77 | 80 |
|
@@ -118,8 +121,10 @@ export class UmaClient { |
118 | 121 |
|
119 | 122 | const body = []; |
120 | 123 | for (const [ target, modes ] of permissions.entrySets()) { |
| 124 | + // const umaId = await this.umaIdStore.get(target.path); |
| 125 | + // if (!umaId) throw new NotFoundHttpError(); |
121 | 126 | body.push({ |
122 | | - resource_id: target.path, |
| 127 | + resource_id: target.path, // TODO: map to umaId ? (but raises problems on creation, discovery ...) |
123 | 128 | resource_scopes: Array.from(modes).map(mode => `urn:example:css:modes:${mode}`) |
124 | 129 | }); |
125 | 130 | } |
@@ -259,4 +264,77 @@ export class UmaClient { |
259 | 264 |
|
260 | 265 | return configuration; |
261 | 266 | } |
| 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 | + } |
262 | 340 | } |
0 commit comments