mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Rename WebIdAdapterFactory to ClientIdAdapterFactory
This commit is contained in:
parent
7a44581406
commit
607c04ff28
@ -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.
|
- All classes related to setup have been removed.
|
||||||
- The `StaticAssetHandler` has bene updated to support the new functionality.
|
- The `StaticAssetHandler` has bene updated to support the new functionality.
|
||||||
- `SeededPodInitializer` has been renamed to `SeededAccountInitializer`.
|
- `SeededPodInitializer` has been renamed to `SeededAccountInitializer`.
|
||||||
|
- `WebIdAdapterFactory` has been renamed to `ClientIdAdapterFactory`.
|
||||||
|
|
||||||
## v6.1.0
|
## v6.1.0
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"webIdStore": { "@id": "urn:solid-server:default:WebIdStore" },
|
"webIdStore": { "@id": "urn:solid-server:default:WebIdStore" },
|
||||||
"clientCredentialsStore": { "@id": "urn:solid-server:default:ClientCredentialsStore" },
|
"clientCredentialsStore": { "@id": "urn:solid-server:default:ClientCredentialsStore" },
|
||||||
"source": {
|
"source": {
|
||||||
"@type": "WebIdAdapterFactory",
|
"@type": "ClientIdAdapterFactory",
|
||||||
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
||||||
"source": {
|
"source": {
|
||||||
"@type": "ExpiringAdapterFactory",
|
"@type": "ExpiringAdapterFactory",
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||||
"import": [
|
"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/jwks/default.json",
|
||||||
"css:config/identity/handler/provider-factory/identity.json",
|
"css:config/identity/handler/provider-factory/identity.json",
|
||||||
"css:config/identity/handler/storage/default.json"
|
"css:config/identity/handler/storage/default.json"
|
||||||
|
@ -11,16 +11,15 @@ import { OIDC } from '../../util/Vocabularies';
|
|||||||
import type { AdapterFactory } from './AdapterFactory';
|
import type { AdapterFactory } from './AdapterFactory';
|
||||||
import { PassthroughAdapter, PassthroughAdapterFactory } from './PassthroughAdapterFactory';
|
import { PassthroughAdapter, PassthroughAdapterFactory } from './PassthroughAdapterFactory';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This {@link Adapter} redirects the `find` call to its source adapter.
|
* 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,
|
* 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 WebId.
|
* this class will do an HTTP GET request to that Client ID.
|
||||||
* If a valid `solid:oidcRegistration` triple is found there,
|
* If the result is a valid Client ID document, that will be returned instead.
|
||||||
* that data 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);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
private readonly converter: RepresentationConverter;
|
private readonly converter: RepresentationConverter;
|
||||||
@ -39,7 +38,6 @@ export class WebIdAdapter extends PassthroughAdapter {
|
|||||||
// so no extra checks are needed from our side.
|
// so no extra checks are needed from our side.
|
||||||
if (!payload && this.name === 'Client' && hasScheme(id, 'http', 'https')) {
|
if (!payload && this.name === 'Client' && hasScheme(id, 'http', 'https')) {
|
||||||
this.logger.debug(`Looking for payload data at ${id}`);
|
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)) {
|
if (!/^https:|^http:\/\/localhost(?::\d+)?(?:\/|$)/u.test(id)) {
|
||||||
throw new Error(`SSL is required for client_id authentication unless working locally.`);
|
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) {
|
} catch (error: unknown) {
|
||||||
json = undefined;
|
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) {
|
if (json) {
|
||||||
// Need to make sure the document is about the id
|
// Need to make sure the document is about the id
|
||||||
if (json.client_id !== 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;
|
payload = json;
|
||||||
} else {
|
} else {
|
||||||
// Since the WebID does not match the default JSON-LD we try to interpret it as RDF
|
// Since the client ID does not match the default JSON-LD we try to interpret it as RDF
|
||||||
payload = await this.parseRdfWebId(data, id, response);
|
payload = await this.parseRdfClientId(data, id, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `token_endpoint_auth_method: 'none'` prevents oidc-provider from requiring a client_secret
|
// `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' };
|
payload = { ...payload, token_endpoint_auth_method: 'none' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,12 +80,12 @@ export class WebIdAdapter extends PassthroughAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses RDF data found at a client WebID.
|
* Parses RDF data found at a Client ID.
|
||||||
* @param data - Raw data from the WebID.
|
* @param data - Raw data from the Client ID.
|
||||||
* @param id - The actual WebID.
|
* @param id - The actual Client ID.
|
||||||
* @param response - Response object from the request.
|
* @param response - Response object from the request.
|
||||||
*/
|
*/
|
||||||
private async parseRdfWebId(data: string, id: string, response: Response): Promise<AdapterPayload> {
|
private async parseRdfClientId(data: string, id: string, response: Response): Promise<AdapterPayload> {
|
||||||
const representation = await responseToDataset(response, this.converter, data);
|
const representation = await responseToDataset(response, this.converter, data);
|
||||||
|
|
||||||
// Find the valid redirect URIs
|
// Find the valid redirect URIs
|
||||||
@ -98,14 +97,16 @@ export class WebIdAdapter extends PassthroughAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
return {
|
return {
|
||||||
client_id: id,
|
client_id: id,
|
||||||
redirect_uris: redirectUris,
|
redirect_uris: redirectUris,
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebIdAdapterFactory extends PassthroughAdapterFactory {
|
export class ClientIdAdapterFactory extends PassthroughAdapterFactory {
|
||||||
private readonly converter: RepresentationConverter;
|
private readonly converter: RepresentationConverter;
|
||||||
|
|
||||||
public constructor(source: AdapterFactory, converter: RepresentationConverter) {
|
public constructor(source: AdapterFactory, converter: RepresentationConverter) {
|
||||||
@ -114,6 +115,6 @@ export class WebIdAdapterFactory extends PassthroughAdapterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public createStorageAdapter(name: string): Adapter {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -253,9 +253,9 @@ export * from './identity/ownership/TokenOwnershipValidator';
|
|||||||
|
|
||||||
// Identity/Storage
|
// Identity/Storage
|
||||||
export * from './identity/storage/AdapterFactory';
|
export * from './identity/storage/AdapterFactory';
|
||||||
|
export * from './identity/storage/ClientIdAdapterFactory';
|
||||||
export * from './identity/storage/ExpiringAdapterFactory';
|
export * from './identity/storage/ExpiringAdapterFactory';
|
||||||
export * from './identity/storage/PassthroughAdapterFactory';
|
export * from './identity/storage/PassthroughAdapterFactory';
|
||||||
export * from './identity/storage/WebIdAdapterFactory';
|
|
||||||
|
|
||||||
// Identity
|
// Identity
|
||||||
export * from './identity/AccountInitializer';
|
export * from './identity/AccountInitializer';
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import type { AdapterFactory } from '../../../../src/identity/storage/AdapterFactory';
|
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 { RdfToQuadConverter } from '../../../../src/storage/conversion/RdfToQuadConverter';
|
||||||
import type { Adapter } from '../../../../templates/types/oidc-provider';
|
import type { Adapter } from '../../../../templates/types/oidc-provider';
|
||||||
|
|
||||||
jest.mock('cross-fetch');
|
jest.mock('cross-fetch');
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
describe('A WebIdAdapterFactory', (): void => {
|
describe('A ClientIdAdapterFactory', (): void => {
|
||||||
const fetchMock: jest.Mock = fetch as any;
|
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 json: any;
|
||||||
let rdf: string;
|
let rdf: string;
|
||||||
let source: Adapter;
|
let source: Adapter;
|
||||||
let sourceFactory: AdapterFactory;
|
let sourceFactory: AdapterFactory;
|
||||||
let adapter: Adapter;
|
let adapter: Adapter;
|
||||||
const converter = new RdfToQuadConverter();
|
const converter = new RdfToQuadConverter();
|
||||||
let factory: WebIdAdapterFactory;
|
let factory: ClientIdAdapterFactory;
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
json = {
|
json = {
|
||||||
@ -24,14 +24,14 @@ describe('A WebIdAdapterFactory', (): void => {
|
|||||||
|
|
||||||
client_id: id,
|
client_id: id,
|
||||||
client_name: 'Solid Application Name',
|
client_name: 'Solid Application Name',
|
||||||
redirect_uris: [ 'http://test.com/' ],
|
redirect_uris: [ 'http://example.com/' ],
|
||||||
scope: 'openid profile offline_access',
|
scope: 'openid profile offline_access',
|
||||||
grant_types: [ 'refresh_token', 'authorization_code' ],
|
grant_types: [ 'refresh_token', 'authorization_code' ],
|
||||||
response_types: [ 'code' ],
|
response_types: [ 'code' ],
|
||||||
default_max_age: 3600,
|
default_max_age: 3600,
|
||||||
require_auth_time: true,
|
require_auth_time: true,
|
||||||
};
|
};
|
||||||
rdf = `<${id}> <http://www.w3.org/ns/solid/oidc#redirect_uris> <http://test.com>.`;
|
rdf = `<${id}> <http://www.w3.org/ns/solid/oidc#redirect_uris> <http://example.com>.`;
|
||||||
|
|
||||||
fetchMock.mockImplementation((url: string): any => ({ text: (): any => '', url, status: 200 }));
|
fetchMock.mockImplementation((url: string): any => ({ text: (): any => '', url, status: 200 }));
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ describe('A WebIdAdapterFactory', (): void => {
|
|||||||
createStorageAdapter: jest.fn().mockReturnValue(source),
|
createStorageAdapter: jest.fn().mockReturnValue(source),
|
||||||
};
|
};
|
||||||
|
|
||||||
factory = new WebIdAdapterFactory(sourceFactory, converter);
|
factory = new ClientIdAdapterFactory(sourceFactory, converter);
|
||||||
adapter = factory.createStorageAdapter('Client');
|
adapter = factory.createStorageAdapter('Client');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ describe('A WebIdAdapterFactory', (): void => {
|
|||||||
json.client_id = 'someone else';
|
json.client_id = 'someone else';
|
||||||
fetchMock.mockResolvedValueOnce({ url: id, status: 200, text: (): string => JSON.stringify(json) });
|
fetchMock.mockResolvedValueOnce({ url: id, status: 200, text: (): string => JSON.stringify(json) });
|
||||||
await expect(adapter.find(id)).rejects
|
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<void> => {
|
it('can handle a valid RDF response.', async(): Promise<void> => {
|
||||||
@ -107,15 +107,15 @@ describe('A WebIdAdapterFactory', (): void => {
|
|||||||
);
|
);
|
||||||
await expect(adapter.find(id)).resolves.toEqual({
|
await expect(adapter.find(id)).resolves.toEqual({
|
||||||
client_id: id,
|
client_id: id,
|
||||||
redirect_uris: [ 'http://test.com' ],
|
redirect_uris: [ 'http://example.com' ],
|
||||||
token_endpoint_auth_method: 'none',
|
token_endpoint_auth_method: 'none',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('falls back to RDF parsing if no valid context was found.', async(): Promise<void> => {
|
it('falls back to RDF parsing if no valid context was found.', async(): Promise<void> => {
|
||||||
json = {
|
json = {
|
||||||
'@id': 'https://app.test.com/card#me',
|
'@id': 'https://app.example.com/card#me',
|
||||||
'http://www.w3.org/ns/solid/oidc#redirect_uris': { '@id': 'http://test.com' },
|
'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' },
|
'http://randomField': { '@value': 'this will not be there since RDF parsing only takes preset fields' },
|
||||||
};
|
};
|
||||||
fetchMock.mockResolvedValueOnce(
|
fetchMock.mockResolvedValueOnce(
|
||||||
@ -126,7 +126,7 @@ describe('A WebIdAdapterFactory', (): void => {
|
|||||||
);
|
);
|
||||||
await expect(adapter.find(id)).resolves.toEqual({
|
await expect(adapter.find(id)).resolves.toEqual({
|
||||||
client_id: id,
|
client_id: id,
|
||||||
redirect_uris: [ 'http://test.com' ],
|
redirect_uris: [ 'http://example.com' ],
|
||||||
token_endpoint_auth_method: 'none',
|
token_endpoint_auth_method: 'none',
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
x
Reference in New Issue
Block a user