Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sign traffic between RS and AS #31

Merged
merged 4 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This repository contains SolidLab research artefacts on use of UMA in the Solid

In order to run this project you need to perform the following steps.

1. Ensure that you are using Node.js 18.18 or higher, e.g. by running `nvm use`.
1. Ensure that you are using Node.js 20 or higher, e.g. by running `nvm use`. (see [.nvmrc](./.nvmrc))
1. Enable Node.js Corepack with `corepack enable`.
1. Run `yarn install` in the project root (this will automatically call `yarn build:all`).
1. Run `yarn start:all`.
Expand Down
14 changes: 1 addition & 13 deletions packages/css/config/ldp/authentication/uma.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,6 @@
}
]
}
},
{
"comment": "Returns the UMA ticket in case of an unauthorized request.",
"@id": "urn:solid-server:default:UmaClient",
"@type": "UmaClientImpl",
"UmaClientImpl:_args_asUrl": "http://localhost:4000/uma",
"UmaClientImpl:_args_credentials_ecAlgorithm": "ES256",
"UmaClientImpl:_args_credentials_ecPrivateKey": "-----BEGIN PRIVATE KEY-----MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg/cteLEDr0AH+7mA3lvCtf2pY32NMVpy2yWCk8LbfJ+WhRANCAAQYmTM7fikydPHi7GhMPT528HiBVpez1f6qSC7NQI1P1nNtn+idNmu9AMtUB0f75zuxL++Z+s24AJR42Euv1pgU-----END PRIVATE KEY----",
"UmaClientImpl:_args_baseUrl": {
"@id": "urn:solid-server:default:variable:baseUrl"
},
"UmaClientImpl:_args_maxTokenAge": 600
}
]
}
}
53 changes: 47 additions & 6 deletions packages/css/config/uma/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,50 @@
"https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma-css/^0.0.0/components/context.jsonld"
],
"@graph": [
{
"@id": "urn:solid-server:default:ServerConfigurator",
"@type": "ParallelHandler",
"handlers": [
{
"@id": "urn:solid-server:default:StatusDependantServerConfigurator",
"@type": "StatusDependantServerConfigurator",
"dependants": [
{ "@id": "urn:solid-server:default:Fetcher" }
],
"statusMap": [
{
"StatusDependantServerConfigurator:_statusMap_key": "listening",
"StatusDependantServerConfigurator:_statusMap_value": true
},
{
"StatusDependantServerConfigurator:_statusMap_key": "close",
"StatusDependantServerConfigurator:_statusMap_value": false
},
{
"StatusDependantServerConfigurator:_statusMap_key": "error",
"StatusDependantServerConfigurator:_statusMap_value": false
}
]
}
]
},
{
"comment": "Returns the UMA ticket in case of an unauthorized request.",
"@id": "urn:solid-server:default:UmaClient",
"@type": "UmaClientImpl",
"UmaClientImpl:pat": "MYPAT",
"UmaClientImpl:_options_maxTokenAge": 600
"@type": "UmaClient",
"baseUrl": {
"@id": "urn:solid-server:default:variable:baseUrl"
},
"keyGen": {
"@id": "urn:solid-server:default:JwkGenerator"
},
"fetcher": {
"@id": "urn:solid-server:default:Fetcher",
"@type": "PausableFetcher",
"fetcher": {
"@type": "BaseFetcher"
}
}
},
{
"@id": "urn:solid-server:default:OwnerUtil",
Expand All @@ -35,12 +73,15 @@
"store": {
"@id": "urn:solid-server:default:ResourceStore"
},
"ownerUtil": {
"@id": "urn:solid-server:default:OwnerUtil"
},
"umaIdStore": {
"@id": "urn:solid-server:default:UmaIdStore",
"@type": "MemoryMapStorage"
},
"ownerUtil": {
"@id": "urn:solid-server:default:OwnerUtil"
},
"umaClient": {
"@id": "urn:solid-server:default:UmaClient"
}
},
{
Expand Down
1 change: 1 addition & 0 deletions packages/css/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@solidlab/uma": "workspace:^",
"componentsjs": "^5.4.2",
"cross-fetch": "^4.0.0",
"http-message-signatures": "^1.0.4",
"jose": "^5.2.2",
"n3": "^1.17.2"
},
Expand Down
5 changes: 3 additions & 2 deletions packages/css/src/authentication/UmaTokenExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { CredentialsExtractor, getLoggerFor, HttpRequest,
NotImplementedHttpError, BadRequestHttpError, Credentials, TargetExtractor } from '@solid/community-server';
import { UmaClaims, UmaClient } from '../uma/UmaClient';
import { OwnerUtil } from '../util/OwnerUtil';
import { decodeJwt } from 'jose';

export type UmaCredentials = Credentials & { uma: { rpt: UmaClaims } };

Expand Down Expand Up @@ -40,8 +39,10 @@ export class UmaTokenExtractor extends CredentialsExtractor {
*/
public async handle(request: HttpRequest): Promise<UmaCredentials> {
this.logger.info('Extracting token from ' + request.headers.authorization);

const token = request.headers.authorization?.replace(/^Bearer/, '')?.trimStart();
if (!token) throw new BadRequestHttpError('Found empty Bearer token.');

try {
const target = await this.targetExtractor.handle({ request });
const owners = await this.ownerUtil.findOwners(target);
Expand Down Expand Up @@ -70,7 +71,7 @@ export class UmaTokenExtractor extends CredentialsExtractor {
private async tryIntrospection(token: string, owner: string): Promise<UmaClaims> {
const issuer = await this.ownerUtil.findIssuer(owner);
if (!issuer) return Promise.reject();
return this.client.verifyOpaqueToken(token, issuer, owner)
return this.client.verifyOpaqueToken(token, issuer)
}

}
2 changes: 1 addition & 1 deletion packages/css/src/authorization/UmaAuthorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class UmaAuthorizer extends Authorizer {
if (!issuer) throw new Error(`No UMA authorization server found for ${owner}.`);

try {
const ticket = await this.umaClient.fetchTicket(requestedModes, owner, issuer);
const ticket = await this.umaClient.fetchTicket(requestedModes, issuer);
return ticket ? `UMA realm="solid", as_uri="${issuer}", ticket="${ticket}"` : undefined;
} catch (e) {
this.logger.error(`Error while requesting UMA header: ${(e as Error).message}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {exportJWK, generateKeyPair, JSONWebKeySet, JWK, KeyLike} from 'jose';
import {JwksKeyHolder} from './JwksKeyHolder';
import {v4} from 'uuid';
import {Logger} from '../util/logging/Logger';
import {getLoggerFor} from '../util/logging/LoggerUtils';
import { exportJWK, generateKeyPair, JSONWebKeySet, JWK, KeyLike } from 'jose';
import { JwksKeyHolder } from './JwksKeyHolder';
import { v4 } from 'uuid';
import { getLoggerFor } from '@solid/community-server';

const SUPPORTED_ALGORITHMS = new Set(['ES256', 'ES384', 'ES512', 'RS256', 'RS384', 'RS512']);

Expand All @@ -15,7 +14,7 @@ export interface KeyPair {
* In-memory implementation of a JWKS Key Holder.
*/
export class InMemoryJwksKeyHolder extends JwksKeyHolder {
protected readonly logger: Logger = getLoggerFor(this);
protected readonly logger = getLoggerFor(this);
private readonly keys: Map<string, KeyPair>;
private currentKid: undefined | string;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import * as jose from 'jose';

/**
Expand Down
12 changes: 9 additions & 3 deletions packages/css/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export * from './authorization/UmaPermissionReader';

export * from './http/output/metadata/UmaTicketMetadataWriter';

// export * from './identity/configuration/JwksKeyHolder';
// export * from './identity/configuration/InMemoryJwksKeyHolder';

export * from './identity/interaction/account/util/AccountStore';
export * from './identity/interaction/account/util/BaseAccountStore';
export * from './identity/interaction/account/util/LoginStorage';
Expand All @@ -19,8 +22,11 @@ export * from './storage/keyvalue/IndexedStorage';

export * from './uma/ResourceRegistrar';
export * from './uma/UmaClient';
export * from './uma/UmaClientImpl';

export * from './util/OwnerUtil';
export * from './util/StringGuard';
export * from './util/Vocabularies';

export * from './util/fetch/Fetcher';
export * from './util/fetch/BaseFetcher';
export * from './util/fetch/PausableFetcher';
export * from './util/fetch/StatusDependant';
export * from './util/fetch/StatusDependantServerConfigurator';
2 changes: 1 addition & 1 deletion packages/css/src/server/middleware/JwksHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ export class JwksHandler extends HttpHandler {
return;
}

response.end(JSON.stringify({ keys: [ key ] }));
response.end(JSON.stringify({ keys: [ Object.assign(key, { kid: 'TODO' }) ] }));
}
}
59 changes: 30 additions & 29 deletions packages/css/src/uma/ResourceRegistrar.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { ResourceIdentifier, MonitoringStore, KeyValueStorage } from '@solid/community-server';
import { AccessMode, AS, getLoggerFor, StaticHandler } from '@solid/community-server';
import { AS, getLoggerFor, StaticHandler } from '@solid/community-server';
import { OwnerUtil } from '../util/OwnerUtil';
import { fetchUmaConfig } from './util/UmaConfigFetcher';
import { ResourceDescription } from '@solidlab/uma';
import type { UmaClient } from '../uma/UmaClient';

export class ResourceRegistrar extends StaticHandler {
protected readonly logger = getLoggerFor(this);
Expand All @@ -11,6 +11,7 @@ export class ResourceRegistrar extends StaticHandler {
protected store: MonitoringStore,
protected umaIdStore: KeyValueStorage<string, string>,
protected ownerUtil: OwnerUtil,
protected umaClient: UmaClient,
) {
super();

Expand All @@ -27,12 +28,10 @@ export class ResourceRegistrar extends StaticHandler {

protected async createResource(resource: ResourceIdentifier, owner: string): Promise<void> {
const issuer = await this.ownerUtil.findIssuer(owner);
const pat = await this.ownerUtil.retrievePat(owner);

if (!issuer) throw new Error(`Could not find UMA AS for resource owner ${owner}`);
if (!pat) throw new Error(`Could not find PAT for resource owner ${owner}`);

const { resource_registration_endpoint: endpoint } = await fetchUmaConfig(issuer);
const { resource_registration_endpoint: endpoint } = await this.umaClient.fetchUmaConfig(issuer);

const description: ResourceDescription = {
resource_scopes: [
Expand All @@ -46,17 +45,18 @@ export class ResourceRegistrar extends StaticHandler {

this.logger.info(`Creating resource registration for <${resource.path}> at <${endpoint}>`);

try {
const resp = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${pat}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(description),
});
const request = {
url: endpoint,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(description),
};

// do not await - registration happens in background to cope with errors etc.
this.umaClient.signedFetch(endpoint, request).then(async resp => {
if (resp.status !== 201) {
throw new Error (`Resource registration request failed. ${await resp.text()}`);
}
Expand All @@ -68,41 +68,42 @@ export class ResourceRegistrar extends StaticHandler {
}

this.umaIdStore.set(resource.path, umaId);
} catch (error) {
}).catch(error => {
// TODO: Do something useful on error
this.logger.warn(
`Something went wrong during UMA resource registration to create ${resource.path}: ${(error as Error).message}`
);
}
});
}

protected async deleteResource(resource: ResourceIdentifier, owner: string): Promise<void> {
const issuer = await this.ownerUtil.findIssuer(owner);
const pat = await this.ownerUtil.retrievePat(owner);

if (!issuer) throw new Error(`Could not find UMA AS for resource owner ${owner}`);
if (!pat) throw new Error(`Could not find PAT for resource owner ${owner}`);

const { resource_registration_endpoint: endpoint } = await fetchUmaConfig(issuer);
const { resource_registration_endpoint: endpoint } = await this.umaClient.fetchUmaConfig(issuer);

this.logger.info(`Deleting resource registration for <${resource.path}> at <${endpoint}>`);

const umaId = await this.umaIdStore.get(resource.path);
const url = `${endpoint}/${umaId}`;

try {
const request = {
url,
method: 'DELETE',
headers: {}
};

// do not await - registration happens in background to cope with errors etc.
this.umaClient.signedFetch(endpoint, request).then(async _resp => {
if (!umaId) throw new Error('Trying to delete unknown/unregistered resource; no UMA id found.');

const resp = await fetch(`${endpoint}/${umaId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${pat}`,
}
});
} catch (error) {
await this.umaClient.signedFetch(url, request);
}).catch(error => {
// TODO: Do something useful on error
this.logger.warn(
`Something went wrong during UMA resource registration to delete ${resource.path}: ${(error as Error).message}`
);
}
});
}
}
Loading