diff --git a/demo/demoEngine.ts b/demo/demoEngine.ts new file mode 100644 index 0000000..600a683 --- /dev/null +++ b/demo/demoEngine.ts @@ -0,0 +1,48 @@ +import { ContainerUCRulesStorage, PolicyExecutor, UCRulesStorage, UconEnforcementDecision, UcpPatternEnforcement, UcpPlugin } from "@solidlab/ucp" +import { App, AppRunner, AppRunnerInput } from "@solid/community-server"; +import * as Path from 'path'; +import { EyeJsReasoner, readText } from "koreografeye"; + +type Demo = { + css: App, + ucpEngine: UconEnforcementDecision, + storage: UCRulesStorage +} + +export async function initEngine(portNumber = 3123): Promise { + const containerURL = `http://localhost:${portNumber}/` + // code to start css server somewhere + const css = await configSolidServer(portNumber) + + // initiating + // load plugin(s) + const plugins = { "http://example.org/dataUsage": new UcpPlugin() } + // Initialise koreografeye policy executor + const policyExecutor = new PolicyExecutor(plugins) + // load N3 Rules from a directory + const rulesDirectory = Path.join(__dirname) + const n3Rules: string[] = [readText(Path.join(rulesDirectory, 'purpose-time.n3'))!] + // Initialise Usage Control Rule Storage + const uconRulesStorage = new ContainerUCRulesStorage(containerURL); + const ucpEngine = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ + "--quiet", + "--nope", + "--pass"]), policyExecutor) + return {css, ucpEngine, storage: uconRulesStorage} +} + + +// utils +async function configSolidServer(port: number): Promise { + const input: AppRunnerInput = { + config: Path.join(__dirname, "memory.json"), + variableBindings: { + 'urn:solid-server:default:variable:port': port, + 'urn:solid-server:default:variable:baseUrl': `http://localhost:${port}/`, + 'urn:solid-server:default:variable:loggingLevel': 'warn', + } + } + const cssRunner = await new AppRunner().create(input) + return cssRunner +} + diff --git a/demo/main.ts b/demo/main.ts new file mode 100644 index 0000000..a1e0a76 --- /dev/null +++ b/demo/main.ts @@ -0,0 +1,41 @@ +import { createContext, storeToString } from "@solidlab/ucp"; +import { initEngine } from "./demoEngine"; +import { demoPolicy } from "./policyCreation"; + +async function main(){ + const {css, ucpEngine, storage} = await initEngine(); + + const target = "urn:wout:age" + const requestingParty = "https://pod.rubendedecker.be/profile/card#me" + + const request = { + subject: requestingParty, + action: ["http://www.w3.org/ns/auth/acl#Read"], + resource: target, + owner: "https://pod.woutslabbinck.com/profile/card#me" + } + const policy = demoPolicy(target, requestingParty) + // start server + await css.start(); + + + const noAccessModes = await ucpEngine.calculateAccessModes(request); + console.log("Access modes retrieved when no policy in storage", noAccessModes); + + // Add following Policy to storage: + // Wout gives access to Ruben regarding Wout his age + // constraints: two weeks from now on + purpose= "age-verification" + await storage.addRule(policy.representation) + + + const accessModes = await ucpEngine.calculateAccessModes(request); + console.log("Access modes retrieved when policy in storage", accessModes); + + // debug logs + // console.log(storeToString(createContext(request))); // Note: request -> which is also what is expected in the uma server at that stage + // console.log(storeToString(policy.representation)); // Note: log ODRL rule + console.log("Right now 'storage' is used to PUT the demo policy to 'http://localhost:3123/'. A normal HTTP request can also be used to do that."); + +} + +main() \ No newline at end of file diff --git a/demo/memory.json b/demo/memory.json new file mode 100644 index 0000000..5bf225e --- /dev/null +++ b/demo/memory.json @@ -0,0 +1,37 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld", + "import": [ + "css:config/app/init/initialize-root.json", + "css:config/app/main/default.json", + "css:config/app/variables/default.json", + "css:config/http/handler/default.json", + "css:config/http/middleware/default.json", + "css:config/http/notifications/all.json", + "css:config/http/server-factory/http.json", + "css:config/http/static/default.json", + "css:config/identity/access/public.json", + "css:config/identity/email/default.json", + "css:config/identity/handler/disabled.json", + "css:config/identity/oidc/disabled.json", + "css:config/identity/ownership/token.json", + "css:config/identity/pod/static.json", + "css:config/ldp/authentication/dpop-bearer.json", + "css:config/ldp/authorization/webacl.json", + "css:config/ldp/handler/default.json", + "css:config/ldp/metadata-parser/default.json", + "css:config/ldp/metadata-writer/default.json", + "css:config/ldp/modes/default.json", + "css:config/storage/backend/memory.json", + "css:config/storage/key-value/resource-store.json", + "css:config/storage/location/root.json", + "css:config/storage/middleware/default.json", + "css:config/util/auxiliary/acl.json", + "css:config/util/identifiers/suffix.json", + "css:config/util/index/default.json", + "css:config/util/logging/winston.json", + "css:config/util/representation-conversion/default.json", + "css:config/util/resource-locker/memory.json", + "css:config/util/variables/default.json" + ], + "@graph": [] + } \ No newline at end of file diff --git a/demo/policyCreation.ts b/demo/policyCreation.ts new file mode 100644 index 0000000..48d4844 --- /dev/null +++ b/demo/policyCreation.ts @@ -0,0 +1,58 @@ +/** + * create ODRL policy with three constraints -> basically a function that prints that as output + - constraints + - start time + - end time + - purpose: string -> "age-verification" + - target: SHACL shape? + */ + +import { SimplePolicy, UCPPolicy, basicPolicy, storeToString } from '@solidlab/ucp' + +export const agePurpose = "age-verification" +/** + * Create demo ODRL policy: + * + * Read access for requestingparty to target under constraints (temporal + purpose) + * @param targetIRI - an IRI representing the target -> the resource + * @param requestingPartyIRI - an IRI representing the entity requesting access + * @param constraints + */ +export function demoPolicy(targetIRI, requestingPartyIRI, constraints?: { startDate?: Date, endDate?: Date, purpose?: string }): SimplePolicy { + const startDate = constraints?.startDate ?? new Date() + const endDate = constraints?.endDate ?? new Date(startDate.valueOf()+ 86_400 * 14 * 1000) + const purpose = constraints?.purpose ?? agePurpose + + const policy: UCPPolicy = { + rules: [{ + requestingParty: requestingPartyIRI, + action: "http://www.w3.org/ns/odrl/2/read", // msut be odrl + resource: targetIRI, + owner: "https://pod.woutslabbinck.com/profile/card#me", // can lead to bugs, depending on whether we use owner or not + constraints: [ + { + type: "temporal", + operator: "http://www.w3.org/ns/odrl/2/gt", + value: startDate + }, + { + type: "temporal", + operator: "http://www.w3.org/ns/odrl/2/lt", + value: endDate + }, + { + type: "purpose", + operator: "http://www.w3.org/ns/odrl/2/eq", + value: purpose + }, + ] + }] + } + + const policyObject = basicPolicy(policy); + // console.log(storeToString(policyObject.representation)); + return policyObject +} + +// example: Wout gives access to Ruben regarding Wout his age +// demoPolicy("urn:wout:age", "https://pod.rubendedecker.be/profile/card#me") \ No newline at end of file diff --git a/demo/purpose-time.n3 b/demo/purpose-time.n3 new file mode 100644 index 0000000..ab433ab --- /dev/null +++ b/demo/purpose-time.n3 @@ -0,0 +1,72 @@ +@prefix xsd: . +@prefix odrl: . +@prefix : . +@prefix acl: . +@prefix fno: . +@prefix log: . +@prefix string: . +@prefix list: . +@prefix time: . +@prefix math: . +# Create ODRL Rule: doesn't exist (`odrl:write` and `odrl:append` are deprecated) +{ :currentTime :is ?currentTime } <= { "" time:localTime ?currentTime }. + +# Read ODRL rule +{ + ?permission a odrl:Permission; + odrl:action ?action ; + odrl:target ?targetResource ; + odrl:assignee ?requestedParty; + odrl:assigner ?resourceOwner . + + ?action list:in (odrl:use odrl:read) . # multiple options + + # context of a request + ?context + :resourceOwner ?resourceOwner; + :requestingParty ?requestedParty; + :target ?targetResource; + :requestPermission acl:Read. + + :uuid5 log:uuid ?uuidStringdataUsagePolicyExecution. + ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. + ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . + + # Constraint checking + # number of constraints must be two (temporal needs lower and upper bound) + (?template {?permission odrl:constraint _:s} ?L) log:collectAllIn ?SCOPE. + ?L list:length 3 . + + :currentTime :is ?currentTime . + + # lower bound + ?permission odrl:constraint ?lowerBoundIRI . + ?lowerBoundIRI odrl:leftOperand odrl:dateTime ; + odrl:operator odrl:gt ; + odrl:rightOperand ?lowerBound . + + # greater bound + ?permission odrl:constraint ?upperBoundIRI . + ?upperBoundIRI odrl:leftOperand odrl:dateTime ; + odrl:operator odrl:lt ; + odrl:rightOperand ?upperBound . + + # ?lowerBound < ?currentTime < ?upperBound + ?currentTime math:greaterThan ?lowerBound . + ?currentTime math:lessThan ?upperBound . + + # purpose constraint + ?permission odrl:constraint ?purposeConstraint . + ?purposeConstraint odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand ?purposeValue + # Note: nothing is done with the purpose right now TODO: needs checking +} => +{ + ?dataUsagePolicyExecution a fno:Execution; + fno:executes ; + :accessModesAllowed acl:Read. + ?dataUsagePolicyExecution ?currentTime . + +}. + diff --git a/packages/ucp/src/policy/ODRL.ts b/packages/ucp/src/policy/ODRL.ts index f56a3f5..4828278 100644 --- a/packages/ucp/src/policy/ODRL.ts +++ b/packages/ucp/src/policy/ODRL.ts @@ -83,6 +83,14 @@ export function createConstraintQuads(constraint: UCPConstraint, ruleIRI?: strin quads.push(quad(namedNode(ruleIRI), ODRL.terms.constraint, namedNode(constraintIRI))) } break; + case "purpose": + quads.push(quad(namedNode(constraintIRI), ODRL.terms.leftOperand, ODRL.terms.purpose)); + quads.push(quad(namedNode(constraintIRI), ODRL.terms.operator, namedNode(constraint.operator))); + quads.push(quad(namedNode(constraintIRI), ODRL.terms.rightOperand, literal(constraint.value))); + if (ruleIRI) { + quads.push(quad(namedNode(ruleIRI), ODRL.terms.constraint, namedNode(constraintIRI))) + } + break; default: console.log("Can not create constraint as the type is not understood:", constraint.type); break; diff --git a/packages/ucp/src/util/Vocabularies.ts b/packages/ucp/src/util/Vocabularies.ts index d86b70f..fb82fdd 100644 --- a/packages/ucp/src/util/Vocabularies.ts +++ b/packages/ucp/src/util/Vocabularies.ts @@ -122,6 +122,7 @@ export const ODRL = createVocabulary( 'operator', 'permission', 'dateTime', + 'purpose', 'leftOperand', 'rightOperand' )