Skip to content

Commit 3846a28

Browse files
committed
feat: Support authorization tokens when using the policy API
1 parent 7d97d96 commit 3846a28

File tree

19 files changed

+172
-52
lines changed

19 files changed

+172
-52
lines changed

documentation/policy-management.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,21 @@ The current implementation supports the following requests on the UMA server:
1616
These requests comply with some restrictions:
1717

1818
- When the URL contains a policy ID, it must be URI encoded.
19-
- The request must have its `Authorization` header set to the owners WebID.
20-
More on that [later](#authentication).
19+
- Every request requires a valid Authorization header, which is detailed below.
20+
21+
### Authorization
22+
23+
The policy API supports similar authentication tokens as the UMA API,
24+
but expects them in the Authorization header,
25+
as the body is already used for other purposes.
26+
Two authorization methods are supported: OIDC tokens, both Solid and standard, and unsafe WebID strings.
27+
28+
To use OIDC, the `Bearer` authorization scheme needs to be used, followed by the token.
29+
For example, `Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI...`.
30+
31+
To directly pass a WebID, the `WebID` scheme can be used together with a URL encoded WebID.
32+
For example, `Authorization: WebID http%3A%2F%2Fexample.com%2Fprofile%2Fcard%23me`.
33+
No validation is performed in this case, so this should only be used for development and debugging purposes.
2134

2235
### Creating policies
2336

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"@context": [
3+
"https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld"
4+
],
5+
"@graph": [
6+
{
7+
"@id": "urn:uma:default:CredentialParser",
8+
"@type": "MappedSchemeParser",
9+
"schemeMap": [
10+
{
11+
"MappedSchemeParser:_schemeMap_key": "WebID",
12+
"MappedSchemeParser:_schemeMap_value": "urn:solidlab:uma:claims:formats:webid"
13+
},
14+
{
15+
"MappedSchemeParser:_schemeMap_key": "Bearer",
16+
"MappedSchemeParser:_schemeMap_value": "http://openid.net/specs/openid-connect-core-1_0.html#IDToken"
17+
}
18+
]
19+
}
20+
]
21+
}

packages/uma/config/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"https://linkedsoftwaredependencies.org/bundles/npm/asynchronous-handlers/^1.0.0/components/context.jsonld"
66
],
77
"import": [
8+
"sai-uma:config/credentials/parsers/default.json",
89
"sai-uma:config/credentials/verifiers/default.json",
910
"sai-uma:config/dialog/negotiators/default.json",
1011
"sai-uma:config/policies/authorizers/default.json",

packages/uma/config/routes/accessrequests.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
"@id": "urn:uma:default:AccessRequestHandler",
1313
"@type": "BaseHandler",
1414
"controller": { "@id": "urn:uma:default:AccessRequestController" },
15+
"credentialParser": { "@id": "urn:uma:default:CredentialParser" },
16+
"verifier": { "@id": "urn:uma:default:Verifier" },
1517
"handleLogMessage": "received access request/grants request",
16-
"patchContentType": "application/json"
18+
"patchContentType": "application/json"
1719
},
1820
{
1921
"@id": "urn:uma:default:AccessRequestRoute",
@@ -33,7 +35,7 @@
3335
"OPTIONS",
3436
"PATCH",
3537
"GET",
36-
"DELETE",
38+
"DELETE",
3739
"PUT"
3840
],
3941
"handler": { "@id": "urn:uma:default:AccessRequestHandler" },

packages/uma/config/routes/policies.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
"@id": "urn:uma:default:PolicyHandler",
1313
"@type": "BaseHandler",
1414
"controller": { "@id": "urn:uma:default:PolicyController" },
15+
"credentialParser": { "@id": "urn:uma:default:CredentialParser" },
16+
"verifier": { "@id": "urn:uma:default:Verifier" },
1517
"handleLogMessage": "received policy request",
16-
"patchContentType": "application/sparql-update"
18+
"patchContentType": "application/sparql-update"
1719
},
1820
{
1921
"@id": "urn:uma:default:PolicyRoute",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { AsyncHandler } from 'asynchronous-handlers';
2+
import { HttpHandlerRequest } from '../util/http/models/HttpHandler';
3+
import { Credential } from './Credential';
4+
5+
/**
6+
* Converts the contents of a request to a Credential token,
7+
* generally by parsing the Authorization header.
8+
*/
9+
export abstract class CredentialParser extends AsyncHandler<HttpHandlerRequest, Credential> {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ForbiddenHttpError, NotImplementedHttpError, UnauthorizedHttpError } from '@solid/community-server';
2+
import { HttpHandlerRequest } from '../../util/http/models/HttpHandler';
3+
import { Credential } from '../Credential';
4+
import { CredentialParser } from '../CredentialParser';
5+
6+
/**
7+
* Interprets Bearer Authorization headers as OIDC tokens.
8+
*/
9+
export class MappedSchemeParser extends CredentialParser {
10+
public constructor(
11+
protected readonly schemeMap: Record<string, string>,
12+
) {
13+
super();
14+
}
15+
16+
public async canHandle(request: HttpHandlerRequest): Promise<void> {
17+
if (!request.headers.authorization) {
18+
throw new UnauthorizedHttpError('Missing Authorization header.');
19+
}
20+
const scheme = request.headers.authorization.split(' ', 1)[0];
21+
if (!this.schemeMap[scheme]) {
22+
throw new ForbiddenHttpError(`Unsupported Authorization scheme ${scheme}.`);
23+
}
24+
}
25+
26+
public async handle(request: HttpHandlerRequest): Promise<Credential> {
27+
const scheme = request.headers.authorization.split(' ', 1)[0];
28+
const token = request.headers.authorization.slice(scheme.length + 1);
29+
return { token, format: this.schemeMap[scheme] };
30+
}
31+
}

packages/uma/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
export * from './credentials/ClaimSet';
44
export * from './credentials/Requirements';
55
export * from './credentials/Credential';
6+
export * from './credentials/CredentialParser';
7+
export * from './credentials/Formats';
8+
9+
// CredentialParsers
10+
export * from './credentials/parse/MappedSchemeParser';
611

712
// Verifiers
813
export * from './credentials/verify/Verifier';

packages/uma/src/routes/BaseHandler.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { BadRequestHttpError, MethodNotAllowedHttpError } from "@solid/community-server";
1+
import { BadRequestHttpError, ForbiddenHttpError, MethodNotAllowedHttpError } from '@solid/community-server';
22
import { getLoggerFor } from 'global-logger-factory';
3-
import { BaseController } from "../controller/BaseController";
4-
import { HttpHandler, HttpHandlerContext, HttpHandlerRequest, HttpHandlerResponse } from "../util/http/models/HttpHandler";
5-
import { verifyHttpCredentials } from "../util/routeSpecific/middlewareUtil";
3+
import { BaseController } from '../controller/BaseController';
4+
import { WEBID } from '../credentials/Claims';
5+
import { ClaimSet } from '../credentials/ClaimSet';
6+
import { CredentialParser } from '../credentials/CredentialParser';
7+
import { Verifier } from '../credentials/verify/Verifier';
8+
import {
9+
HttpHandler,
10+
HttpHandlerContext,
11+
HttpHandlerRequest,
12+
HttpHandlerResponse
13+
} from '../util/http/models/HttpHandler';
614

715
/**
816
* Base handler for policy and access request endpoints.
@@ -18,19 +26,23 @@ import { verifyHttpCredentials } from "../util/routeSpecific/middlewareUtil";
1826
* - **GET** `/` - retrieve all policies (including their rules) or access requests
1927
* - **POST** `/` - create new policy or access request
2028
*/
21-
export abstract class BaseHandler extends HttpHandler {
29+
export class BaseHandler extends HttpHandler {
2230

2331
protected readonly logger = getLoggerFor(this);
2432

2533
/**
2634
* @param controller reference to the controller implementing the policy/access request logic
35+
* @param credentialParser parses the request headers to find the credential format and token
36+
* @param verifier verifies the credential token and extracts the claims
2737
* @param handleLogMessage message to log at the start of each handled request
2838
* @param patchContentType expected content type for PATCH requests (e.g. `application/json` or `application/sparql-update`)
2939
*/
3040
constructor(
3141
protected readonly controller: BaseController,
32-
private readonly handleLogMessage: string,
33-
private readonly patchContentType: string,
42+
protected readonly credentialParser: CredentialParser,
43+
protected readonly verifier: Verifier,
44+
protected readonly handleLogMessage: string,
45+
protected readonly patchContentType: string,
3446
) {
3547
super();
3648
}
@@ -48,20 +60,25 @@ export abstract class BaseHandler extends HttpHandler {
4860
if (request.method === 'OPTIONS')
4961
return this.handleOptions();
5062

51-
const credentials = verifyHttpCredentials(request);
63+
const credential = await this.credentialParser.handleSafe(request);
64+
const claims = await this.verifier.verify(credential);
65+
const userId = claims[WEBID];
66+
if (typeof userId !== 'string') {
67+
throw new ForbiddenHttpError(`Missing claim ${WEBID}.`);
68+
}
5269

5370
if (request.parameters?.id) {
5471
switch (request.method) {
55-
case 'GET': return this.handleSingleGet(request.parameters.id, credentials);
56-
case 'PATCH': return this.handlePatch(request as HttpHandlerRequest<string>, request.parameters.id, credentials);
57-
case 'PUT': return this.handlePut(request as HttpHandlerRequest<string>, request.parameters.id, credentials);
58-
case 'DELETE': return this.handleDelete(request.parameters.id, credentials);
72+
case 'GET': return this.handleSingleGet(request.parameters.id, userId);
73+
case 'PATCH': return this.handlePatch(request as HttpHandlerRequest<string>, request.parameters.id, userId);
74+
case 'PUT': return this.handlePut(request as HttpHandlerRequest<string>, request.parameters.id, userId);
75+
case 'DELETE': return this.handleDelete(request.parameters.id, userId);
5976
default: throw new MethodNotAllowedHttpError();
6077
}
6178
} else {
6279
switch (request.method) {
63-
case 'GET': return this.handleGet(credentials);
64-
case 'POST': return this.handlePost(request as HttpHandlerRequest<string>, credentials);
80+
case 'GET': return this.handleGet(userId);
81+
case 'POST': return this.handlePost(request as HttpHandlerRequest<string>, userId);
6582
default: throw new MethodNotAllowedHttpError();
6683
}
6784
}

packages/uma/src/util/routeSpecific/middlewareUtil.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)