From bc4528dbf48a62a99f5af5466b7c09becbe14245 Mon Sep 17 00:00:00 2001 From: Wouter Termont Date: Thu, 14 Mar 2024 11:28:40 +0100 Subject: [PATCH] chore: debugging & cleaunup Signed-off-by: Wouter Termont --- demo/demoEngine.ts | 48 --------------- demo/flow.ts | 129 ++++++++++++++++++++--------------------- demo/main.ts | 41 ------------- demo/memory.json | 37 ------------ demo/policyCreation.ts | 75 +++++++++++------------- 5 files changed, 98 insertions(+), 232 deletions(-) delete mode 100644 demo/demoEngine.ts delete mode 100644 demo/main.ts delete mode 100644 demo/memory.json diff --git a/demo/demoEngine.ts b/demo/demoEngine.ts deleted file mode 100644 index 600a683..0000000 --- a/demo/demoEngine.ts +++ /dev/null @@ -1,48 +0,0 @@ -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/flow.ts b/demo/flow.ts index dd0cc5b..e6d8659 100644 --- a/demo/flow.ts +++ b/demo/flow.ts @@ -1,7 +1,11 @@ /* eslint-disable max-len */ import { fetch } from 'cross-fetch'; -import { Parser, Store } from 'n3'; +import { Parser, Writer, Store } from 'n3'; +import { demoPolicy } from "./policyCreation"; + +const parser = new Parser(); +const writer = new Writer(); const terms = { solid: { @@ -14,64 +18,31 @@ const terms = { filters: { bday: 'http://localhost:3000/catalog/public/filters/bday', age: 'http://localhost:3000/catalog/public/filters/age', + }, + views: { + bday: 'http://localhost:3000/ruben/private/derived/bday', + age: 'http://localhost:3000/ruben/private/derived/age', + }, + agents: { + ruben: 'http://localhost:3000/ruben/profile/card#me', + vendor: 'http://localhost:3000/demo/public/vendor', + present: 'http://localhost:3000/demo/public/bday-app', + }, + scopes: { + read: 'urn:example:css:modes:read', } } -const parser = new Parser(); - -const privateRequest = async (resource_id: string, tokenEndpoint: string) => { - const claim_token = "http://localhost:3000/demo/public/bday-app" - - const content = { - grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket', - claim_token: encodeURIComponent(claim_token), - claim_token_format: 'urn:solidlab:uma:claims:formats:webid', - // ticket, - permissions: [{ - resource_id, - resource_scopes: [ 'urn:example:css:modes:read', 'urn:example:css:modes:write' ], - }] - }; - - const asRequestResponse = await fetch(tokenEndpoint, { - method: "POST", - headers: { - "content-type":"application/json" - }, - body: JSON.stringify(content), - }); - - const asResponse = await asRequestResponse.json(); - const tokenResponse = await fetch(resource_id, { - headers: { 'Authorization': `${asResponse.token_type} ${asResponse.access_token}` } - }); -} - -const log = (msg: string, obj?: any) => { - console.log(''); - console.log(msg); - if (obj) { - console.log('\n'); - console.log(obj); - } -} - -function parseJwt (token:string) { - return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); -} - async function main() { log(`Alright, so, for the demo ...`); - const webId = 'http://localhost:3000/ruben/profile/card#me'; - - log(`Ruben V., a.k.a. <${webId}>, has some private data in .`); + log(`Ruben V., a.k.a. <${terms.agents.ruben}>, has some private data in .`); log(`Of course, he does not want everyone to be able to see all of his private data when they need just one aspect of it. Therefore, Ruben has installed two Views on his data, based on SPARQL filters from a public Catalog. (When and how this is done is out-of-scope for now.)`); - const webIdData = new Store(parser.parse(await (await fetch(webId)).text())); - const viewIndex = webIdData.getObjects(webId, terms.solid.viewIndex, null)[0].value; + const webIdData = new Store(parser.parse(await (await fetch(terms.agents.ruben)).text())); + const viewIndex = webIdData.getObjects(terms.agents.ruben, terms.solid.viewIndex, null)[0].value; const views = Object.fromEntries(webIdData.getObjects(viewIndex, terms.solid.entry, null).map(entry => { const filter = webIdData.getObjects(entry, terms.solid.filter, null)[0].value; const location = webIdData.getObjects(entry, terms.solid.location, null)[0].value; @@ -83,11 +54,11 @@ async function main() { log(`(1) <${views[terms.filters.bday]}> filters out his birth date, according to the <${terms.filters.bday}> filter`); log(`(2) <${views[terms.filters.age]}> derives his age, according to the <${terms.filters.bday}> filter`); - const policyDir = 'http://localhost:3000/ruben/settings/policies/'; + const policyContainer = 'http://localhost:3000/ruben/settings/policies/'; - log(`Access to Ruben's data is based on policies he manages through his Authz Companion app, and which are stored in <${policyDir}>. (This is, of course, not publicly known.)`); + log(`Access to Ruben's data is based on policies he manages through his Authz Companion app, and which are stored in <${policyContainer}>. (This is, of course, not publicly known.)`); - const umaServer = webIdData.getObjects(webId, terms.solid.umaServer, null)[0].value; + const umaServer = webIdData.getObjects(terms.agents.ruben, terms.solid.umaServer, null)[0].value; const configUrl = new URL('.well-known/uma2-configuration', umaServer); const umaConfig = await (await fetch(configUrl)).json(); const tokenEndpoint = umaConfig.token_endpoint; @@ -101,17 +72,29 @@ async function main() { log(`Having been notified in some way of the access request, Ruben could go to his Authz Companion app, and add a policy allowing the requested access.`); - const privateResource = "http://localhost:3000/ruben/private/derived/age" - const claim_token = "http://localhost:3000/demo/public/bday-app" + const startDate = new Date(); + const endDate = new Date(startDate.valueOf() + 14 * 24 * 60 * 60 * 1000); + const purpose = 'age-verification' + const policy = demoPolicy(terms.views.age, terms.agents.vendor, { startDate, endDate, purpose }) + + const policyCreationResponse = await fetch(policyContainer, { + method: 'POST', + headers: { 'content-type': 'text/turtle' }, + body: writer.quadsToString(policy.representation.getQuads(null, null, null, null)) + }); + + if (policyCreationResponse.status !== 201) { log('Adding a policy did not succeed...'); throw 0; } + + log(`Now that the policy has been set, and the agent has possibly been notified in some way, the agent can try the access request again.`); const content = { - grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket', - claim_token: encodeURIComponent(claim_token), + // grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket', + claim_token: encodeURIComponent(terms.agents.vendor), claim_token_format: 'urn:solidlab:uma:claims:formats:webid', // ticket, permissions: [{ - resource_id: privateResource, - resource_scopes: [ 'urn:example:css:modes:read' ], + resource_id: terms.views.age, + resource_scopes: [ terms.scopes.read ], }] }; @@ -121,9 +104,7 @@ async function main() { const asRequestResponse = await fetch(tokenEndpoint, { method: "POST", - headers: { - "content-type":"application/json" - }, + headers: { "content-type":"application/json" }, body: JSON.stringify(content), }) @@ -140,13 +121,13 @@ async function main() { console.log({ ...asResponse, access_token: asResponse.access_token.slice(0,10).concat('...') }); console.log('\n'); - // for (const permission of decodedToken.permissions) { - // console.log(`Permissioned scopes for resource ${permission.resource_id}:`, permission.resource_scopes) - // } + for (const permission of decodedToken.permissions) { + console.log(`Permissioned scopes for resource ${permission.resource_id}:`, permission.resource_scopes) + } - console.log(`=== Trying to create private resource <${privateResource}> WITH access token.\n`); + console.log(`=== Trying to create private resource <${terms.views.age}> WITH access token.\n`); - const tokenResponse = await fetch(privateResource, { + const tokenResponse = await fetch(terms.views.age, { headers: { 'Authorization': `${asResponse.token_type} ${asResponse.access_token}` } }); @@ -157,3 +138,19 @@ async function main() { } main(); + + +/* Helper functions */ + +function parseJwt (token:string) { + return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); +} + +function log(msg: string, obj?: any) { + console.log(''); + console.log(msg); + if (obj) { + console.log('\n'); + console.log(obj); + } +} diff --git a/demo/main.ts b/demo/main.ts deleted file mode 100644 index a1e0a76..0000000 --- a/demo/main.ts +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 5bf225e..0000000 --- a/demo/memory.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@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 index 48d4844..326e949 100644 --- a/demo/policyCreation.ts +++ b/demo/policyCreation.ts @@ -1,58 +1,53 @@ -/** - * 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' +import { SimplePolicy, UCPPolicy, basicPolicy } 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 + * @param constraints - the temporal and purpuse constraints on the usage of the data */ -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 +export function demoPolicy( + targetIRI: string, + requestingPartyIRI: string, + constraints?: { + startDate?: Date, + endDate?: Date, + purpose?: string + } +): SimplePolicy { + const constraintList: any[] = []; + + if (constraints?.startDate) constraintList.push({ + type: 'temporal', + operator: 'http://www.w3.org/ns/odrl/2/gt', + value: constraints?.startDate, + }); + + if (constraints?.endDate) constraintList.push({ + type: 'temporal', + operator: 'http://www.w3.org/ns/odrl/2/lt', + value: constraints?.endDate, + }); + + if (constraints?.purpose) constraintList.push({ + type: 'purpose', + operator: 'http://www.w3.org/ns/odrl/2/eq', + value: constraints?.purpose, + }); 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 - }, - ] + action: "http://www.w3.org/ns/odrl/2/read", // ODRL action + requestingParty: requestingPartyIRI, + // owner: "https://pod.woutslabbinck.com/profile/card#me", // might error + constraints: constraintList }] } 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