Skip to content

Commit 47504e5

Browse files
committed
feat: Use odrl:deliveryChannel to constrain client IDs
1 parent 44dc03e commit 47504e5

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
@@ -464,9 +464,7 @@ The `azp` claim of the token will be used.
464464

465465
To restrict a policy to a certain client application,
466466
a constraint needs to be added to the policy.
467-
Due to some issues with internal libraries,
468-
the `odrl:purpose` constraint is currently used to identify the client.
469-
This will be fixed in the near future.
467+
For this, we use the odrl:deliveryChannel left operand.
470468

471469
To restrict a policy to only permit access when using the application `http://example.com/client`,
472470
the policy should look as follows:
@@ -482,7 +480,7 @@ ex:permission a odrl:Permission ;
482480
odrl:target <http://localhost:3000/alice/private/> ;
483481
odrl:assignee <https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me> ;
484482
odrl:constraint ex:constraint .
485-
ex:constraint odrl:leftOperand odrl:purpose ;
483+
ex:constraint odrl:leftOperand odrl:deliveryChannel ;
486484
odrl:operator odrl:eq ;
487485
odrl:rightOperand <http://example.com/client> .
488486
```

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
@@ -195,7 +195,7 @@ describe('A server supporting OIDC tokens', (): void => {
195195
odrl:constraint ex:constraintStandardClient.
196196
197197
ex:constraintStandardClient
198-
odrl:leftOperand odrl:purpose ;
198+
odrl:leftOperand odrl:deliveryChannel ;
199199
odrl:operator odrl:eq ;
200200
odrl:rightOperand <http://example.com/id/${client}> .`;
201201

@@ -333,7 +333,7 @@ describe('A server supporting OIDC tokens', (): void => {
333333
odrl:constraint ex:constraintSolidClient.
334334
335335
ex:constraintSolidClient
336-
odrl:leftOperand odrl:purpose ;
336+
odrl:leftOperand odrl:deliveryChannel ;
337337
odrl:operator odrl:eq ;
338338
odrl:rightOperand <${client}> .`;
339339

0 commit comments

Comments
 (0)