-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathUmaAuthorizer.ts
More file actions
78 lines (65 loc) · 3.06 KB
/
UmaAuthorizer.ts
File metadata and controls
78 lines (65 loc) · 3.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import {
Authorizer, createErrorMessage, ForbiddenHttpError, getLoggerFor, InternalServerError, UnauthorizedHttpError
} from '@solid/community-server';
import type { AccessMap, AuthorizerInput } from '@solid/community-server';
import { OwnerUtil } from '../util/OwnerUtil';
import { UmaClient } from '../uma/UmaClient';
import { DataFactory } from 'n3';
const { blankNode, namedNode, literal } = DataFactory;
export const WWW_AUTH = namedNode('urn:css:http:headers:www-authenticate');
/**
* Authorizer that bases its decision on that of another Authorizer.
* It discovers the relevant UMA Authorization Server for the requested resource(s),
* and checks whether that server protects the resource or whether it is public.
* If the resource is not public and access is not granted by the other Authorizer,
* the UmaAuthorizer includes an UMA Permission Ticket in the resulting error.
*/
export class UmaAuthorizer extends Authorizer {
protected readonly logger = getLoggerFor(this);
/**
* The UmaAuthorizer bases its decisions on those of another {@link Authorizer}.
* It uses an {@link OwnerUtil} to retrieve the relevant UMA issuer,
* and an {@link UmaClient} to communicate with that issuer.
* @param authorizer - {@link Authorizer} that makes the main decision.
* @param ownerUtil - {@link OwnerUtil} that links resources to owners and issuers.
* @param umaClient - {@link UmaClient} that communicates with UMA issuers.
*/
public constructor(
protected authorizer: Authorizer,
protected ownerUtil: OwnerUtil,
protected umaClient: UmaClient,
) {
super();
}
public async handle(input: AuthorizerInput): Promise<void> {
try {
// Try authorizer
await this.authorizer.handleSafe(input);
} catch (error: unknown) {
// Unless 403/403 throw original error
if (!UnauthorizedHttpError.isInstance(error) && !ForbiddenHttpError.isInstance(error)) throw error;
// Request UMA ticket
const authHeader = await this.requestTicket(input.requestedModes);
// Add auth header to error metadata if private
if (authHeader) {
error.metadata.add(WWW_AUTH, literal(authHeader));
this.logger.info(`Authorization failed: ${createErrorMessage(error)}`);
throw error;
}
// Return if public
this.logger.info(`Authorization succeeded: resource is public.`);
}
}
protected async requestTicket(requestedModes: AccessMap): Promise<string | undefined> {
const owner = await this.ownerUtil.findCommonOwner(requestedModes.keys());
const issuer = await this.ownerUtil.findIssuer(owner);
if (!issuer) throw new InternalServerError(`No UMA authorization server found for ${owner}.`);
try {
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}`);
throw new InternalServerError(`Error while requesting UMA header: ${(e as Error).message}.`);
}
}
}