From 607c04ff286a9842e12c750fcdf87b903a838f8a Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 11 Oct 2023 13:01:36 +0200 Subject: [PATCH] refactor: Rename WebIdAdapterFactory to ClientIdAdapterFactory --- RELEASE_NOTES.md | 1 + .../{webid.json => default.json} | 2 +- config/identity/handler/base/default.json | 2 +- ...erFactory.ts => ClientIdAdapterFactory.ts} | 37 ++++++++++--------- src/index.ts | 2 +- ...test.ts => ClientIdAdapterFactory.test.ts} | 24 ++++++------ 6 files changed, 35 insertions(+), 33 deletions(-) rename config/identity/handler/adapter-factory/{webid.json => default.json} (95%) rename src/identity/storage/{WebIdAdapterFactory.ts => ClientIdAdapterFactory.ts} (76%) rename test/unit/identity/storage/{WebIdAdapterFactory.test.ts => ClientIdAdapterFactory.test.ts} (89%) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 382a5dc48..db8033302 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -85,6 +85,7 @@ These changes are relevant if you wrote custom modules for the server that depen - All classes related to setup have been removed. - The `StaticAssetHandler` has bene updated to support the new functionality. - `SeededPodInitializer` has been renamed to `SeededAccountInitializer`. +- `WebIdAdapterFactory` has been renamed to `ClientIdAdapterFactory`. ## v6.1.0 diff --git a/config/identity/handler/adapter-factory/webid.json b/config/identity/handler/adapter-factory/default.json similarity index 95% rename from config/identity/handler/adapter-factory/webid.json rename to config/identity/handler/adapter-factory/default.json index 03dfe2402..1d96a1cdf 100644 --- a/config/identity/handler/adapter-factory/webid.json +++ b/config/identity/handler/adapter-factory/default.json @@ -8,7 +8,7 @@ "webIdStore": { "@id": "urn:solid-server:default:WebIdStore" }, "clientCredentialsStore": { "@id": "urn:solid-server:default:ClientCredentialsStore" }, "source": { - "@type": "WebIdAdapterFactory", + "@type": "ClientIdAdapterFactory", "converter": { "@id": "urn:solid-server:default:RepresentationConverter" }, "source": { "@type": "ExpiringAdapterFactory", diff --git a/config/identity/handler/base/default.json b/config/identity/handler/base/default.json index d7a6b89fa..d11f036e4 100644 --- a/config/identity/handler/base/default.json +++ b/config/identity/handler/base/default.json @@ -1,7 +1,7 @@ { "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ - "css:config/identity/handler/adapter-factory/webid.json", + "css:config/identity/handler/adapter-factory/default.json", "css:config/identity/handler/jwks/default.json", "css:config/identity/handler/provider-factory/identity.json", "css:config/identity/handler/storage/default.json" diff --git a/src/identity/storage/WebIdAdapterFactory.ts b/src/identity/storage/ClientIdAdapterFactory.ts similarity index 76% rename from src/identity/storage/WebIdAdapterFactory.ts rename to src/identity/storage/ClientIdAdapterFactory.ts index 722a22bb9..f9eb62905 100644 --- a/src/identity/storage/WebIdAdapterFactory.ts +++ b/src/identity/storage/ClientIdAdapterFactory.ts @@ -11,16 +11,15 @@ import { OIDC } from '../../util/Vocabularies'; import type { AdapterFactory } from './AdapterFactory'; import { PassthroughAdapter, PassthroughAdapterFactory } from './PassthroughAdapterFactory'; -/* eslint-disable @typescript-eslint/naming-convention */ - /** * This {@link Adapter} redirects the `find` call to its source adapter. - * In case no client data was found in the source for the given WebId, - * this class will do an HTTP GET request to that WebId. - * If a valid `solid:oidcRegistration` triple is found there, - * that data will be returned instead. + * In case no client data was found in the source for the given Client ID, + * this class will do an HTTP GET request to that Client ID. + * If the result is a valid Client ID document, that will be returned instead. + * + * See https://solidproject.org/TR/2022/oidc-20220328#clientids-document. */ -export class WebIdAdapter extends PassthroughAdapter { +export class ClientIdAdapter extends PassthroughAdapter { protected readonly logger = getLoggerFor(this); private readonly converter: RepresentationConverter; @@ -39,7 +38,6 @@ export class WebIdAdapter extends PassthroughAdapter { // so no extra checks are needed from our side. if (!payload && this.name === 'Client' && hasScheme(id, 'http', 'https')) { this.logger.debug(`Looking for payload data at ${id}`); - // All checks based on https://solid.github.io/authentication-panel/solid-oidc/#clientids-webid if (!/^https:|^http:\/\/localhost(?::\d+)?(?:\/|$)/u.test(id)) { throw new Error(`SSL is required for client_id authentication unless working locally.`); } @@ -58,21 +56,22 @@ export class WebIdAdapter extends PassthroughAdapter { } } catch (error: unknown) { json = undefined; - this.logger.debug(`Found unexpected client WebID for ${id}: ${createErrorMessage(error)}`); + this.logger.debug(`Found unexpected client ID for ${id}: ${createErrorMessage(error)}`); } if (json) { // Need to make sure the document is about the id if (json.client_id !== id) { - throw new Error('The client registration `client_id` field must match the client WebID'); + throw new Error('The client registration `client_id` field must match the client ID'); } payload = json; } else { - // Since the WebID does not match the default JSON-LD we try to interpret it as RDF - payload = await this.parseRdfWebId(data, id, response); + // Since the client ID does not match the default JSON-LD we try to interpret it as RDF + payload = await this.parseRdfClientId(data, id, response); } // `token_endpoint_auth_method: 'none'` prevents oidc-provider from requiring a client_secret + // eslint-disable-next-line @typescript-eslint/naming-convention payload = { ...payload, token_endpoint_auth_method: 'none' }; } @@ -81,12 +80,12 @@ export class WebIdAdapter extends PassthroughAdapter { } /** - * Parses RDF data found at a client WebID. - * @param data - Raw data from the WebID. - * @param id - The actual WebID. + * Parses RDF data found at a Client ID. + * @param data - Raw data from the Client ID. + * @param id - The actual Client ID. * @param response - Response object from the request. */ - private async parseRdfWebId(data: string, id: string, response: Response): Promise { + private async parseRdfClientId(data: string, id: string, response: Response): Promise { const representation = await responseToDataset(response, this.converter, data); // Find the valid redirect URIs @@ -98,14 +97,16 @@ export class WebIdAdapter extends PassthroughAdapter { } } + /* eslint-disable @typescript-eslint/naming-convention */ return { client_id: id, redirect_uris: redirectUris, }; + /* eslint-enable @typescript-eslint/naming-convention */ } } -export class WebIdAdapterFactory extends PassthroughAdapterFactory { +export class ClientIdAdapterFactory extends PassthroughAdapterFactory { private readonly converter: RepresentationConverter; public constructor(source: AdapterFactory, converter: RepresentationConverter) { @@ -114,6 +115,6 @@ export class WebIdAdapterFactory extends PassthroughAdapterFactory { } public createStorageAdapter(name: string): Adapter { - return new WebIdAdapter(name, this.source.createStorageAdapter(name), this.converter); + return new ClientIdAdapter(name, this.source.createStorageAdapter(name), this.converter); } } diff --git a/src/index.ts b/src/index.ts index 53317d406..a57c18c4c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -253,9 +253,9 @@ export * from './identity/ownership/TokenOwnershipValidator'; // Identity/Storage export * from './identity/storage/AdapterFactory'; +export * from './identity/storage/ClientIdAdapterFactory'; export * from './identity/storage/ExpiringAdapterFactory'; export * from './identity/storage/PassthroughAdapterFactory'; -export * from './identity/storage/WebIdAdapterFactory'; // Identity export * from './identity/AccountInitializer'; diff --git a/test/unit/identity/storage/WebIdAdapterFactory.test.ts b/test/unit/identity/storage/ClientIdAdapterFactory.test.ts similarity index 89% rename from test/unit/identity/storage/WebIdAdapterFactory.test.ts rename to test/unit/identity/storage/ClientIdAdapterFactory.test.ts index 74eb1d320..dedda97d5 100644 --- a/test/unit/identity/storage/WebIdAdapterFactory.test.ts +++ b/test/unit/identity/storage/ClientIdAdapterFactory.test.ts @@ -1,22 +1,22 @@ import fetch from 'cross-fetch'; import type { AdapterFactory } from '../../../../src/identity/storage/AdapterFactory'; -import { WebIdAdapterFactory } from '../../../../src/identity/storage/WebIdAdapterFactory'; +import { ClientIdAdapterFactory } from '../../../../src/identity/storage/ClientIdAdapterFactory'; import { RdfToQuadConverter } from '../../../../src/storage/conversion/RdfToQuadConverter'; import type { Adapter } from '../../../../templates/types/oidc-provider'; jest.mock('cross-fetch'); /* eslint-disable @typescript-eslint/naming-convention */ -describe('A WebIdAdapterFactory', (): void => { +describe('A ClientIdAdapterFactory', (): void => { const fetchMock: jest.Mock = fetch as any; - const id = 'https://app.test.com/card#me'; + const id = 'https://app.example.com/card#me'; let json: any; let rdf: string; let source: Adapter; let sourceFactory: AdapterFactory; let adapter: Adapter; const converter = new RdfToQuadConverter(); - let factory: WebIdAdapterFactory; + let factory: ClientIdAdapterFactory; beforeEach(async(): Promise => { json = { @@ -24,14 +24,14 @@ describe('A WebIdAdapterFactory', (): void => { client_id: id, client_name: 'Solid Application Name', - redirect_uris: [ 'http://test.com/' ], + redirect_uris: [ 'http://example.com/' ], scope: 'openid profile offline_access', grant_types: [ 'refresh_token', 'authorization_code' ], response_types: [ 'code' ], default_max_age: 3600, require_auth_time: true, }; - rdf = `<${id}> .`; + rdf = `<${id}> .`; fetchMock.mockImplementation((url: string): any => ({ text: (): any => '', url, status: 200 })); @@ -49,7 +49,7 @@ describe('A WebIdAdapterFactory', (): void => { createStorageAdapter: jest.fn().mockReturnValue(source), }; - factory = new WebIdAdapterFactory(sourceFactory, converter); + factory = new ClientIdAdapterFactory(sourceFactory, converter); adapter = factory.createStorageAdapter('Client'); }); @@ -98,7 +98,7 @@ describe('A WebIdAdapterFactory', (): void => { json.client_id = 'someone else'; fetchMock.mockResolvedValueOnce({ url: id, status: 200, text: (): string => JSON.stringify(json) }); await expect(adapter.find(id)).rejects - .toThrow('The client registration `client_id` field must match the client WebID'); + .toThrow('The client registration `client_id` field must match the client ID'); }); it('can handle a valid RDF response.', async(): Promise => { @@ -107,15 +107,15 @@ describe('A WebIdAdapterFactory', (): void => { ); await expect(adapter.find(id)).resolves.toEqual({ client_id: id, - redirect_uris: [ 'http://test.com' ], + redirect_uris: [ 'http://example.com' ], token_endpoint_auth_method: 'none', }); }); it('falls back to RDF parsing if no valid context was found.', async(): Promise => { json = { - '@id': 'https://app.test.com/card#me', - 'http://www.w3.org/ns/solid/oidc#redirect_uris': { '@id': 'http://test.com' }, + '@id': 'https://app.example.com/card#me', + 'http://www.w3.org/ns/solid/oidc#redirect_uris': { '@id': 'http://example.com' }, 'http://randomField': { '@value': 'this will not be there since RDF parsing only takes preset fields' }, }; fetchMock.mockResolvedValueOnce( @@ -126,7 +126,7 @@ describe('A WebIdAdapterFactory', (): void => { ); await expect(adapter.find(id)).resolves.toEqual({ client_id: id, - redirect_uris: [ 'http://test.com' ], + redirect_uris: [ 'http://example.com' ], token_endpoint_auth_method: 'none', }); });