diff --git a/packages/uma/src/policies/authorizers/SimpleOdrlAuthorizer.ts b/packages/uma/src/policies/authorizers/SimpleOdrlAuthorizer.ts
index 1f08c6f5..b7709050 100644
--- a/packages/uma/src/policies/authorizers/SimpleOdrlAuthorizer.ts
+++ b/packages/uma/src/policies/authorizers/SimpleOdrlAuthorizer.ts
@@ -55,6 +55,7 @@ export class SimpleOdrlAuthorizer implements Authorizer {
let permissions: Permission[] = [];
for (const { resource_id, resource_scopes } of query) {
+ const allowedScopes: string[] = [];
for (const scope of resource_scopes) {
const result = this.getPermissions(store, claims, resource_id, scope);
if (!result) {
@@ -62,14 +63,20 @@ export class SimpleOdrlAuthorizer implements Authorizer {
return this.authorizer.permissions(claims, query);
}
- permissions.push(...result);
+ allowedScopes.push(...result);
+ }
+ if (allowedScopes.length > 0) {
+ permissions.push({
+ resource_id,
+ resource_scopes: allowedScopes,
+ });
}
}
return permissions;
}
protected getPermissions(policies: ReadOnlyStore, claims: ClaimSet, resource: string, scope: string):
- Permission[] | undefined {
+ string[] | undefined {
this.logger.info(`Evaluating Request ${scope}, ${resource} with claims ${JSON.stringify(claims)}`);
const targets = [ DF.namedNode(resource), ...policies.getObjects(resource, ODRL.terms.partOf, null)];
let rules = targets.flatMap(target => policies.getSubjects(ODRL.terms.target, target, null));
@@ -145,10 +152,7 @@ export class SimpleOdrlAuthorizer implements Authorizer {
}
}
- return [{
- resource_id: resource,
- resource_scopes: [ oldScope ],
- }];
+ return [ oldScope ];
}
// TODO: 3 modes: valid, not valid, too complicated
diff --git a/packages/uma/test/unit/policies/authorizers/SimpleOdrlAuthorizer.test.ts b/packages/uma/test/unit/policies/authorizers/SimpleOdrlAuthorizer.test.ts
index 67b29622..019a44e4 100644
--- a/packages/uma/test/unit/policies/authorizers/SimpleOdrlAuthorizer.test.ts
+++ b/packages/uma/test/unit/policies/authorizers/SimpleOdrlAuthorizer.test.ts
@@ -243,16 +243,17 @@ describe('SimpleOdrlAuthorizer', () => {
const scope2 = 'urn:example:css:modes:write';
const odrlScope2 = 'http://www.w3.org/ns/odrl/2/modify';
const multiQuery: Permission[] = [
- { resource_id: resource, resource_scopes: [scope] },
+ { resource_id: resource, resource_scopes: [scope, odrlScope] },
{ resource_id: resource2, resource_scopes: [scope2] },
];
addRule({});
+ addRule({ action: odrlScope });
addRule({ target: resource2, action: odrlScope2 });
const result = await authorizer.permissions({}, multiQuery);
expect(result).toEqual([
- { resource_id: resource, resource_scopes: [scope] },
+ { resource_id: resource, resource_scopes: [scope, odrlScope] },
{ resource_id: resource2, resource_scopes: [scope2] },
]);
expect(fallback.permissions).not.toHaveBeenCalled();
diff --git a/test/integration/Partial.test.ts b/test/integration/Partial.test.ts
index 9b0a1595..cd325807 100644
--- a/test/integration/Partial.test.ts
+++ b/test/integration/Partial.test.ts
@@ -1,5 +1,6 @@
import { App } from '@solid/community-server';
import { setGlobalLoggerFactory, WinstonLoggerFactory } from 'global-logger-factory';
+import { decodeJwt } from 'jose';
import { createServer, Server } from 'node:http';
import path from 'node:path';
import { getPorts, instantiateFromConfig } from '../util/ServerUtil';
@@ -18,10 +19,12 @@ interface UmaConfig {
describe('A server with partial results enabled', (): void => {
const owner = 'http://example.com/alice#me';
const user = `http://example.com/bob`;
+ const aliceContainer = `http://localhost:${rsPort}/alice/`;
const aliceResource = `http://localhost:${rsPort}/alice/data`;
const bobResource = `http://localhost:${rsPort}/bob/data`;
const readScope = 'http://www.w3.org/ns/odrl/2/read';
- const writeScope = 'http://www.w3.org/ns/odrl/2/write';
+ const createScope = 'http://www.w3.org/ns/odrl/2/create';
+ const modifyScope = 'http://www.w3.org/ns/odrl/2/modify';
let umaApp: App;
let rsServer: Server;
let umaConfig: UmaConfig;
@@ -77,6 +80,20 @@ describe('A server with partial results enabled', (): void => {
pat = `${patJson.token_type} ${patJson.access_token}`;
let resourceRegistrationResponse = await fetch(umaConfig.resource_registration_endpoint, {
+ method: 'POST',
+ headers: {
+ Authorization: pat,
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ body: JSON.stringify({
+ name: aliceContainer,
+ resource_scopes: [ createScope, modifyScope ],
+ }),
+ });
+ expect(resourceRegistrationResponse.status).toBe(201);
+
+ resourceRegistrationResponse = await fetch(umaConfig.resource_registration_endpoint, {
method: 'POST',
headers: {
Authorization: pat,
@@ -85,7 +102,7 @@ describe('A server with partial results enabled', (): void => {
},
body: JSON.stringify({
name: aliceResource,
- resource_scopes: [ readScope, writeScope ],
+ resource_scopes: [ createScope, modifyScope ],
}),
});
expect(resourceRegistrationResponse.status).toBe(201);
@@ -99,7 +116,7 @@ describe('A server with partial results enabled', (): void => {
},
body: JSON.stringify({
name: bobResource,
- resource_scopes: [ readScope, writeScope ],
+ resource_scopes: [ readScope, modifyScope ],
}),
});
expect(resourceRegistrationResponse.status).toBe(201);
@@ -187,7 +204,7 @@ describe('A server with partial results enabled', (): void => {
odrl:assignee <${owner}> ;
odrl:assigner <${owner}> ;
odrl:action odrl:create, odrl:modify ;
- odrl:target ,
+ odrl:target <${aliceContainer}> ,
<${aliceResource}> .
ex:userPermission a odrl:Permission ;
@@ -208,6 +225,44 @@ describe('A server with partial results enabled', (): void => {
expect(response.status).toBe(200);
});
+ it('returns all requested scopes when possible.', async(): Promise => {
+ const response = await fetch(umaConfig.permission_endpoint, {
+ method: 'POST',
+ headers: {
+ Authorization: pat,
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ body: JSON.stringify([
+ {
+ resource_id: aliceResource,
+ resource_scopes: [ createScope, modifyScope ],
+ },
+ {
+ resource_id: aliceContainer,
+ resource_scopes: [ createScope, modifyScope ],
+ }
+ ]),
+ });
+ expect(response.status).toBe(201);
+ const { ticket } = await response.json() as { ticket: string };
+
+ const token = await getToken(ticket, umaConfig.token_endpoint, owner);
+ expect((token as unknown as { partial?: boolean }).partial).toBeUndefined();
+
+ const decodedToken = decodeJwt(token.access_token);
+ expect(decodedToken.permissions).toEqual([
+ {
+ resource_id: aliceResource,
+ resource_scopes: [createScope, modifyScope],
+ },
+ {
+ resource_id: aliceContainer,
+ resource_scopes: [ createScope, modifyScope ],
+ }
+ ]);
+ });
+
it('returns partial=true for mixed namespaces when not all scopes are granted.', async(): Promise => {
const permissionResponse = await fetch(umaConfig.permission_endpoint, {
method: 'POST',
@@ -219,7 +274,7 @@ describe('A server with partial results enabled', (): void => {
body: JSON.stringify([
{
resource_id: aliceResource,
- resource_scopes: [ readScope, writeScope ],
+ resource_scopes: [ readScope, modifyScope ],
},
{
resource_id: bobResource,