From 7f455496795e5f1014e32c285b0f2137c2733ca0 Mon Sep 17 00:00:00 2001 From: woutslabbinck Date: Thu, 22 Jan 2026 15:55:28 +0100 Subject: [PATCH] feat: introduce the prohibition over permission + default deny strategy in the ODRL authorizer --- packages/uma/package.json | 3 +- packages/uma/src/index.ts | 2 + .../policies/authorizers/OdrlAuthorizer.ts | 224 ++++-------------- .../policy/PrioritizeProhibitionStrategy.ts | 52 ++++ packages/uma/src/ucp/policy/Strategy.ts | 37 +++ .../authorizers/OdrlAuthorizer.test.ts | 28 ++- .../PrioritizeProhibitionStrategy.test.ts | 168 +++++++++++++ yarn.lock | 74 ++++-- 8 files changed, 385 insertions(+), 203 deletions(-) create mode 100644 packages/uma/src/ucp/policy/PrioritizeProhibitionStrategy.ts create mode 100644 packages/uma/src/ucp/policy/Strategy.ts create mode 100644 packages/uma/test/unit/ucp/policy/PrioritizeProhibitionStrategy.test.ts diff --git a/packages/uma/package.json b/packages/uma/package.json index e0b4b99..1e9a79c 100644 --- a/packages/uma/package.json +++ b/packages/uma/package.json @@ -73,7 +73,8 @@ "logform": "^2.6.0", "ms": "^2.1.3", "n3": "^1.17.2", - "odrl-evaluator": "^0.5.0", + "odrl-evaluator": "^0.6.0", + "policy-conflict-resolver": "^0.0.2", "rdf-vocabulary": "^1.0.1", "uri-template-lite": "^23.4.0", "winston": "^3.11.0" diff --git a/packages/uma/src/index.ts b/packages/uma/src/index.ts index 46367ee..e413b79 100644 --- a/packages/uma/src/index.ts +++ b/packages/uma/src/index.ts @@ -81,6 +81,8 @@ export * from './util/http/validate/RequestValidator'; // UCP export * from './ucp/policy/ODRL'; +export * from './ucp/policy/Strategy' +export * from './ucp/policy/PrioritizeProhibitionStrategy' export * from './ucp/policy/UsageControlPolicy'; export * from './ucp/storage/ContainerUCRulesStorage'; export * from './ucp/storage/DirectoryUCRulesStorage'; diff --git a/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts b/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts index 7c750af..2ebc50c 100644 --- a/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts +++ b/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts @@ -1,12 +1,13 @@ -import { BadRequestHttpError, DC, NotImplementedHttpError, RDF } from '@solid/community-server'; +import { BadRequestHttpError, NotImplementedHttpError, RDF } from '@solid/community-server'; import { getLoggerFor } from 'global-logger-factory'; -import { DataFactory, Literal, NamedNode, Quad, Quad_Subject, Store } from 'n3'; -import { EyeReasoner, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator' -import { createVocabulary } from 'rdf-vocabulary'; +import { DataFactory, Quad, Store } from 'n3'; +import { EyeReasoner, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator'; import { CLIENTID, WEBID } from '../../credentials/Claims'; import { ClaimSet } from '../../credentials/ClaimSet'; import { Requirements } from '../../credentials/Requirements'; import { basicPolicy } from '../../ucp/policy/ODRL'; +import { PrioritizeProhibitionStrategy } from '../../ucp/policy/PrioritizeProhibitionStrategy'; +import { Strategy } from '../../ucp/policy/Strategy'; import { UCPPolicy } from '../../ucp/policy/UsageControlPolicy'; import { UCRulesStorage } from '../../ucp/storage/UCRulesStorage'; import { ODRL } from '../../ucp/util/Vocabularies'; @@ -34,6 +35,7 @@ const { quad, namedNode, literal, blankNode } = DataFactory export class OdrlAuthorizer implements Authorizer { protected readonly logger = getLoggerFor(this); private readonly odrlEvaluator: ODRLEvaluator; + private readonly strategy: Strategy; /** * Creates a OdrlAuthorizer enforcing policies using ODRL with the ODRL Evaluator. @@ -47,13 +49,14 @@ export class OdrlAuthorizer implements Authorizer { eyePath?: string, ) { const engine = eyePath ? - new ODRLEngineMultipleSteps({reasoner: new EyeReasoner(eyePath, ["--quiet", "--nope", "--pass-only-new"])}) : - new ODRLEngineMultipleSteps(); + new ODRLEngineMultipleSteps({ reasoner: new EyeReasoner(eyePath, ["--quiet", "--nope", "--pass-only-new"]) }) : + new ODRLEngineMultipleSteps(); this.odrlEvaluator = new ODRLEvaluator(engine); + this.strategy = new PrioritizeProhibitionStrategy(); } public async permissions(claims: ClaimSet, query?: Permission[]): Promise { - this.logger.info(`Calculating permissions. ${JSON.stringify({claims, query})}`); + this.logger.info(`Calculating permissions. ${JSON.stringify({ claims, query })}`); if (!query) { this.logger.warn('The OdrlAuthorizer can only calculate permissions for explicit queries.') return []; @@ -68,9 +71,9 @@ export class OdrlAuthorizer implements Authorizer { // prepare sotw const sotw = new Store(); sotw.add(quad( - namedNode('http://example.com/request/currentTime'), - namedNode('http://purl.org/dc/terms/issued'), - literal(new Date().toISOString(), namedNode("http://www.w3.org/2001/XMLSchema#dateTime"))), + namedNode('http://example.com/request/currentTime'), + namedNode('http://purl.org/dc/terms/issued'), + literal(new Date().toISOString(), namedNode("http://www.w3.org/2001/XMLSchema#dateTime"))), ); const subject = typeof claims[WEBID] === 'string' ? claims[WEBID] : 'urn:solidlab:uma:id:anonymous'; @@ -92,7 +95,7 @@ export class OdrlAuthorizer implements Authorizer { // }); } - for (const {resource_id, resource_scopes} of query) { + for (const { resource_id, resource_scopes } of query) { grantedPermissions[resource_id] = []; const actions = transformActionsCssToOdrl(resource_scopes); for (const action of actions) { @@ -112,9 +115,9 @@ export class OdrlAuthorizer implements Authorizer { // Adding context triples for the client identifier, if there is one if (clientQuads.length > 0) { requestStore.addQuad(quad( - namedNode(request.ruleIRIs[0]), - namedNode('https://w3id.org/force/sotw#context'), - clientSubject, + namedNode(request.ruleIRIs[0]), + namedNode('https://w3id.org/force/sotw#context'), + clientSubject, )); requestStore.addQuads(clientQuads); } @@ -124,19 +127,19 @@ export class OdrlAuthorizer implements Authorizer { [...policyStore], [...requestStore], [...sotw]); - const reportStore = new Store(reports); - // TODO: handle multiple reports -> possible to be generated - // NOTE: current strategy, add all actions of active reports generated by the request - // fetch active and attempted - const PolicyReportNodes = reportStore.getSubjects(RDF.type, CR.PolicyReport, null); - for (const policyReportNode of PolicyReportNodes) { - const policyReport = parseComplianceReport(policyReportNode, reportStore) - const activeReports = policyReport.ruleReport.filter( - (report) => report.activationState === ActivationState.Active); - if (activeReports.length > 0 && activeReports[0].type === RuleReportType.PermissionReport) { - grantedPermissions[resource_id].push(action); - } + // handle potential conflicts with a strategy + const allowed = await this.strategy.handleSafe({ + request: { + request: [...requestStore], + identifier: namedNode(request.policyIRI) + }, + policies: [...policyStore], + reports: reports + }) + + if (allowed) { + grantedPermissions[resource_id].push(action); } } } @@ -145,7 +148,7 @@ export class OdrlAuthorizer implements Authorizer { resource_id => permissions.push({ resource_id, resource_scopes: transformActionsOdrlToCss(grantedPermissions[resource_id]) - }) ); + })); return permissions; } @@ -155,13 +158,13 @@ export class OdrlAuthorizer implements Authorizer { } const scopeCssToOdrl: Map = new Map(); -scopeCssToOdrl.set('urn:example:css:modes:read','http://www.w3.org/ns/odrl/2/read'); -scopeCssToOdrl.set('urn:example:css:modes:append','http://www.w3.org/ns/odrl/2/append'); -scopeCssToOdrl.set('urn:example:css:modes:create','http://www.w3.org/ns/odrl/2/create'); -scopeCssToOdrl.set('urn:example:css:modes:delete','http://www.w3.org/ns/odrl/2/delete'); -scopeCssToOdrl.set('urn:example:css:modes:write','http://www.w3.org/ns/odrl/2/write'); +scopeCssToOdrl.set('urn:example:css:modes:read', 'http://www.w3.org/ns/odrl/2/read'); +scopeCssToOdrl.set('urn:example:css:modes:append', 'http://www.w3.org/ns/odrl/2/append'); +scopeCssToOdrl.set('urn:example:css:modes:create', 'http://www.w3.org/ns/odrl/2/create'); +scopeCssToOdrl.set('urn:example:css:modes:delete', 'http://www.w3.org/ns/odrl/2/delete'); +scopeCssToOdrl.set('urn:example:css:modes:write', 'http://www.w3.org/ns/odrl/2/write'); -const scopeOdrlToCss : Map = new Map(Array.from(scopeCssToOdrl, entry => [entry[1], entry[0]])); +const scopeOdrlToCss: Map = new Map(Array.from(scopeCssToOdrl, entry => [entry[1], entry[0]])); /** * Transform the Actions enforced by the Community Solid Server to equivalent ODRL Actions @@ -173,11 +176,11 @@ function transformActionsCssToOdrl(actions: string[]): string[] { // in UMAPermissionReader, only the last part of the URN will be used, divided by a colon // again, see CSS package return actions.map(action => { - const result = scopeCssToOdrl.get(action); - if (!result) { - throw new BadRequestHttpError(`Unsupported action ${action}`); - } - return result; + const result = scopeCssToOdrl.get(action); + if (!result) { + throw new BadRequestHttpError(`Unsupported action ${action}`); + } + return result; }); } /** @@ -187,151 +190,10 @@ function transformActionsCssToOdrl(actions: string[]): string[] { function transformActionsOdrlToCss(actions: string[]): string[] { const cssActions = [] for (const action of actions) { - if (action === 'http://www.w3.org/ns/odrl/2/use'){ + if (action === 'http://www.w3.org/ns/odrl/2/use') { return Array.from(scopeCssToOdrl.keys()); } cssActions.push(scopeOdrlToCss.get(action)!); } return cssActions; -} - -type PolicyReport = { - id: NamedNode; - created: Literal; - request: NamedNode; - policy: NamedNode; - ruleReport: RuleReport[]; -} -type RuleReport = { - id: NamedNode; - type: RuleReportType; - activationState: ActivationState - rule: NamedNode; - requestedRule: NamedNode; - premiseReport: PremiseReport[] -} - -type PremiseReport = { - id: NamedNode; - type:PremiseReportType; - premiseReport: PremiseReport[]; - satisfactionState: SatisfactionState -} - -// is it possible to just use CR.namespace + "term"? -// https://github.com/microsoft/TypeScript/issues/40793 -enum RuleReportType { - PermissionReport= 'https://w3id.org/force/compliance-report#PermissionReport', - ProhibitionReport= 'https://w3id.org/force/compliance-report#ProhibitionReport', - ObligationReport= 'https://w3id.org/force/compliance-report#ObligationReport', -} -enum SatisfactionState { - Satisfied= 'https://w3id.org/force/compliance-report#Satisfied', - Unsatisfied= 'https://w3id.org/force/compliance-report#Unsatisfied', -} - -enum PremiseReportType { - ConstraintReport = 'https://w3id.org/force/compliance-report#ConstraintReport', - PartyReport = 'https://w3id.org/force/compliance-report#PartyReport', - TargetReport = 'https://w3id.org/force/compliance-report#TargetReport', - ActionReport = 'https://w3id.org/force/compliance-report#ActionReport', -} - -enum ActivationState { - Active= 'https://w3id.org/force/compliance-report#Active', - Inactive= 'https://w3id.org/force/compliance-report#Inactive', -} - -/** - * Parses an ODRL Compliance Report Model into a {@link PolicyReport}. - * @param identifier - * @param store - */ -function parseComplianceReport(identifier: Quad_Subject, store: Store): PolicyReport { - const exists = store.getQuads(identifier,RDF.type,CR.PolicyReport, null).length === 1; - if (!exists) { throw Error(`No Policy Report found with: ${identifier}.`); } - const ruleReportNodes = store.getObjects(identifier, CR.ruleReport, null) as NamedNode[]; - - return { - id: identifier as NamedNode, - created: store.getObjects(identifier, DC.namespace+"created", null)[0] as Literal, - policy: store.getObjects(identifier, CR.policy, null)[0] as NamedNode, - request: store.getObjects(identifier, CR.policyRequest, null)[0] as NamedNode, - ruleReport: ruleReportNodes.map(ruleReportNode => parseRuleReport(ruleReportNode, store)) - } -} - -/** - * Parses Rule Reports from a Compliance Report, including its premises - * @param identifier - * @param store - */ -function parseRuleReport(identifier: Quad_Subject, store: Store): RuleReport { - const premiseNodes = store.getObjects(identifier,CR.premiseReport, null) as NamedNode[]; - return { - id: identifier as NamedNode, - type: store.getObjects(identifier, RDF.type, null)[0].value as RuleReportType, - activationState: store.getObjects(identifier, CR.activationState, null)[0].value as ActivationState, - requestedRule: store.getObjects(identifier, CR.ruleRequest, null)[0] as NamedNode, - rule: store.getObjects(identifier, CR.rule, null)[0] as NamedNode, - premiseReport: premiseNodes.map((prem) => parsePremiseReport(prem, store)) - } -} - -/** - * Parses Premise Reports, including premises of a Premise Report itself. - * Note that if for some reason there are circular premise reports, this will result into an infinite loop - * @param identifier - * @param store - */ -function parsePremiseReport(identifier: Quad_Subject, store: Store): PremiseReport { - const nestedPremises = store.getObjects(identifier, CR.PremiseReport, null) as NamedNode[]; - return { - id: identifier as NamedNode, - type: store.getObjects(identifier, RDF.type, null)[0].value as PremiseReportType, - premiseReport: nestedPremises.map((prem) => parsePremiseReport(prem, store)), - satisfactionState: store.getObjects(identifier, CR.satisfactionState, null)[0].value as SatisfactionState - } -} -const CR = createVocabulary('https://w3id.org/force/compliance-report#', - 'PolicyReport', - 'RuleReport', - 'PermissionReport', - 'ProhibitionReport', - 'DutyReport', - 'PremiseReport', - 'ConstraintReport', - 'PartyReport', - 'ActionReport', - 'TargetReport', - 'ActivationState', - 'Active', - 'Inactive', - 'AttemptState', - 'Attempted', - 'NotAttempted', - 'PerformanceState', - 'Performed', - 'Unperformed', - 'Unknown', - 'DeonticState', - 'NonSet', - 'Violated', - 'Fulfilled', - 'SatisfactionState', - 'Satisfied', - 'Unsatisfied', - 'policy', - 'policyRequest', - 'ruleReport', - 'conditionReport', - 'premiseReport', - 'rule', - 'ruleRequest', - 'activationState', - 'attemptState', - 'performanceState', - 'deonticState', - 'constraint', - 'satisfactionState', - ) +} \ No newline at end of file diff --git a/packages/uma/src/ucp/policy/PrioritizeProhibitionStrategy.ts b/packages/uma/src/ucp/policy/PrioritizeProhibitionStrategy.ts new file mode 100644 index 0000000..3d3975e --- /dev/null +++ b/packages/uma/src/ucp/policy/PrioritizeProhibitionStrategy.ts @@ -0,0 +1,52 @@ +import { Store } from 'n3'; +import { parseComplianceReport, RDF, REPORT, serializeComplianceReport } from 'odrl-evaluator'; +import { ActiveConflictResolver, ConflictResolverInput, DenyConflictResolver, FORCE } from 'policy-conflict-resolver'; +import { ConflictResolutionStrategyInput, Strategy } from './Strategy'; + +/** + * A strategy for ODRL evaluations that combines two strategies: + * - default deny: If there is no active permission, the action is not allowed -> There must be at least one permission. + * - prohibition over permissions: The action is allowed if there is no prohibition and at least one permission. + * + * The stronger of the two is that there must be at least one permission and no prohibitions for that given request. + * + * It works for one request at a time to determine whether the action is allowed on the resource or not. + */ +export class PrioritizeProhibitionStrategy extends Strategy { + public constructor() { + super(new ActiveConflictResolver(new DenyConflictResolver())); + } + + async handle(input: ConflictResolutionStrategyInput): Promise { + const reportStore = new Store(input.reports) + const policyReportNodes = reportStore.getSubjects(RDF.type, REPORT.PolicyReport, null); + const conflictResolverInput: ConflictResolverInput = { reports: [] } + + for (const policyReportNode of policyReportNodes) { + const parsedReport = parseComplianceReport(policyReportNode, reportStore); + + if (parsedReport.request.value !== input.request.identifier.value) { + // Ignore this compliance report as it pertains to another request + continue; + } + // NOTE: on rule level of the compliance, this does not get checked. + // In theory it is possible to have a compliance report that has a different requested rule than to the top level. + // In practice, that should not happen. + + conflictResolverInput.reports.push( + { + report: serializeComplianceReport(parsedReport), + policy: input.policies + }) + + } + const result = await this.resolver.handleSafe(conflictResolverInput); + const resultStore = new Store(result.report); + const status = resultStore.getObjects(result.identifier, FORCE.conclusion, null); + if (status.length < 1) { + return false; + } + + return status[0].value === FORCE.Allow; + } +} \ No newline at end of file diff --git a/packages/uma/src/ucp/policy/Strategy.ts b/packages/uma/src/ucp/policy/Strategy.ts new file mode 100644 index 0000000..f90ae62 --- /dev/null +++ b/packages/uma/src/ucp/policy/Strategy.ts @@ -0,0 +1,37 @@ +import type { Quad, Quad_Subject } from '@rdfjs/types'; +import { AsyncHandler } from 'asynchronous-handlers'; +import { ConflictResolver } from 'policy-conflict-resolver'; + +export interface ConflictResolutionStrategyInput { + /** + * The Evaluation Request to which an ODRL Evaluation has occurred. + */ + request: { + identifier: Quad_Subject; + request: Quad[]; + } + /** + * A set of ODRL policies reports serialized as a list of quads + */ + policies: Quad[]; + /** + * A set of policy compliance reports serialized as a list of quads + */ + reports: Quad[]; +} + +/** + * The strategy employed for ODRL Evaluations. + * + * This is necessary to deal with multiple rule reports that might conflict with each other. + * It also contains the logic to encode the strategy what happens when there is no information to be gained from the compliance report. + * I.e. what access control decision is employed when no active Permission Rule Reports are present. + */ +export abstract class Strategy extends AsyncHandler { + protected resolver: ConflictResolver; + + constructor(conflictResolver: ConflictResolver) { + super(); + this.resolver = conflictResolver; + } +} \ No newline at end of file diff --git a/packages/uma/test/unit/policies/authorizers/OdrlAuthorizer.test.ts b/packages/uma/test/unit/policies/authorizers/OdrlAuthorizer.test.ts index 7362403..cf4451f 100644 --- a/packages/uma/test/unit/policies/authorizers/OdrlAuthorizer.test.ts +++ b/packages/uma/test/unit/policies/authorizers/OdrlAuthorizer.test.ts @@ -35,7 +35,7 @@ describe('OdrlAuthorizer', (): void => { vi.mocked(basicPolicy).mockReturnValueOnce({ ruleIRIs:[], - policyIRI: '', + policyIRI: 'req', representation: new Store(requestQuads), }); @@ -109,8 +109,13 @@ describe('OdrlAuthorizer', (): void => { @prefix dc: . @prefix xsd: . a cr:PolicyReport ; + dc:created "2024-02-12T11:20:10.999Z"^^xsd:dateTime ; + cr:policyRequest ; + cr:policy ; cr:ruleReport . a cr:PermissionReport ; + cr:rule ; + cr:ruleRequest ; cr:activationState cr:Active ; cr:premiseReport . a cr:Target-Report ; @@ -132,8 +137,13 @@ describe('OdrlAuthorizer', (): void => { @prefix dc: . @prefix xsd: . a cr:PolicyReport ; + dc:created "2024-02-12T11:20:10.999Z"^^xsd:dateTime ; + cr:policyRequest ; + cr:policy ; cr:ruleReport . a cr:PermissionReport ; + cr:rule ; + cr:ruleRequest ; cr:activationState cr:Inactive ; cr:premiseReport . a cr:Target-Report ; @@ -155,8 +165,14 @@ describe('OdrlAuthorizer', (): void => { @prefix dc: . @prefix xsd: . a cr:PolicyReport ; + cr:policyRequest ; + dc:created "2024-02-12T11:20:10.999Z"^^xsd:dateTime ; + cr:policyRequest ; + cr:policy ; cr:ruleReport . a cr:ProhibitionReport ; + cr:rule ; + cr:ruleRequest ; cr:activationState cr:Active ; cr:premiseReport . a cr:Target-Report ; @@ -181,8 +197,13 @@ describe('OdrlAuthorizer', (): void => { @prefix dc: . @prefix xsd: . a cr:PolicyReport ; + dc:created "2024-02-12T11:20:10.999Z"^^xsd:dateTime ; + cr:policyRequest ; + cr:policy ; cr:ruleReport . a cr:PermissionReport ; + cr:rule ; + cr:ruleRequest ; cr:activationState cr:Active ; cr:premiseReport . a cr:Target-Report ; @@ -193,8 +214,13 @@ describe('OdrlAuthorizer', (): void => { @prefix dc: . @prefix xsd: . a cr:PolicyReport ; + dc:created "2024-02-12T11:20:10.999Z"^^xsd:dateTime ; + cr:policyRequest ; + cr:policy ; cr:ruleReport . a cr:ProhibitionReport ; + cr:rule ; + cr:ruleRequest ; cr:activationState cr:Active ; cr:premiseReport . a cr:Target-Report ; diff --git a/packages/uma/test/unit/ucp/policy/PrioritizeProhibitionStrategy.test.ts b/packages/uma/test/unit/ucp/policy/PrioritizeProhibitionStrategy.test.ts new file mode 100644 index 0000000..8ab25ca --- /dev/null +++ b/packages/uma/test/unit/ucp/policy/PrioritizeProhibitionStrategy.test.ts @@ -0,0 +1,168 @@ +import { + PolicyReport, + RuleReportType, + PremiseReportType, + SatisfactionState, + ActivationState, + AttemptState, + serializeComplianceReport, + ODRL +} from "odrl-evaluator" +import { DataFactory } from 'n3'; +import type { Quad, Quad_Subject } from '@rdfjs/types' +import { PrioritizeProhibitionStrategy } from '../../../../src/ucp/policy/PrioritizeProhibitionStrategy' + +const { namedNode, literal, quad } = DataFactory; + +describe("PrioritizeProhibitionStrategy", (): void => { + let complianceReport: PolicyReport; + let strategy: PrioritizeProhibitionStrategy; + let request: { identifier: Quad_Subject, request: Quad[] }; + beforeEach(async (): Promise => { + let requestIdentifier = "urn:uuid:evaluation-request-1"; + const requestRule = "urn:uuid:requested-rule-xyz" + request = { + identifier: namedNode(requestIdentifier), + request: [ + quad(namedNode(requestIdentifier), ODRL.terms.permission, namedNode(requestRule)) + ] + } + complianceReport = { + id: namedNode("urn:uuid:policy-report-1"), + created: literal("2024-02-12T11:20:10.999Z", "http://www.w3.org/2001/XMLSchema#dateTime"), + request: namedNode(requestIdentifier), + policy: namedNode("urn:uuid:policy-123"), + ruleReport: [ + { + id: namedNode("urn:uuid:rule-report-1"), + type: RuleReportType.PermissionReport, + activationState: ActivationState.Active, + attemptState: AttemptState.Attempted, + performanceState: undefined, + deonticState: undefined, + rule: namedNode("urn:uuid:rule-abc"), + requestedRule: namedNode(requestRule), + premiseReport: [ + { + id: namedNode("urn:uuid:premise-1"), + type: PremiseReportType.TargetReport, + premiseReport: [], + satisfactionState: SatisfactionState.Satisfied + }, + { + id: namedNode("urn:uuid:premise-2"), + type: PremiseReportType.PartyReport, + premiseReport: [], + satisfactionState: SatisfactionState.Satisfied + }, + { + id: namedNode("urn:uuid:premise-3"), + type: PremiseReportType.ActionReport, + premiseReport: [], + satisfactionState: SatisfactionState.Satisfied + } + ], + conditionReport: [] + } + ] + } + strategy = new PrioritizeProhibitionStrategy(); + }); + + it('returns true when there are only active rule reports.', async (): Promise => { + const result = await strategy.handle({ + request: request, + reports: serializeComplianceReport(complianceReport), + policies: [] + }); + expect(result).toBe(true) + }); + + it('returns false when there no active permission rule reports.', async (): Promise => { + complianceReport.ruleReport[0].activationState = ActivationState.Inactive + const result = await strategy.handle({ + request: request, + reports: serializeComplianceReport(complianceReport), + policies: [] + }); + expect(result).toBe(false) + }); + + it('returns false when there is an active prohibition rule report.', async (): Promise => { + complianceReport.ruleReport[0].type = RuleReportType.ProhibitionReport; + const result = await strategy.handle({ + request: request, + reports: serializeComplianceReport(complianceReport), + policies: [] + }); + expect(result).toBe(false) + }); + + it('returns false when there is an active prohibition and active permission rule report in different compliance reports.', async (): Promise => { + let prohibitionComplianceReport: PolicyReport = { + id: namedNode("urn:uuid:policy-report-2"), + created: literal("2024-02-12T11:20:10.999Z", "http://www.w3.org/2001/XMLSchema#dateTime"), + request: complianceReport.request, + policy: namedNode("urn:uuid:policy-124"), + ruleReport: [ + { + id: namedNode("urn:uuid:rule-report-2"), + type: RuleReportType.ProhibitionReport, + activationState: ActivationState.Active, + attemptState: AttemptState.Attempted, + performanceState: undefined, + deonticState: undefined, + rule: namedNode("urn:uuid:rule-abc"), + requestedRule: complianceReport.ruleReport[0].requestedRule, + premiseReport: [ + ], + conditionReport: [] + } + ] + } + const result = await strategy.handle({ + request: request, + reports: [...serializeComplianceReport(complianceReport), ...serializeComplianceReport(prohibitionComplianceReport)], + policies: [] + }); + expect(result).toBe(false) + }); + + it('returns false when there is an active prohibition and active permission rule report in the same compliance report.', async (): Promise => { + complianceReport.ruleReport.push( + { + id: namedNode("urn:uuid:rule-report-2"), + type: RuleReportType.ProhibitionReport, + activationState: ActivationState.Active, + attemptState: AttemptState.Attempted, + performanceState: undefined, + deonticState: undefined, + rule: namedNode("urn:uuid:rule-abc"), + requestedRule: complianceReport.ruleReport[0].requestedRule, + premiseReport: [ + ], + conditionReport: [] + } + ) + const result = await strategy.handle({ + request: request, + reports: serializeComplianceReport(complianceReport), + policies: [] + }); + expect(result).toBe(false) + }); + + + it('returns false when there are no active permission rule reports for the given request.', async (): Promise => { + request.identifier = namedNode("random-request"); + const result = await strategy.handle({ + request: request, + reports: serializeComplianceReport(complianceReport), + policies: [] + }); + expect(result).toBe(false) + }) +}) + + + diff --git a/yarn.lock b/yarn.lock index 970d544..01bcdd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5656,7 +5656,8 @@ __metadata: logform: "npm:^2.6.0" ms: "npm:^2.1.3" n3: "npm:^1.17.2" - odrl-evaluator: "npm:^0.5.0" + odrl-evaluator: "npm:^0.6.0" + policy-conflict-resolver: "npm:^0.0.2" rdf-vocabulary: "npm:^1.0.1" uri-template-lite: "npm:^23.4.0" winston: "npm:^3.11.0" @@ -5837,10 +5838,10 @@ __metadata: languageName: node linkType: hard -"@types/emscripten@npm:^1.39.13": - version: 1.40.1 - resolution: "@types/emscripten@npm:1.40.1" - checksum: 10c0/0d6cd29e551f85ba49a0e7d58de16c857960d40e57553e7cc2860b7d80c4210c992ed292998ec3fd3bdc3b41d96541e91d01a6c232106ac0ad79b4710e87f38d +"@types/emscripten@npm:^1.41.5": + version: 1.41.5 + resolution: "@types/emscripten@npm:1.41.5" + checksum: 10c0/ae816da716f896434e59df7a71b67c71ae7e85ca067a32aef1616572fc4757459515d42ade6f5b8fd8d69733a9dbd0cf23010fec5b2f41ce52c09501aa350e45 languageName: node linkType: hard @@ -8547,17 +8548,26 @@ __metadata: languageName: node linkType: hard -"eyereasoner@npm:^16.18.4": - version: 16.34.1 - resolution: "eyereasoner@npm:16.34.1" +"eyeling@npm:^1.10.6": + version: 1.10.14 + resolution: "eyeling@npm:1.10.14" + bin: + eyeling: eyeling.js + checksum: 10c0/fa6787a209b38029b14318d16ea667a8ec5e324570477bc4d688200416595b34762383a454f7610fbd06a8ea4f290d42dcc93aa5695c145691f50772baa1bf1a + languageName: node + linkType: hard + +"eyereasoner@npm:^19.0.1": + version: 19.0.1 + resolution: "eyereasoner@npm:19.0.1" dependencies: n3: "npm:^1.16.3" - swipl-wasm: "npm:4.0.13" + swipl-wasm: "npm:6.0.0" peerDependencies: "@rdfjs/types": ^1.1.0 bin: eyereasoner: dist/bin/index.js - checksum: 10c0/a1c78114edc20d94b7cb052305d97bc16e49ece53b7c221cda4309c6e92deb1ed485ba31fff728e4bd1195ee16421e1cf25f5da43225e52939ddac2bca9678ba + checksum: 10c0/320d4d40a9941d979861fb24a949485f3b0655bd85c9519a039e83a4c7da5162fc30bc9d39f43231075ff5557d083bb3317ee8aeb2630b54406ed2ac14eecb25 languageName: node linkType: hard @@ -10738,6 +10748,16 @@ __metadata: languageName: node linkType: hard +"n3@npm:^2.0.1": + version: 2.0.1 + resolution: "n3@npm:2.0.1" + dependencies: + buffer: "npm:^6.0.3" + readable-stream: "npm:^4.0.0" + checksum: 10c0/f8c6de2052004c83b8870445e844ce6022d34a91807003b407c696b12e56cc8d74c987fa4103fef05a3544b4a70b44b7219500f609808181207fde34faac0a32 + languageName: node + linkType: hard + "nanoid@npm:^3.3.11": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -10959,15 +10979,16 @@ __metadata: languageName: node linkType: hard -"odrl-evaluator@npm:^0.5.0": - version: 0.5.0 - resolution: "odrl-evaluator@npm:0.5.0" +"odrl-evaluator@npm:^0.6.0": + version: 0.6.0 + resolution: "odrl-evaluator@npm:0.6.0" dependencies: "@jeswr/pretty-turtle": "npm:^1.8.2" "@rdfjs/types": "npm:^1.1.0" "@types/n3": "npm:^1.16.3" commit-and-tag-version: "npm:^12.6.0" - eyereasoner: "npm:^16.18.4" + eyeling: "npm:^1.10.6" + eyereasoner: "npm:^19.0.1" n3: "npm:^1.20.4" odrl-atomizer: "npm:^0.1.2" rdf-isomorphic: "npm:^1.3.1" @@ -10978,7 +10999,7 @@ __metadata: streamify-string: "npm:^1.0.1" tmp: "npm:^0.2.3" uuidv4: "npm:^6.2.13" - checksum: 10c0/38058c3d7342a939a65bf93de6b23ee332497966c4043543246cf10f6238cec72d10115b19dbf647cad4a4fe55e43f9e18f3482cbaf31ae71ff6fc894e0c3ca5 + checksum: 10c0/0df8d6cb738d159e83ae742af1d70c704c2c865e244c2761f1ab45d7d63ce5f974909f77bfc3659271002b326e0bc4e894f1d8785ce54caf6b2d638b33e47a31 languageName: node linkType: hard @@ -11340,6 +11361,19 @@ __metadata: languageName: node linkType: hard +"policy-conflict-resolver@npm:^0.0.2": + version: 0.0.2 + resolution: "policy-conflict-resolver@npm:0.0.2" + dependencies: + "@rdfjs/types": "npm:^2.0.1" + "@types/n3": "npm:^1.21.1" + asynchronous-handlers: "npm:^1.0.2" + n3: "npm:^2.0.1" + rdf-vocabulary: "npm:^1.0.0" + checksum: 10c0/1d2d07b7d811907bd991507a6e83463550bcdc5eee308b29a587d805993c1e3ee152afac98affd420b132ecd5c3da4e3148cf807588bdafc3f41e49ef351192d + languageName: node + linkType: hard + "postcss@npm:^8.5.3": version: 8.5.6 resolution: "postcss@npm:8.5.6" @@ -12966,14 +13000,14 @@ __metadata: languageName: node linkType: hard -"swipl-wasm@npm:4.0.13": - version: 4.0.13 - resolution: "swipl-wasm@npm:4.0.13" +"swipl-wasm@npm:6.0.0": + version: 6.0.0 + resolution: "swipl-wasm@npm:6.0.0" dependencies: - "@types/emscripten": "npm:^1.39.13" + "@types/emscripten": "npm:^1.41.5" bin: swipl-generate: dist/bin/index.js - checksum: 10c0/ea61942ceb60883bddd213d029ce21bb7010860621b69dfd1e13209b17948c90bc3c422153244a8662f5eb89275135b3905be5274d827ce4f2effac6a383f038 + checksum: 10c0/89db476938488e4a24e417ebc813a427f204520b9829383995e8ca26a1796859fcc6f32481acbeaac95b78e7d6e377dbffb2096cefa6dc18870b71902ad1eeb8 languageName: node linkType: hard