Skip to content
107 changes: 107 additions & 0 deletions demo/dynamic-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Parser, Writer } from 'n3';
import { materializePolicy, ODRLEngineMultipleSteps, ODRLEvaluator, resourceToOptimisedTurtle } from "../dist/index";


// Variant on test case 036: Read request from Alice to resource X returns into yes (temporal lt) (Alice Request Read X).
// https://github.com/SolidLabResearch/ODRL-Test-Suite/blob/main/data/test_cases/testcase-036-alice-read-x.ttl
const policy = `
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
@prefix ex: <http://example.org/> .
@prefix dct: <http://purl.org/dc/terms/> .
@prefix odrluc: <https://w3id.org/force/odrlproposed#> .

<urn:uuid:32127a3f-5296-4cc6-b9d6-ef6c647a721d> a odrl:Set ;
odrl:uid <urn:uuid:32127a3f-5296-4cc6-b9d6-ef6c647a721d> ;
dct:description "ALICE may READ resource X when it is before 'ex:updateValue' (see sotw)." ;
dct:source <https://github.com/SolidLabResearch/ODRL-Test-Suite/> ;
odrl:permission <urn:uuid:d6ab4a38-68fb-418e-8af5-e77649a2187a> .

<urn:uuid:d6ab4a38-68fb-418e-8af5-e77649a2187a> a odrl:Permission ;
odrl:assignee ex:alice ;
odrl:action odrl:read ;
odrl:target ex:x ;
odrl:constraint <urn:uuid:constraint:86526f9b-57c2-4c94-b079-9762fec562f1> .

<urn:uuid:constraint:86526f9b-57c2-4c94-b079-9762fec562f1> odrl:leftOperand odrl:dateTime ;
odrl:operator odrl:lt ;
odrl:rightOperandReference ex:operandReference1 .

ex:operandReference1 a odrluc:OperandReference ;
odrluc:reference ex:externalSource ;
odrluc:path ex:updatedValue .
`

// state of the world -> with external value indicating the time
const sotw = `
@prefix ex: <http://example.org/> .
@prefix temp: <http://example.com/request/> .
@prefix dct: <http://purl.org/dc/terms/> .

<urn:uuid:192620fa-06d9-447b-adbd-bd1ece4f9b12> a ex:Sotw ;
ex:includes temp:currentTime .

temp:currentTime dct:issued "2017-02-12T11:20:10.999Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .

# external value that will be materialized in the policy
ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
`

const request = `
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
@prefix ex: <http://example.org/> .
@prefix dct: <http://purl.org/dc/terms/> .

<urn:uuid:1bafee59-006c-46a3-810c-5d176b4be364> a odrl:Request ;
odrl:uid <urn:uuid:1bafee59-006c-46a3-810c-5d176b4be364> ;
dct:description "Requesting Party ALICE requests to READ resource X." ;
odrl:permission <urn:uuid:186be541-5857-4ce3-9f03-1a274f16bf59> .

<urn:uuid:186be541-5857-4ce3-9f03-1a274f16bf59> a odrl:Permission ;
odrl:assignee ex:alice ;
odrl:action odrl:read ;
odrl:target ex:x .
`

async function main() {
const parser = new Parser()
const writer = new Writer()

const odrlDynamicPolicyQuads = parser.parse(policy)

const odrlRequestQuads = parser.parse(request)
const stateOfTheWorldQuads = parser.parse(sotw)

const instantiatedPolicyQuads = materializePolicy(odrlDynamicPolicyQuads, stateOfTheWorldQuads)

console.log("Instantiated Policy:")
console.log(writer.quadsToString(instantiatedPolicyQuads));


// reasoning over dynamic policy
const evaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps());
const reasoningResult = await evaluator.evaluate(
odrlDynamicPolicyQuads,
odrlRequestQuads,
stateOfTheWorldQuads)

const output = writer.quadsToString(reasoningResult);
console.log("Compliance Report")
console.log(output);

const prefixes = {
'odrl': 'http://www.w3.org/ns/odrl/2/',
'ex': 'http://example.org/',
'temp': 'http://example.com/request/',
'dct': 'http://purl.org/dc/terms/',
'xsd': 'http://www.w3.org/2001/XMLSchema#',
'foaf': 'http://xmlns.com/foaf/0.1/',
'report': 'https://w3id.org/force/compliance-report#'
}

// created report with N3
// @ts-ignore
console.log(resourceToOptimisedTurtle(reasoningResult, prefixes));

}
main()

71 changes: 66 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"clean": "rm -rf ./dist",
"demo:test-engine": "ts-node demo/test-n3-engine.ts",
"demo:test-evaluator": "ts-node demo/test-n3-evaluator.ts",
"demo:dynamic-policy": "ts-node demo/dynamic-policy.ts",
"prepare": "npm run build",
"release": "npm run build && npm publish --access public",
"test": "jest"
Expand All @@ -39,8 +40,8 @@
"@types/jest": "^29.5.12",
"@types/tmp": "^0.2.6",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"jest-rdf": "^2.0.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
Expand All @@ -50,9 +51,10 @@
"eyereasoner": "^16.18.4",
"n3": "^1.20.4",
"rdf-isomorphic": "^1.3.1",
"rdf-lens": "^1.3.5",
"rdf-parse": "^3.0.0",
"rdf-store-stream": "^2.0.1",
"rdf-vocabulary": "^1.0.0",
"rdf-vocabulary": "^1.0.1",
"streamify-string": "^1.0.1",
"tmp": "^0.2.3",
"uuidv4": "^6.2.13"
Expand Down
66 changes: 66 additions & 0 deletions src/evaluator/DynamicConstraint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Quad, Term } from "@rdfjs/types";
import { Literal, NamedNode, Store } from 'n3';
import { BasicLensM, CBDLens, Cont, pred, ShaclPath } from "rdf-lens";
import { ODRL, ODRLUC, RDF } from "../util/Vocabularies";

// use RDF lens instead of clownface for SHACL Property Paths: https://ceur-ws.org/Vol-3759/paper13.pdf
export const pathLens = pred(ODRLUC.terms.path)
.one()
.then(ShaclPath);

// Utility function to extract the SHACL property path of the Dynamic ODRL Policy using `odrluc:path`
export function getPath(id: Term, quads: Quad[]): BasicLensM<Cont, Term> {
return pathLens.execute({ id, quads }).mapAll((x) => x.id);
}

// Utility function to extract the value from an external resource using a SHACL Property path
export function usePath(id: Term, quads: Quad[], lens: BasicLensM<Cont, Term>): Term[] {
return lens.execute({ id, quads })
}

// implements dynamic policy constraint algorithm
// algorithm to fetch value using SHACL path from sotw in policy
// will only materialize when RightOperandReference class is present
export function materializePolicy(dynamicPolicy: Quad[], stateOfTheWorld: Quad[]): Quad[] {
const odrlPolicyStore = new Store(dynamicPolicy)
const odrlDynamicPolicyStore = new Store(dynamicPolicy)

// get all constraints containing references
const operandReferenceNodes = odrlDynamicPolicyStore.getSubjects(RDF.terms.type, ODRLUC.terms.OperandReference, null);

for (const operandReferenceNode of operandReferenceNodes) {
// ODRL constraint that has that given rightOperandReference
const constraintNodes = odrlDynamicPolicyStore.getSubjects(ODRL.terms.rightOperandReference, operandReferenceNode, null)

// external resource
const externalResource = odrlDynamicPolicyStore.getObjects(operandReferenceNode, ODRLUC.terms.reference, null)[0]
if (!externalResource) {
throw Error("There is no source present in the Dynamic Policy Constraint.")
}

// SHACL Property path
const lens = getPath(operandReferenceNode, dynamicPolicy);

// extract SHACL Property Path using RDF-Lens
const getValue = usePath(externalResource, stateOfTheWorld, lens);
const instantiatedValue = getValue[0]; // according to the algorithm (see paper), there should be only one term

if (!(instantiatedValue instanceof NamedNode || instantiatedValue instanceof Literal)) {
// Now instantiatedValue is a valid value
throw Error("Instantiated value is not a proper value.");
}
for (const constraintNode of constraintNodes) {
// remove rightOperandRefrence triple from constraint
odrlPolicyStore.removeQuad(constraintNode, ODRL.terms.rightOperandReference, operandReferenceNode)
// add materialized rightOperand
odrlPolicyStore.addQuad(constraintNode, ODRL.terms.rightOperand, instantiatedValue)
}

// remove OperandReference triples from Policy store
const operandReferenceQuads = CBDLens.execute({id:operandReferenceNode, quads: dynamicPolicy})
odrlPolicyStore.removeQuads(operandReferenceQuads)
}
return odrlPolicyStore.getQuads(null, null, null, null)
}


5 changes: 4 additions & 1 deletion src/evaluator/Evaluate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Quad } from '@rdfjs/types';
import { ODRLEngine, Engine} from './Engine';
import { RDFValidator, TripleTermValidator, SHACLValidator } from './Validate';
import { materializePolicy } from './DynamicConstraint';

export interface Evaluator {
/**
Expand Down Expand Up @@ -64,9 +65,11 @@ export class ODRLEvaluator implements Evaluator {

// if there are compact policies -> they must be expanded (also reocmmended by ODRL § 2.7.1 Compact Policy)

// handle dynamic policies
const instantiatedPolicies = materializePolicy(policy, state);
// evaluate
// the evaluation will result into a conformance report
const evaluation = await this.engine.evaluate([...policy, ...request, ...state])
const evaluation = await this.engine.evaluate([...instantiatedPolicies, ...request, ...state])

// TODO: think about when the report can be empty
// does it always mean there is not enough information?
Expand Down
12 changes: 1 addition & 11 deletions src/index.browser.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1 @@
export * from './evaluator/Engine'
export * from './evaluator/Evaluate'
export * from './evaluator/Validate'

export * from './reasoner/EyeJsReasoner'
// export * from './reasoner/EyeReasoner'
export * from './reasoner/Reasoner'

export * from './util/Notation3Util'
// export * from './util/RDFUtil'
export * from './rules/Rules'
export * from "./index.core"
12 changes: 12 additions & 0 deletions src/index.core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export * from './evaluator/Engine'
export * from './evaluator/Evaluate'
export * from './evaluator/Validate'
export * from './evaluator/DynamicConstraint'

export * from './reasoner/EyeJsReasoner'
export * from './reasoner/Reasoner'

export * from './util/Notation3Util'
export * from './util/Vocabularies'

export * from './rules/Rules'
8 changes: 1 addition & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
export * from './evaluator/Engine'
export * from './evaluator/Evaluate'
export * from './evaluator/Validate'

export * from './reasoner/EyeJsReasoner'
export * from "./index.core"
export * from './reasoner/EyeReasoner'
export * from './reasoner/Reasoner'

export * from './util/Notation3Util'
export * from './util/RDFUtil'
2 changes: 1 addition & 1 deletion src/reasoner/EyeJsReasoner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { n3reasoner } from "eyereasoner/dist";
import { n3reasoner } from "eyereasoner";
import { Reasoner } from "./Reasoner";

export class EyeJsReasoner extends Reasoner {
Expand Down
Loading