Skip to content

Commit 07d06bc

Browse files
committed
feat: Use CSS HttpHandlers and responses
1 parent e1b6a2f commit 07d06bc

16 files changed

+284
-308
lines changed

packages/uma/config/routes/resources.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
},
3838
"idName": "id"
3939
}],
40+
"parameterMap": [
41+
{
42+
"InteractionRouterHandler:_args_parameterMap_key": "id",
43+
"InteractionRouterHandler:_args_parameterMap_value": "urn:custom:uma:id"
44+
}
45+
],
4046
"handler": { "@id": "urn:uma:default:ResourceRegistrationHandler" }
4147
}
4248
]

packages/uma/src/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ export * from './util/ReType';
5959

6060
// HTTP
6161
export * from './util/http/identifier/BaseTargetExtractor';
62-
export * from './util/http/models/HttpHandler';
63-
export * from './util/http/models/HttpHandlerRequest';
64-
export * from './util/http/models/HttpHandlerResponse';
6562
export * from './util/http/server/ErrorHandler';
6663
export * from './util/http/server/InteractionRouterHandler';
6764
export * from './util/http/server/NodeHttpRequestResponseHandler';

packages/uma/src/routes/Config.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { ASYMMETRIC_CRYPTOGRAPHIC_ALGORITHM }
22
from '@solid/access-token-verifier/dist/constant/ASYMMETRIC_CRYPTOGRAPHIC_ALGORITHM';
3-
import { getLoggerFor } from '@solid/community-server';
4-
import { HttpHandler } from '../util/http/models/HttpHandler';
5-
import { HttpHandlerRequest } from '../util/http/models/HttpHandlerRequest';
6-
import { HttpHandlerResponse } from '../util/http/models/HttpHandlerResponse';
3+
import {
4+
APPLICATION_JSON,
5+
CONTENT_TYPE,
6+
getLoggerFor,
7+
guardedStreamFrom,
8+
OkResponseDescription,
9+
OperationHttpHandler,
10+
OperationHttpHandlerInput,
11+
RepresentationMetadata,
12+
ResponseDescription
13+
} from '@solid/community-server';
714

815
// eslint-disable no-unused-vars
916
export enum ResponseType {
@@ -34,7 +41,7 @@ export type UmaConfiguration = OAuthConfiguration & {
3441
* An HttpHandler used for returning the configuration
3542
* of the UMA Authorization Service.
3643
*/
37-
export class ConfigRequestHandler extends HttpHandler {
44+
export class ConfigRequestHandler extends OperationHttpHandler {
3845
protected readonly logger = getLoggerFor(this);
3946

4047
/**
@@ -48,18 +55,14 @@ export class ConfigRequestHandler extends HttpHandler {
4855

4956
/**
5057
* Returns the endpoint's UMA configuration
51-
*
52-
* @param {HttpHandlerRequest} request - an irrelevant incoming context
53-
* @return {HttpHandlerResponse} - the mock response
54-
*/
55-
async handle(request: HttpHandlerRequest): Promise<HttpHandlerResponse> {
56-
this.logger.info(`Received discovery request at '${request.url}'`);
58+
**/
59+
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
60+
this.logger.info(`Received discovery request at '${input.operation.target.path}'`);
5761

58-
return {
59-
body: JSON.stringify(this.getConfig()),
60-
headers: {'content-type': 'application/json'},
61-
status: 200,
62-
};
62+
return new OkResponseDescription(
63+
new RepresentationMetadata({[CONTENT_TYPE]: APPLICATION_JSON}),
64+
guardedStreamFrom(JSON.stringify(this.getConfig()))
65+
)
6366
}
6467

6568
/**

packages/uma/src/routes/Introspection.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
import { HttpHandler } from '../util/http/models/HttpHandler';
2-
import { HttpHandlerRequest } from '../util/http/models/HttpHandlerRequest';
3-
import { HttpHandlerResponse } from '../util/http/models/HttpHandlerResponse';
41
import { AccessToken } from '../tokens/AccessToken';
52
import { JwtTokenFactory } from '../tokens/JwtTokenFactory';
63
import { SerializedToken } from '../tokens/TokenFactory';
74
import {
5+
APPLICATION_JSON,
86
BadRequestHttpError,
7+
CONTENT_TYPE,
98
getLoggerFor,
10-
JwkGenerator, KeyValueStorage,
9+
guardedStreamFrom,
10+
KeyValueStorage,
11+
OkResponseDescription,
12+
OperationHttpHandler,
13+
OperationHttpHandlerInput,
14+
readableToString,
15+
RepresentationMetadata,
16+
ResponseDescription,
1117
UnauthorizedHttpError,
1218
UnsupportedMediaTypeHttpError
1319
} from '@solid/community-server';
@@ -16,7 +22,7 @@ import { verifyRequest } from '../util/HttpMessageSignatures';
1622
/**
1723
* An HTTP handler that provides introspection into opaque access tokens.
1824
*/
19-
export class IntrospectionHandler extends HttpHandler {
25+
export class IntrospectionHandler extends OperationHttpHandler {
2026
protected readonly logger = getLoggerFor(this);
2127

2228
/**
@@ -34,36 +40,33 @@ export class IntrospectionHandler extends HttpHandler {
3440

3541
/**
3642
* Handle incoming requests for token introspection
37-
* @param {HttpHandlerRequest} request
38-
* @return {HttpHandlerResponse}
3943
*/
40-
async handle(request: HttpHandlerRequest): Promise<HttpHandlerResponse<any>> {
41-
if (!await verifyRequest(request)) throw new UnauthorizedHttpError();
44+
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
45+
if (!await verifyRequest(input.request, input.operation)) throw new UnauthorizedHttpError();
4246

43-
if (request.headers['content-type'] !== 'application/x-www-form-urlencoded') {
47+
if (input.request.headers['content-type'] !== 'application/x-www-form-urlencoded') {
4448
throw new UnsupportedMediaTypeHttpError(
4549
'Only Media Type "application/x-www-form-urlencoded" is supported for this route.');
4650
}
4751

48-
if (request.headers['accept'] !== 'application/json') {
52+
if (input.request.headers['accept'] !== 'application/json') {
4953
throw new UnsupportedMediaTypeHttpError(
5054
'Only "application/json" can be served by this route.');
5155
}
5256

53-
if (!request.body || !(request.body instanceof Object)) {
57+
if (input.operation.body.isEmpty) {
5458
throw new BadRequestHttpError('Missing request body.');
5559
}
5660

5761
try {
58-
const opaqueToken = new URLSearchParams(request.body as Record<string, string>).get('token');
62+
const opaqueToken = new URLSearchParams(await readableToString(input.operation.body.data)).get('token');
5963
if (!opaqueToken) throw new Error ();
6064

61-
const jwt = this.opaqueToJwt(opaqueToken);
62-
return {
63-
headers: {'content-type': 'application/json'},
64-
status: 200,
65-
body: jwt,
66-
};
65+
const jwt = await this.opaqueToJwt(opaqueToken);
66+
return new OkResponseDescription(
67+
new RepresentationMetadata({[CONTENT_TYPE]: APPLICATION_JSON}),
68+
guardedStreamFrom(JSON.stringify(jwt)),
69+
);
6770
} catch (e) {
6871
throw new BadRequestHttpError('Invalid request body.');
6972
}

packages/uma/src/routes/Jwks.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
import { HttpHandler } from '../util/http/models/HttpHandler';
2-
import { HttpHandlerRequest } from '../util/http/models/HttpHandlerRequest';
3-
import { HttpHandlerResponse } from '../util/http/models/HttpHandlerResponse';
4-
import { getLoggerFor, JwkGenerator } from '@solid/community-server';
1+
import {
2+
APPLICATION_JSON,
3+
CONTENT_TYPE,
4+
getLoggerFor,
5+
guardedStreamFrom,
6+
JwkGenerator,
7+
OkResponseDescription,
8+
OperationHttpHandler,
9+
OperationHttpHandlerInput,
10+
RepresentationMetadata,
11+
ResponseDescription
12+
} from '@solid/community-server';
513

614
/**
715
* An HttpHandler used for returning the configuration
816
* of the UMA Authorization Service.
917
*/
10-
export class JwksRequestHandler extends HttpHandler {
18+
export class JwksRequestHandler extends OperationHttpHandler {
1119
protected readonly logger = getLoggerFor(this);
1220

1321
/**
1422
* Yields a new request handler for JWKS
15-
* @param {JwksKeyHolder} keyholder - the keyholder to be used for serving JWKS
23+
* @param {JwkGenerator} generator - the generator to be used for serving JWKS
1624
*/
1725
public constructor(
1826
private readonly generator: JwkGenerator
@@ -22,20 +30,15 @@ export class JwksRequestHandler extends HttpHandler {
2230

2331
/**
2432
* Returns the JSON Web KeySet for specified keyholder
25-
* @param {HttpHandlerRequest} request
26-
* @return {HttpHandlerResponse} - the JWKS response
2733
*/
28-
async handle(request: HttpHandlerRequest): Promise<HttpHandlerResponse> {
29-
this.logger.info(`Received JWKS request at '${request.url}'`);
34+
async handle(request: OperationHttpHandlerInput): Promise<ResponseDescription> {
35+
this.logger.info(`Received JWKS request at '${request.operation.target.path}'`);
3036

3137
const key = await this.generator.getPublicKey();
3238

33-
return {
34-
status: 200,
35-
headers: {
36-
'content-type': 'application/json'
37-
},
38-
body: JSON.stringify({ keys: [ key ] }),
39-
};
39+
return new OkResponseDescription(
40+
new RepresentationMetadata({[CONTENT_TYPE]: APPLICATION_JSON}),
41+
guardedStreamFrom(JSON.stringify({ keys: [ key ] }))
42+
);
4043
}
4144
}

packages/uma/src/routes/ResourceRegistration.ts

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import {
2+
APPLICATION_JSON,
23
BadRequestHttpError,
4+
CONTENT_TYPE,
35
createErrorMessage,
46
getLoggerFor,
7+
guardedStreamFrom,
58
KeyValueStorage,
69
MethodNotAllowedHttpError,
10+
OperationHttpHandler,
11+
OperationHttpHandlerInput,
12+
readableToString,
13+
RepresentationMetadata,
14+
ResponseDescription,
715
UnauthorizedHttpError,
816
UnsupportedMediaTypeHttpError
917
} from '@solid/community-server';
10-
import {HttpHandler} from '../util/http/models/HttpHandler';
11-
import {HttpHandlerResponse} from '../util/http/models/HttpHandlerResponse';
1218
import {v4} from 'uuid';
13-
import { HttpHandlerRequest } from '../util/http/models/HttpHandlerRequest';
19+
import { CUSTOM_UMA } from '../util/Vocabularies';
1420
import { ResourceDescription } from '../views/ResourceDescription';
1521
import { reType } from '../util/ReType';
1622
import { extractRequestSigner, verifyRequest } from '../util/HttpMessageSignatures';
@@ -23,12 +29,9 @@ type ErrorConstructor = { new(msg: string): Error };
2329
*
2430
* It provides an endpoint to a Resource Server for registering its resources.
2531
*/
26-
export class ResourceRegistrationRequestHandler extends HttpHandler {
32+
export class ResourceRegistrationRequestHandler extends OperationHttpHandler {
2733
protected readonly logger = getLoggerFor(this);
2834

29-
/**
30-
* @param {RequestingPartyRegistration[]} resourceServers - Pod Servers to be registered with the UMA AS
31-
*/
3235
constructor(
3336
private readonly resourceStore: KeyValueStorage<string, ResourceDescription>,
3437
) {
@@ -37,32 +40,30 @@ export class ResourceRegistrationRequestHandler extends HttpHandler {
3740

3841
/**
3942
* Handle incoming requests for resource registration
40-
* @param {HttpHandlerRequest} request
41-
* @return {HttpHandlerResponse}
4243
*/
43-
async handle(request: HttpHandlerRequest): Promise<HttpHandlerResponse> {
44-
const signer = await extractRequestSigner(request);
44+
async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
45+
const signer = await extractRequestSigner(input.request);
4546

4647
// TODO: check if signer is actually the correct one
4748

48-
if (!await verifyRequest(request, signer)) {
49+
if (!await verifyRequest(input.request, input.operation, signer)) {
4950
throw new UnauthorizedHttpError(`Failed to verify signature of <${signer}>`);
5051
}
5152

52-
switch (request.method) {
53-
case 'POST': return this.handlePost(request);
54-
case 'DELETE': return this.handleDelete(request);
53+
switch (input.operation.method) {
54+
case 'POST': return this.handlePost(input);
55+
case 'DELETE': return this.handleDelete(input);
5556
default: throw new MethodNotAllowedHttpError();
5657
}
5758
}
5859

59-
private async handlePost(request: HttpHandlerRequest): Promise<HttpHandlerResponse<any>> {
60-
const { headers, body } = request;
61-
62-
if (headers['content-type'] !== 'application/json') {
60+
private async handlePost(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
61+
if (input.request.headers['content-type'] !== 'application/json') {
6362
throw new UnsupportedMediaTypeHttpError('Only Media Type "application/json" is supported for this route.');
6463
}
6564

65+
const body = JSON.parse(await readableToString(input.operation.body.data));
66+
6667
try {
6768
reType(body, ResourceDescription);
6869
} catch (e) {
@@ -71,35 +72,31 @@ export class ResourceRegistrationRequestHandler extends HttpHandler {
7172
}
7273

7374
const resource = v4();
74-
this.resourceStore.set(resource, body);
75+
await this.resourceStore.set(resource, body);
7576

7677
this.logger.info(`Registered resource ${resource}.`);
7778

78-
return ({
79-
status: 201,
80-
headers: {
81-
'content-type': 'application/json'
82-
},
83-
body: JSON.stringify({
79+
return new ResponseDescription(
80+
201,
81+
new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_JSON }),
82+
guardedStreamFrom(JSON.stringify({
8483
_id: resource,
8584
user_access_policy_uri: 'TODO: implement policy UI',
86-
}),
87-
})
85+
}))
86+
);
8887
}
8988

90-
private async handleDelete({ parameters }: HttpHandlerRequest): Promise<HttpHandlerResponse<any>> {
91-
if (typeof parameters?.id !== 'string') throw new Error('URI for DELETE operation should include an id.');
89+
private async handleDelete(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
90+
const id = input.operation.body.metadata.get(CUSTOM_UMA.terms.id)?.value;
91+
if (!id) throw new Error('URI for DELETE operation should include an id.');
9292

93-
if (!await this.resourceStore.has(parameters.id)) {
93+
if (!await this.resourceStore.has(id)) {
9494
throw new Error('Registration to be deleted does not exist (id unknown).');
9595
}
9696

97-
this.logger.info(`Deleted resource ${parameters.id}.`);
97+
this.logger.info(`Deleted resource ${id}.`);
9898

99-
return ({
100-
status: 204,
101-
headers: {},
102-
});
99+
return new ResponseDescription(204);
103100
}
104101

105102
/**

0 commit comments

Comments
 (0)