Skip to content

Commit 586042e

Browse files
committed
feat: Use odrl:deliveryChannel to constrain client IDs
1 parent f68fb9d commit 586042e

6 files changed

Lines changed: 43 additions & 28 deletions

File tree

documentation/getting-started.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,7 @@ The `azp` claim of the token will be used.
421421

422422
To restrict a policy to a certain client application,
423423
a constraint needs to be added to the policy.
424-
Due to some issues with internal libraries,
425-
the `odrl:purpose` constraint is currently used to identify the client.
426-
This will be fixed in the near future.
424+
For this, we use the odrl:deliveryChannel left operand.
427425

428426
To restrict a policy to only permit access when using the application `http://example.com/client`,
429427
the policy should look as follows:
@@ -439,7 +437,7 @@ ex:permission a odrl:Permission ;
439437
odrl:target <http://localhost:3000/alice/private/> ;
440438
odrl:assignee <https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me> ;
441439
odrl:constraint ex:constraint .
442-
ex:constraint odrl:leftOperand odrl:purpose ;
440+
ex:constraint odrl:leftOperand odrl:deliveryChannel ;
443441
odrl:operator odrl:eq ;
444442
odrl:rightOperand <http://example.com/client> .
445443
```

packages/uma/src/policies/authorizers/OdrlAuthorizer.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { BadRequestHttpError, DC, RDF } from '@solid/community-server';
1+
import { RDF } from '@solid/community-server';
22
import { getLoggerFor } from 'global-logger-factory';
3-
import { DataFactory, Quad, Store, Writer } from 'n3';
4-
import { EyelingReasoner, EyeReasoner, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator';
3+
import { DataFactory, Quad, Store } from 'n3';
4+
import { EyelingReasoner, ODRL, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator';
55
import { CLIENTID, WEBID } from '../../credentials/Claims';
66
import { ClaimSet } from '../../credentials/ClaimSet';
77
import { basicPolicy } from '../../ucp/policy/ODRL';
88
import { PrioritizeProhibitionStrategy } from '../../ucp/policy/PrioritizeProhibitionStrategy';
99
import { Strategy } from '../../ucp/policy/Strategy';
1010
import { UCPPolicy } from '../../ucp/policy/UsageControlPolicy';
1111
import { UCRulesStorage } from '../../ucp/storage/UCRulesStorage';
12-
import { ODRL } from '../../ucp/util/Vocabularies';
1312
import { Permission } from '../../views/Permission';
1413
import { Authorizer } from './Authorizer';
1514

@@ -76,17 +75,10 @@ export class OdrlAuthorizer implements Authorizer {
7675
if (typeof claims[CLIENTID] === 'string') {
7776
clientQuads.push(
7877
quad(clientSubject, RDF.terms.type, ODRL.terms.Constraint),
79-
// TODO: using purpose as other constraints are not supported in current version of ODRL evaluator
80-
// https://github.com/SolidLabResearch/ODRL-Evaluator/blob/v0.5.0/ODRL-Support.md#left-operands
81-
quad(clientSubject, ODRL.terms.leftOperand, namedNode(ODRL.namespace + 'purpose')),
78+
quad(clientSubject, ODRL.terms.leftOperand, ODRL.terms.deliveryChannel),
8279
quad(clientSubject, ODRL.terms.operator, ODRL.terms.eq),
8380
quad(clientSubject, ODRL.terms.rightOperand, namedNode(claims[CLIENTID])),
8481
);
85-
// constraints.push({
86-
// type: ODRL.namespace + 'deliveryChannel',
87-
// operator: ODRL.eq,
88-
// value: namedNode(claims[CLIENTID]),
89-
// });
9082
}
9183

9284
for (const { resource_id, resource_scopes } of query) {

packages/uma/src/policies/authorizers/SimpleOdrlAuthorizer.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export class SimpleOdrlAuthorizer implements Authorizer {
151151
* Determines if all constraints for the given rule are valid.
152152
* Returns true if all constraints are valid, false if any constraint is not valid,
153153
* and undefined if any constraint is too complex to evaluate.
154-
* Only supports purpose (for client ID) and dateTime constraints.
154+
* Only supports deliveryChannel (for client ID) and dateTime constraints.
155155
*/
156156
protected validateConstraints(rule: Quad_Subject, policies: ReadOnlyStore, claims: ClaimSet): boolean | undefined {
157157
const constraints = policies.getObjects(rule, ODRL.terms.constraint, null).map(constraint => ({
@@ -165,8 +165,7 @@ export class SimpleOdrlAuthorizer implements Authorizer {
165165
}
166166
for (const constraint of constraints) {
167167
// Return undefined if any of these are too complex or unknown
168-
// TODO: because of weird hack described in OdrlAuthorizer, needs to change to term that makes more sense
169-
if (constraint.leftOperand.equals(ODRL.terms.purpose)) {
168+
if (constraint.leftOperand.equals(ODRL.terms.deliveryChannel)) {
170169
if (!constraint.operator.equals(ODRL.terms.eq)) {
171170
return false;
172171
}

packages/uma/test/unit/policies/authorizers/OdrlAuthorizer.test.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { NotImplementedHttpError, RDF, XSD } from '@solid/community-server';
1+
import 'jest-rdf';
2+
import { RDF, XSD } from '@solid/community-server';
23
import { DataFactory as DF, Parser, Store } from 'n3';
3-
import { ODRLEvaluator } from 'odrl-evaluator';
4+
import { ODRL, ODRLEvaluator } from 'odrl-evaluator';
45
import { Mocked } from 'vitest';
6+
import { CLIENTID } from '../../../../src/credentials/Claims';
57
import { OdrlAuthorizer } from '../../../../src/policies/authorizers/OdrlAuthorizer';
68
import { basicPolicy } from '../../../../src/ucp/policy/ODRL';
79
import { UCRulesStorage } from '../../../../src/ucp/storage/UCRulesStorage';
@@ -34,7 +36,7 @@ describe('OdrlAuthorizer', (): void => {
3436
evaluate.mockResolvedValue([]);
3537

3638
vi.mocked(basicPolicy).mockReturnValue({
37-
ruleIRIs:[],
39+
ruleIRIs: [ 'urn:req-rule' ],
3840
policyIRI: 'req',
3941
representation: new Store(requestQuads),
4042
});
@@ -97,6 +99,30 @@ describe('OdrlAuthorizer', (): void => {
9799
);
98100
});
99101

102+
it('adds client claim context using odrl:deliveryChannel', async(): Promise<void> => {
103+
const claims = { [CLIENTID]: 'client-a' };
104+
const query: Permission[] = [{ resource_id: 'rid', resource_scopes: [ 'urn:example:css:modes:read' ] }];
105+
106+
await expect(authorizer.permissions(claims, query)).resolves.toEqual([{ resource_id: 'rid', resource_scopes: [] }]);
107+
108+
expect(evaluate).toHaveBeenCalledTimes(1);
109+
const generatedRequest = evaluate.mock.calls[0][1];
110+
const clientConstraintQuad = generatedRequest.find((quad) =>
111+
quad.predicate.equals(ODRL.terms.leftOperand) && quad.object.equals(ODRL.terms.deliveryChannel));
112+
113+
expect(clientConstraintQuad).toBeDefined();
114+
const clientConstraintSubject = clientConstraintQuad!.subject;
115+
const clientConstraintQuads = generatedRequest.filter((quad) =>
116+
quad.subject.equals(clientConstraintSubject));
117+
118+
expect(clientConstraintQuads).toEqualRdfQuadArray([
119+
DF.quad(clientConstraintSubject, RDF.terms.type, ODRL.terms.Constraint),
120+
DF.quad(clientConstraintSubject, ODRL.terms.leftOperand, ODRL.terms.deliveryChannel),
121+
DF.quad(clientConstraintSubject, ODRL.terms.operator, ODRL.terms.eq),
122+
DF.quad(clientConstraintSubject, ODRL.terms.rightOperand, DF.namedNode('client-a')),
123+
]);
124+
});
125+
100126
it('extracts the allowed scopes from the resulting report.', async(): Promise<void> => {
101127
const query: Permission[] = [{ resource_id: 'rid', resource_scopes: [ 'urn:example:css:modes:read' ] }];
102128

packages/uma/test/unit/policies/authorizers/SimpleOdrlAuthorizer.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ describe('SimpleOdrlAuthorizer', () => {
134134
expect(fallback.permissions).toHaveBeenCalledWith({}, query);
135135
});
136136

137-
it('returns empty if constraint is not satisfied (purpose)', async () => {
137+
it('returns empty if constraint is not satisfied (deliveryChannel)', async () => {
138138
const rule = addRule({});
139139
addConstraint({
140140
rule,
141-
leftOperand: ODRL.terms.purpose,
141+
leftOperand: ODRL.terms.deliveryChannel,
142142
operator: ODRL.terms.eq,
143143
rightOperand: 'clientA',
144144
});
@@ -150,11 +150,11 @@ describe('SimpleOdrlAuthorizer', () => {
150150
expect(fallback.permissions).not.toHaveBeenCalled();
151151
});
152152

153-
it('returns permission if constraint is satisfied (purpose)', async () => {
153+
it('returns permission if constraint is satisfied (deliveryChannel)', async () => {
154154
const rule = addRule({});
155155
addConstraint({
156156
rule,
157-
leftOperand: ODRL.terms.purpose,
157+
leftOperand: ODRL.terms.deliveryChannel,
158158
operator: ODRL.terms.eq,
159159
rightOperand: 'clientA',
160160
});

test/integration/Oidc.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ describe('A server supporting OIDC tokens', (): void => {
187187
odrl:constraint ex:constraintStandardClient.
188188
189189
ex:constraintStandardClient
190-
odrl:leftOperand odrl:purpose ;
190+
odrl:leftOperand odrl:deliveryChannel ;
191191
odrl:operator odrl:eq ;
192192
odrl:rightOperand <http://example.com/id/${client}> .`;
193193

@@ -325,7 +325,7 @@ describe('A server supporting OIDC tokens', (): void => {
325325
odrl:constraint ex:constraintSolidClient.
326326
327327
ex:constraintSolidClient
328-
odrl:leftOperand odrl:purpose ;
328+
odrl:leftOperand odrl:deliveryChannel ;
329329
odrl:operator odrl:eq ;
330330
odrl:rightOperand <${client}> .`;
331331

0 commit comments

Comments
 (0)