From 4d790e273ab4101fcb643ae13d86abd754eb30f8 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 4 Jun 2025 10:41:16 +0200 Subject: [PATCH 1/6] feat: Clean up docker files and add EYE reasoner installation --- .dockerignore | 2 ++ Dockerfile | 34 +++++++++++++++------------------- docker-compose.debug.yml | 20 -------------------- docker-compose.yml | 15 +++++++-------- dockerize.sh | 2 +- 5 files changed, 25 insertions(+), 48 deletions(-) delete mode 100644 docker-compose.debug.yml diff --git a/.dockerignore b/.dockerignore index 6d68aeaf..06ce2515 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ **/.classpath **/.dockerignore **/.env +**/.idea **/.git **/.gitignore **/.project @@ -8,6 +9,7 @@ **/.toolstarget **/.vs **/.vscode +**/.yarn **/*.*proj.user **/*.dbmdl **/*.jfm diff --git a/Dockerfile b/Dockerfile index 3f500cc0..9343125c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,24 @@ -FROM node:20.0.0 +FROM node:22 ENV NODE_ENV=production -WORKDIR /usr/src/app -# COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"] -# RUN npm install -g yarn - -COPY . . -ENV YARN_VERSION 4.0.0 -RUN yarn policies set-version $YARN_VERSION +# Install EYE reasoner +RUN apt-get update \ + && apt-get install swi-prolog -y \ + && git clone https://github.com/eyereasoner/eye.git \ + && /eye/install.sh --prefix=/usr/local \ + && rm -r /eye -RUN corepack enable yarn -RUN yarn install -# COPY . . +WORKDIR /usr/src/app +COPY . . -RUN yarn build +# Install packages and build server +RUN corepack enable yarn \ + && yarn install \ + && yarn build \ + && chown -R node /usr/src/app EXPOSE 3000 EXPOSE 4000 -EXPOSE 4444 -EXPOSE 5123 -EXPOSE 8201 -EXPOSE 8202 -EXPOSE 8203 -RUN chown -R node /usr/src/app USER node -CMD ["yarn", "start:demo"] +CMD ["yarn", "start"] diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml deleted file mode 100644 index 09ed746a..00000000 --- a/docker-compose.debug.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.4' - -services: - solidtrustflows: - image: solidtrustflows - build: - context: . - dockerfile: ./Dockerfile - environment: - NODE_ENV: development - ports: - - 3000:3000 - - 4000:4000 - - 4444:4444 - - 5123:5123 - - 8201:8201 - - 8202:8202 - - 8203:8203 - - 9229:9229 - command: ["node", "--inspect=0.0.0.0:9229", "index.js"] diff --git a/docker-compose.yml b/docker-compose.yml index 70fe69ca..9c6e69a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,12 @@ -version: '3.4' +# The server is currently configured to use localhost as its base URL. +# When using multiple docker containers that want to interact with each other, 0.0.0.0 should be used. +# Because of this, and also because of potential external interactions with the server, +# it might be necessary in the future to add changes to support this. +# Newer versions of CSS can be configured to allow multiple base URLs which will be necessary then. services: - pacsoi-poc-1: - image: pacsoi-poc1 + solidtrustflows: + image: solidtrustflows build: context: . dockerfile: ./Dockerfile @@ -11,8 +15,3 @@ services: ports: - 3000:3000 - 4000:4000 - - 4444:4444 - - 5123:5123 - - 8201:8201 - - 8202:8202 - - 8203:8203 diff --git a/dockerize.sh b/dockerize.sh index 91607440..e6d9366d 100644 --- a/dockerize.sh +++ b/dockerize.sh @@ -1,2 +1,2 @@ #!/bin/bash -docker image build --pull --file './Dockerfile' --tag 'solidlab-trust-flows-demo:latest' --label 'com.microsoft.created-by=visual-studio-code' --network=host ./ +docker image build --pull --file './Dockerfile' --tag 'solidlab-trust-flows-demo:latest' --network=host . From 01f9259cb50b592434f921909d6793ab01fb5a11 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 4 Jun 2025 11:37:58 +0200 Subject: [PATCH 2/6] refactor: Remove unused configuration variables --- packages/uma/bin/demo.ts | 15 +---- packages/uma/bin/main.ts | 15 +---- packages/uma/bin/odrl.ts | 13 +--- packages/uma/config/base_default.json | 74 ---------------------- packages/uma/config/contract_default.json | 74 ---------------------- packages/uma/config/variables/default.json | 10 --- 6 files changed, 8 insertions(+), 193 deletions(-) delete mode 100644 packages/uma/config/base_default.json delete mode 100644 packages/uma/config/contract_default.json diff --git a/packages/uma/bin/demo.ts b/packages/uma/bin/demo.ts index 3e7ee5b0..e704569b 100644 --- a/packages/uma/bin/demo.ts +++ b/packages/uma/bin/demo.ts @@ -10,28 +10,20 @@ const baseUrl = `${protocol}://${host}:${port}/uma`; const rootDir = path.join(__dirname, '../'); export const launch: () => Promise = async () => { - - const variables: Record = {}; + const variables: Record = {}; variables['urn:uma:variables:port'] = port; - variables['urn:uma:variables:host'] = host; - variables['urn:uma:variables:protocol'] = protocol; variables['urn:uma:variables:baseUrl'] = baseUrl; // variables['urn:uma:variables:policyDir'] = path.join(rootDir, './config/rules/policy'); - variables['urn:uma:variables:rulesDir'] = path.join(rootDir, './config/rules/n3'); variables['urn:uma:variables:eyePath'] = 'eye'; - variables['urn:uma:variables:mainModulePath'] = rootDir; - variables['urn:uma:variables:customConfigPath'] = path.join(rootDir, './config/demo.json'); - - const mainModulePath = variables['urn:uma:variables:mainModulePath']; - const configPath = variables['urn:uma:variables:customConfigPath']; + const configPath = path.join(rootDir, './config/demo.json'); setGlobalLoggerFactory(new WinstonLoggerFactory('info')); const manager = await ComponentsManager.build({ - mainModulePath, + mainModulePath: rootDir, logLevel: 'silly', typeChecking: false, }); @@ -40,7 +32,6 @@ export const launch: () => Promise = async () => { const umaServer: ServerInitializer = await manager.instantiate('urn:uma:default:NodeHttpServer',{variables}); await umaServer.handleSafe(); - }; launch(); diff --git a/packages/uma/bin/main.ts b/packages/uma/bin/main.ts index 61c43a8a..37d7eebc 100644 --- a/packages/uma/bin/main.ts +++ b/packages/uma/bin/main.ts @@ -10,28 +10,20 @@ const baseUrl = `${protocol}://${host}:${port}/uma`; const rootDir = path.join(__dirname, '../'); export const launch: () => Promise = async () => { - - const variables: Record = {}; + const variables: Record = {}; variables['urn:uma:variables:port'] = port; - variables['urn:uma:variables:host'] = host; - variables['urn:uma:variables:protocol'] = protocol; variables['urn:uma:variables:baseUrl'] = baseUrl; variables['urn:uma:variables:policyDir'] = path.join(rootDir, './config/rules/policy'); - variables['urn:uma:variables:rulesDir'] = path.join(rootDir, './config/rules/n3'); variables['urn:uma:variables:eyePath'] = 'eye'; - variables['urn:uma:variables:mainModulePath'] = rootDir; - variables['urn:uma:variables:customConfigPath'] = path.join(rootDir, './config/default.json'); - - const mainModulePath = variables['urn:uma:variables:mainModulePath']; - const configPath = variables['urn:uma:variables:customConfigPath']; + const configPath = path.join(rootDir, './config/default.json'); setGlobalLoggerFactory(new WinstonLoggerFactory('info')); const manager = await ComponentsManager.build({ - mainModulePath, + mainModulePath: rootDir, logLevel: 'silly', typeChecking: false, }); @@ -40,7 +32,6 @@ export const launch: () => Promise = async () => { const umaServer: ServerInitializer = await manager.instantiate('urn:uma:default:NodeHttpServer',{variables}); await umaServer.handleSafe(); - }; launch(); diff --git a/packages/uma/bin/odrl.ts b/packages/uma/bin/odrl.ts index c4e1e64b..c7a39a6a 100644 --- a/packages/uma/bin/odrl.ts +++ b/packages/uma/bin/odrl.ts @@ -10,28 +10,20 @@ const baseUrl = `${protocol}://${host}:${port}/uma`; const rootDir = path.join(__dirname, '../'); export const launch: () => Promise = async () => { - const variables: Record = {}; variables['urn:uma:variables:port'] = port; - variables['urn:uma:variables:host'] = host; - variables['urn:uma:variables:protocol'] = protocol; variables['urn:uma:variables:baseUrl'] = baseUrl; variables['urn:uma:variables:policyDir'] = path.join(rootDir, './config/rules/odrl'); - // variables['urn:uma:variables:rulesDir'] = path.join(rootDir, './config/rules/n3'); variables['urn:uma:variables:eyePath'] = 'eye'; - variables['urn:uma:variables:mainModulePath'] = rootDir; - variables['urn:uma:variables:customConfigPath'] = path.join(rootDir, './config/odrl.json'); - - const mainModulePath = variables['urn:uma:variables:mainModulePath']; - const configPath = variables['urn:uma:variables:customConfigPath']; + const configPath = path.join(rootDir, './config/odrl.json'); setGlobalLoggerFactory(new WinstonLoggerFactory('info')); const manager = await ComponentsManager.build({ - mainModulePath, + mainModulePath: rootDir, logLevel: 'silly', typeChecking: false, }); @@ -40,7 +32,6 @@ export const launch: () => Promise = async () => { const umaServer: ServerInitializer = await manager.instantiate('urn:uma:default:NodeHttpServer',{variables}); await umaServer.handleSafe(); - }; launch(); diff --git a/packages/uma/config/base_default.json b/packages/uma/config/base_default.json deleted file mode 100644 index fce059b3..00000000 --- a/packages/uma/config/base_default.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld" - ], - "import": [ - "sai-uma:config/credentials/verifiers/default.json", - "sai-uma:config/dialog/negotiators/default.json", - "sai-uma:config/policies/authorizers/default.json", - "sai-uma:config/resources/storage/default.json", - "sai-uma:config/routes/discovery.json", - "sai-uma:config/routes/introspection.json", - "sai-uma:config/routes/keys.json", - "sai-uma:config/routes/resources.json", - "sai-uma:config/routes/tickets.json", - "sai-uma:config/routes/tokens.json", - "sai-uma:config/routes/log.json", - "sai-uma:config/routes/vc.json", - "sai-uma:config/routes/contract.json", - "sai-uma:config/tickets/storage/default.json", - "sai-uma:config/tickets/strategy/claim-elimination.json", - "sai-uma:config/tokens/factory/default.json", - "sai-uma:config/tokens/storage/default.json", - "sai-uma:config/variables/default.json" - ], - "@graph": [ - { - "@id": "urn:uma:default:NodeHttpServer", - "@type": "NodeHttpServer", - "port": { - "@id": "urn:uma:variables:port" - }, - "host": { - "@id": "urn:uma:variables:host" - }, - "nodeHttpStreamsHandler": { - "@id": "urn:uma:default:NodeHttpRequestResponseHandler", - "@type": "NodeHttpRequestResponseHandler", - "httpHandler": { - "@id": "urn:uma:default:CorsRequestHandler", - "@type": "CorsRequestHandler", - "handler": { - "@id": "urn:uma:default:RoutedHttpRequestHandler", - "@type": "RoutedHttpRequestHandler", - "handlerControllerList": [ - { - "@id": "urn:uma:default:HttpHandlerController", - "@type": "HttpHandlerController", - "label": "ControllerList", - "routes": [ - { "@id": "urn:uma:default:UmaConfigRoute" }, - { "@id": "urn:uma:default:JwksRoute" }, - { "@id": "urn:uma:default:TokenRoute" }, - { "@id": "urn:uma:default:PermissionRegistrationRoute" }, - { "@id": "urn:uma:default:ResourceRegistrationRoute" }, - { "@id": "urn:uma:default:ResourceRegistrationOpsRoute" }, - { "@id": "urn:uma:default:IntrospectionRoute" }, - { "@id": "urn:uma:default:LogRoute" }, - { "@id": "urn:uma:default:VCRoute" }, - { "@id": "urn:uma:default:ContractRoute" } - ] - } - ], - "defaultHandler": { - "@type": "DefaultRequestHandler" - } - } - } - } - }, - { - "comment": "Configuration for the UMA AS." - } - ] -} diff --git a/packages/uma/config/contract_default.json b/packages/uma/config/contract_default.json deleted file mode 100644 index 00532cca..00000000 --- a/packages/uma/config/contract_default.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld" - ], - "import": [ - "sai-uma:config/credentials/verifiers/default.json", - "sai-uma:config/dialog/negotiators/contract.json", - "sai-uma:config/policies/authorizers/default.json", - "sai-uma:config/resources/storage/default.json", - "sai-uma:config/routes/discovery.json", - "sai-uma:config/routes/introspection.json", - "sai-uma:config/routes/keys.json", - "sai-uma:config/routes/resources.json", - "sai-uma:config/routes/tickets.json", - "sai-uma:config/routes/tokens_contract.json", - "sai-uma:config/routes/log.json", - "sai-uma:config/routes/vc.json", - "sai-uma:config/routes/contract.json", - "sai-uma:config/tickets/storage/default.json", - "sai-uma:config/tickets/strategy/claim-elimination.json", - "sai-uma:config/tokens/factory/default.json", - "sai-uma:config/tokens/storage/default.json", - "sai-uma:config/variables/default.json" - ], - "@graph": [ - { - "@id": "urn:uma:default:NodeHttpServer", - "@type": "NodeHttpServer", - "port": { - "@id": "urn:uma:variables:port" - }, - "host": { - "@id": "urn:uma:variables:host" - }, - "nodeHttpStreamsHandler": { - "@id": "urn:uma:default:NodeHttpRequestResponseHandler", - "@type": "NodeHttpRequestResponseHandler", - "httpHandler": { - "@id": "urn:uma:default:CorsRequestHandler", - "@type": "CorsRequestHandler", - "handler": { - "@id": "urn:uma:default:RoutedHttpRequestHandler", - "@type": "RoutedHttpRequestHandler", - "handlerControllerList": [ - { - "@id": "urn:uma:default:HttpHandlerController", - "@type": "HttpHandlerController", - "label": "ControllerList", - "routes": [ - { "@id": "urn:uma:default:UmaConfigRoute" }, - { "@id": "urn:uma:default:JwksRoute" }, - { "@id": "urn:uma:default:DemoTokenRoute" }, - { "@id": "urn:uma:default:PermissionRegistrationRoute" }, - { "@id": "urn:uma:default:ResourceRegistrationRoute" }, - { "@id": "urn:uma:default:ResourceRegistrationOpsRoute" }, - { "@id": "urn:uma:default:IntrospectionRoute" }, - { "@id": "urn:uma:default:LogRoute" }, - { "@id": "urn:uma:default:VCRoute" }, - { "@id": "urn:uma:default:ContractRoute" } - ] - } - ], - "defaultHandler": { - "@type": "DefaultRequestHandler" - } - } - } - } - }, - { - "comment": "Configuration for the UMA AS." - } - ] -} diff --git a/packages/uma/config/variables/default.json b/packages/uma/config/variables/default.json index 7b677185..43b003c3 100644 --- a/packages/uma/config/variables/default.json +++ b/packages/uma/config/variables/default.json @@ -12,21 +12,11 @@ "@id": "urn:uma:variables:baseUrl", "@type": "Variable" }, - { - "comment": "Hostname of the server.", - "@id": "urn:uma:variables:host", - "@type": "Variable" - }, { "comment": "Path to the directory containing the policy rules.", "@id": "urn:uma:variables:policyDir", "@type": "Variable" }, - { - "comment": "Path to the directory containing the N3 rules.", - "@id": "urn:uma:variables:rulesDir", - "@type": "Variable" - }, { "comment": "Path of the local eye reasoner.", "@id": "urn:uma:variables:eyePath", From aa5251ea8fcc10253c2c89143e1ee73644d1941f Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 4 Jun 2025 13:24:46 +0200 Subject: [PATCH 3/6] refactor: Simplify UMA server configurations --- packages/uma/config/default.json | 12 +- .../config/dialog/negotiators/contract.json | 15 --- packages/uma/config/odrl.json | 117 ++---------------- .../uma/config/policies/authorizers/odrl.json | 20 --- .../uma/config/routes/tokens_contract.json | 17 --- 5 files changed, 16 insertions(+), 165 deletions(-) delete mode 100644 packages/uma/config/dialog/negotiators/contract.json delete mode 100644 packages/uma/config/policies/authorizers/odrl.json delete mode 100644 packages/uma/config/routes/tokens_contract.json diff --git a/packages/uma/config/default.json b/packages/uma/config/default.json index 6ecaa44a..c9a41ab8 100644 --- a/packages/uma/config/default.json +++ b/packages/uma/config/default.json @@ -5,7 +5,7 @@ ], "import": [ "sai-uma:config/credentials/verifiers/default.json", - "sai-uma:config/dialog/negotiators/contract.json", + "sai-uma:config/dialog/negotiators/default.json", "sai-uma:config/policies/authorizers/default.json", "sai-uma:config/resources/storage/default.json", "sai-uma:config/routes/discovery.json", @@ -13,7 +13,7 @@ "sai-uma:config/routes/keys.json", "sai-uma:config/routes/resources.json", "sai-uma:config/routes/tickets.json", - "sai-uma:config/routes/tokens_contract.json", + "sai-uma:config/routes/tokens.json", "sai-uma:config/routes/log.json", "sai-uma:config/routes/vc.json", "sai-uma:config/routes/contract.json", @@ -96,14 +96,14 @@ "routes": [ { "@id": "urn:uma:default:UmaConfigRoute" }, { "@id": "urn:uma:default:JwksRoute" }, - { "@id": "urn:uma:default:DemoTokenRoute" }, + { "@id": "urn:uma:default:TokenRoute" }, { "@id": "urn:uma:default:PermissionRegistrationRoute" }, { "@id": "urn:uma:default:ResourceRegistrationRoute" }, { "@id": "urn:uma:default:ResourceRegistrationOpsRoute" }, { "@id": "urn:uma:default:IntrospectionRoute" }, - { "@id": "urn:uma:default:LogRoute" }, - { "@id": "urn:uma:default:VCRoute" }, - { "@id": "urn:uma:default:ContractRoute" } + { "@id": "urn:uma:default:LogRoute" }, + { "@id": "urn:uma:default:VCRoute" }, + { "@id": "urn:uma:default:ContractRoute" } ], "defaultHandler": { "@type": "DefaultRequestHandler" diff --git a/packages/uma/config/dialog/negotiators/contract.json b/packages/uma/config/dialog/negotiators/contract.json deleted file mode 100644 index 7ed4a510..00000000 --- a/packages/uma/config/dialog/negotiators/contract.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld" - ], - "@graph": [ - { - "@id": "urn:uma:default:DemoNegotiator", - "@type": "ContractNegotiator", - "verifier": { "@id": "urn:uma:default:Verifier" }, - "ticketStore": { "@id": "urn:uma:default:TicketStore" }, - "ticketingStrategy": { "@id": "urn:uma:default:TicketingStrategy" }, - "tokenFactory": { "@id": "urn:uma:default:TokenFactory" } - } - ] -} \ No newline at end of file diff --git a/packages/uma/config/odrl.json b/packages/uma/config/odrl.json index ae2ffbcb..e1931379 100644 --- a/packages/uma/config/odrl.json +++ b/packages/uma/config/odrl.json @@ -1,119 +1,22 @@ { "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld" ], "import": [ - "sai-uma:config/credentials/verifiers/default.json", - "sai-uma:config/dialog/negotiators/contract.json", - "sai-uma:config/policies/authorizers/odrl.json", - "sai-uma:config/resources/storage/default.json", - "sai-uma:config/routes/discovery.json", - "sai-uma:config/routes/introspection.json", - "sai-uma:config/routes/keys.json", - "sai-uma:config/routes/resources.json", - "sai-uma:config/routes/tickets.json", - "sai-uma:config/routes/tokens_contract.json", - "sai-uma:config/routes/log.json", - "sai-uma:config/routes/vc.json", - "sai-uma:config/routes/contract.json", - "sai-uma:config/tickets/storage/default.json", - "sai-uma:config/tickets/strategy/immediate-authorizer.json", - "sai-uma:config/tokens/factory/default.json", - "sai-uma:config/tokens/storage/default.json", - "sai-uma:config/variables/default.json" + "sai-uma:config/default.json" ], "@graph": [ { - "@id": "urn:uma:default:NodeHttpServer", - "@type": "ServerInitializer", - "port": { - "@id": "urn:uma:variables:port" - }, - "serverFactory": { - "@id": "urn:uma:default:ServerFactory", - "@type": "BaseServerFactory", - "configurator": { - "comment": "Handles all request events from the server.", - "@id": "urn:uma:default:HandlerServerConfigurator", - "@type": "HandlerServerConfigurator", - "handler": { - "@id": "urn:uma:default:NodeHttpRequestResponseHandler" - } + "@id": "urn:uma:demo:Authorizer", + "@type": "Override", + "overrideInstance": { "@id": "urn:uma:default:Authorizer" }, + "overrideParameters": { + "@type": "OdrlAuthorizer", + "eyePath": { "@id": "urn:uma:variables:eyePath" }, + "policies": { + "@id": "urn:uma:default:RulesStorage" } } - }, - { - "@id": "urn:uma:default:HttpHandler", - "@type": "SequenceHandler", - "handlers": [ - { - "comment": "Adds all the necessary CORS headers.", - "@id": "urn:uma:default:CorsHandler", - "@type": "CorsHandler", - "options_methods": [ - "GET", - "HEAD", - "OPTIONS", - "POST", - "PUT", - "PATCH", - "DELETE" - ], - "options_credentials": true, - "options_preflightContinue": false, - "options_exposedHeaders": [ - "Allow", - "ETag", - "Last-Modified", - "Link", - "Location", - "Updates-Via", - "Www-Authenticate" - ] - }, - { - "@id": "urn:uma:default:NodeHttpRequestResponseHandler" - } - ] - }, - { - "@id": "urn:uma:default:NodeHttpRequestResponseHandler", - "@type": "NodeHttpRequestResponseHandler", - "targetExtractor": { - "@type": "BaseTargetExtractor", - "includeQueryString": true - }, - "httpHandler": { - "@id": "urm:uma:default:JsonHttpErrorHandler", - "@type": "JsonHttpErrorHandler", - "handler": { - "@id": "urm:uma:default:JsonFormHttpHandler", - "@type": "JsonFormHttpHandler", - "handler": { - "@id": "urn:uma:default:RoutedHttpRequestHandler", - "@type": "RoutedHttpRequestHandler", - "routes": [ - { "@id": "urn:uma:default:UmaConfigRoute" }, - { "@id": "urn:uma:default:JwksRoute" }, - { "@id": "urn:uma:default:DemoTokenRoute" }, - { "@id": "urn:uma:default:PermissionRegistrationRoute" }, - { "@id": "urn:uma:default:ResourceRegistrationRoute" }, - { "@id": "urn:uma:default:ResourceRegistrationOpsRoute" }, - { "@id": "urn:uma:default:IntrospectionRoute" }, - { "@id": "urn:uma:default:LogRoute" }, - { "@id": "urn:uma:default:VCRoute" }, - { "@id": "urn:uma:default:ContractRoute" } - ], - "defaultHandler": { - "@type": "DefaultRequestHandler" - } - } - } - } - }, - { - "comment": "Configuration for the UMA AS." } ] } diff --git a/packages/uma/config/policies/authorizers/odrl.json b/packages/uma/config/policies/authorizers/odrl.json deleted file mode 100644 index c42b2a13..00000000 --- a/packages/uma/config/policies/authorizers/odrl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/ucp/^0.0.0/components/context.jsonld" - ], - "@graph": [ - { - "@id": "urn:uma:default:Authorizer", - "@type": "OdrlAuthorizer", - "eyePath": { "@id": "urn:uma:variables:eyePath" }, - "policies": { - "@id": "urn:uma:default:RulesStorage", - "@type": "DirectoryUCRulesStorage", - "directoryPath": { - "@id": "urn:uma:variables:policyDir" - } - } - } - ] -} diff --git a/packages/uma/config/routes/tokens_contract.json b/packages/uma/config/routes/tokens_contract.json deleted file mode 100644 index 5100d97b..00000000 --- a/packages/uma/config/routes/tokens_contract.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma/^0.0.0/components/context.jsonld" - ], - "@graph": [ - { - "@id": "urn:uma:default:DemoTokenRoute", - "@type": "HttpHandlerRoute", - "methods": [ "POST" ], - "handler": { - "@type": "TokenRequestHandler", - "negotiator": { "@id": "urn:uma:default:DemoNegotiator" } - }, - "path": "/uma/token" - } - ] -} From 77cb5617b9747816791424da76ca8a42d51833c0 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Thu, 5 Jun 2025 14:57:12 +0200 Subject: [PATCH 4/6] refactor: Reduce negotiator duplication --- packages/uma/src/dialog/BaseNegotiator.ts | 11 +- packages/uma/src/dialog/ContractNegotiator.ts | 228 +++++++----------- 2 files changed, 91 insertions(+), 148 deletions(-) diff --git a/packages/uma/src/dialog/BaseNegotiator.ts b/packages/uma/src/dialog/BaseNegotiator.ts index a800ae81..673b62a7 100644 --- a/packages/uma/src/dialog/BaseNegotiator.ts +++ b/packages/uma/src/dialog/BaseNegotiator.ts @@ -80,6 +80,11 @@ export class BaseNegotiator implements Negotiator { } // ... on failure, deny if no solvable requirements + this.denyRequest(ticket); + } + + // TODO: + protected denyRequest(ticket: Ticket): never { const requiredClaims = ticket.required.map(req => Object.keys(req)); if (requiredClaims.length === 0) throw new ForbiddenHttpError(); @@ -101,7 +106,7 @@ export class BaseNegotiator implements Negotiator { * * @returns The Ticket describing the dialog at hand. */ - private async getTicket(input: DialogInput): Promise { + protected async getTicket(input: DialogInput): Promise { const { ticket, permissions } = input; if (ticket) { @@ -128,7 +133,7 @@ export class BaseNegotiator implements Negotiator { * * @returns An updated Ticket in which the Credentials have been validated. */ - private async processCredentials(input: DialogInput, ticket: Ticket): Promise { + protected async processCredentials(input: DialogInput, ticket: Ticket): Promise { const { claim_token: token, claim_token_format: format } = input; if (token || format) { @@ -152,7 +157,7 @@ export class BaseNegotiator implements Negotiator { * @throws An Error constructed with the provided constructor with the * provided message */ - private error(constructor: HttpErrorClass, message: string): never { + protected error(constructor: HttpErrorClass, message: string): never { this.logger.warn(message); throw new constructor(message); } diff --git a/packages/uma/src/dialog/ContractNegotiator.ts b/packages/uma/src/dialog/ContractNegotiator.ts index 4b78655b..ab30d720 100644 --- a/packages/uma/src/dialog/ContractNegotiator.ts +++ b/packages/uma/src/dialog/ContractNegotiator.ts @@ -1,32 +1,24 @@ -import { - BadRequestHttpError, - createErrorMessage, - ForbiddenHttpError, - getLoggerFor, - HttpErrorClass, - KeyValueStorage -} from '@solid/community-server'; -import { v4 } from 'uuid'; -import { AccessToken, Permission, Requirements } from '..'; +import { createErrorMessage, getLoggerFor, KeyValueStorage } from '@solid/community-server'; +import { Requirements } from '../credentials/Requirements'; import { Verifier } from '../credentials/verify/Verifier'; -import { NeedInfoError } from '../errors/NeedInfoError'; import { ContractManager } from '../policies/contracts/ContractManager'; import { TicketingStrategy } from '../ticketing/strategy/TicketingStrategy'; import { Ticket } from '../ticketing/Ticket'; +import { AccessToken } from '../tokens/AccessToken'; import { TokenFactory } from '../tokens/TokenFactory'; import { processRequestPermission, switchODRLandCSSPermission } from '../util/rdf/RequestProcessing'; import { Result, Success } from '../util/Result'; import { reType } from '../util/ReType'; import { convertStringOrJsonLdIdentifierToString, ODRLContract, StringOrJsonLdIdentifier } from '../views/Contract'; +import { Permission } from '../views/Permission'; +import { BaseNegotiator } from './BaseNegotiator'; import { DialogInput } from './Input'; -import { Negotiator } from './Negotiator'; import { DialogOutput } from './Output'; - /** * A mocked Negotiator for demonstration purposes to display contract negotiation */ -export class ContractNegotiator implements Negotiator { +export class ContractNegotiator extends BaseNegotiator { protected readonly logger = getLoggerFor(this); // protected readonly operationLogger = getOperationLogger(); @@ -45,7 +37,8 @@ export class ContractNegotiator implements Negotiator { protected ticketingStrategy: TicketingStrategy, protected tokenFactory: TokenFactory, ) { - this.logger.warn('The Contract Negotiator is for demonstration purposes only! DO NOT USE THIS IN PRODUCTION !!!') + super(verifier, ticketStore, ticketingStrategy, tokenFactory); + this.logger.warn('The Contract Negotiator is for demonstration purposes only! DO NOT USE THIS IN PRODUCTION !!!'); } /** @@ -68,12 +61,33 @@ export class ContractNegotiator implements Negotiator { const updatedTicket = await this.processCredentials(input, ticket); this.logger.debug(`Processed credentials ${JSON.stringify(updatedTicket)}`); - let result : Result + // TODO: + const result = await this.toContract(updatedTicket); + + if (result.success) { + // TODO: + return this.toResponse(result.value); + } + + // ... on failure, deny if no solvable requirements + this.denyRequest(ticket); + } + + /** + * Generates a contract based on the given ticket, + * or returns one previously made, + * and returns it as Success. + * + * In case the ticket is not resolved, + * the needed requirements will be returned as Failure. + */ + protected async toContract(ticket: Ticket): Promise> { + let result : Result; let contract: ODRLContract | undefined; // Check contract availability try { - contract = this.contractManager.findContract(updatedTicket) + contract = this.contractManager.findContract(ticket) } catch (e) { this.logger.debug(`Error: ${createErrorMessage(e)}`); } @@ -86,7 +100,7 @@ export class ContractNegotiator implements Negotiator { } else { this.logger.debug(`No existing contract discovered. Attempting to resolve ticket.`) - const resolved = await this.ticketingStrategy.resolveTicket(updatedTicket); + const resolved = await this.ticketingStrategy.resolveTicket(ticket); this.logger.debug(`Resolved ticket. ${JSON.stringify(resolved)}`); if (resolved.success) { @@ -103,144 +117,68 @@ export class ContractNegotiator implements Negotiator { result = resolved } } + return result; + } - if (result.success) { - let contract : ODRLContract = result.value - - this.logger.debug(JSON.stringify(contract, null, 2)) - - // todo: set resource scopes according to contract! - // Using a map first as the contract could return multiple entries for the same resource_id - // as it only allows 1 action per entry. - const permissionMap: Record = {}; - for (const permission of contract.permission) { - const id = convertStringOrJsonLdIdentifierToString(permission.target as StringOrJsonLdIdentifier); - if (!permissionMap[id]) { - permissionMap[id] = { - // We do not accept AssetCollections as targets of an UMA access request formatted as an ODRL request! - resource_id: id, - resource_scopes: [ // mapping from ODRL to internal CSS read permission - switchODRLandCSSPermission(convertStringOrJsonLdIdentifierToString(permission.action)) - ] - }; - } else { - permissionMap[id].resource_scopes.push( + // TODO: name + protected async toResponse(contract: ODRLContract): Promise { + + this.logger.debug(JSON.stringify(contract, null, 2)) + + // todo: set resource scopes according to contract! + // Using a map first as the contract could return multiple entries for the same resource_id + // as it only allows 1 action per entry. + const permissionMap: Record = {}; + for (const permission of contract.permission) { + const id = convertStringOrJsonLdIdentifierToString(permission.target as StringOrJsonLdIdentifier); + if (!permissionMap[id]) { + permissionMap[id] = { + // We do not accept AssetCollections as targets of an UMA access request formatted as an ODRL request! + resource_id: id, + resource_scopes: [ // mapping from ODRL to internal CSS read permission switchODRLandCSSPermission(convertStringOrJsonLdIdentifierToString(permission.action)) - ); - } + ] + }; + } else { + permissionMap[id].resource_scopes.push( + switchODRLandCSSPermission(convertStringOrJsonLdIdentifierToString(permission.action)) + ); } - let permissions: Permission[] = Object.values(permissionMap); - this.logger.debug(`granting permissions: ${JSON.stringify(permissions)}`); - - // Create response - const tokenContents: AccessToken = { permissions, contract } - - this.logger.debug(`resolved result ${JSON.stringify(result)}`); - - const { token, tokenType } = await this.tokenFactory.serialize(tokenContents); - - this.logger.debug(`Minted token ${JSON.stringify(token)}`); - - // TODO:: test logging - // this.operationLogger.addLogEntry(serializePolicyInstantiation()) - - // Store created instantiated policy (above contract variable) in the pod storage as an instantiated policy - // todo: dynamic URL - // todo: fix instantiated from url - // contract['http://www.w3.org/ns/prov#wasDerivedFrom'] = [ 'urn:ucp:be-gov:policy:d81b8118-af99-4ab3-b2a7-63f8477b6386 '] - // TODO: test-private error: this container does not exist and unauth does not have append perms - const instantiatedPolicyContainer = 'http://localhost:3000/ruben/settings/policies/instantiated/'; - const policyCreationResponse = await fetch(instantiatedPolicyContainer, { - method: 'POST', - headers: { 'content-type': 'application/ld+json' }, - body: JSON.stringify(contract, null, 2) - }); - - if (policyCreationResponse.status !== 201) { this.logger.warn('Adding a policy did not succeed...') } - - // TODO:: dynamic contract link to stored signed contract. - // If needed we can always embed here directly into the return JSON - return ({ - access_token: token, - token_type: tokenType, - }); } + let permissions: Permission[] = Object.values(permissionMap); + this.logger.debug(`granting permissions: ${JSON.stringify(permissions)}`); - // ... on failure, deny if no solvable requirements - const requiredClaims = ticket.required.map(req => Object.keys(req)); - if (requiredClaims.length === 0) throw new ForbiddenHttpError(); - - // ... require more info otherwise - const id = v4(); - this.ticketStore.set(id, ticket); - throw new NeedInfoError('Need more info to authorize request ...', id, { - required_claims: { - claim_token_format: requiredClaims, - }, - }); - } + // Create response + const tokenContents: AccessToken = { permissions, contract } - /** - * Helper function that retrieves a Ticket from the TicketStore if it exists, - * or initializes a new one otherwise. - * - * @param input - The input of the negotiation dialog. - * - * @returns The Ticket describing the dialog at hand. - */ - private async getTicket(input: DialogInput): Promise { - const { ticket, permission, permissions } = input; - - if (ticket) { - const stored = await this.ticketStore.get(ticket); - if (!stored) this.error(BadRequestHttpError, 'The provided ticket is not valid.'); + this.logger.debug(`resolved result ${JSON.stringify(contract)}`); - await this.ticketStore.delete(ticket); - return stored; - } + const { token, tokenType } = await this.tokenFactory.serialize(tokenContents); - if (!permissions) { - this.error(BadRequestHttpError, 'A token request without existing ticket should include requested permissions.'); - } + this.logger.debug(`Minted token ${JSON.stringify(token)}`); - return await this.ticketingStrategy.initializeTicket(permissions); - } + // TODO:: test logging + // this.operationLogger.addLogEntry(serializePolicyInstantiation()) - /** - * Helper function that checks for the presence of Credentials and, if present, - * verifies them and validates them in context of the provided Ticket. - * - * @param input - The input of the negotiation dialog. - * @param ticket - The Ticket against which to validate any Credentials. - * - * @returns An updated Ticket in which the Credentials have been validated. - */ - private async processCredentials(input: DialogInput, ticket: Ticket): Promise { - const { claim_token: token, claim_token_format: format } = input; - - if (token || format) { - if (!token) this.error(BadRequestHttpError, 'Request with a "claim_token_format" must contain a "claim_token".'); - if (!format) this.error(BadRequestHttpError, 'Request with a "claim_token" must contain a "claim_token_format".'); - - const claims = await this.verifier.verify({ token, format }); - - return await this.ticketingStrategy.validateClaims(ticket, claims); - } + // Store created instantiated policy (above contract variable) in the pod storage as an instantiated policy + // todo: dynamic URL + // todo: fix instantiated from url + // contract['http://www.w3.org/ns/prov#wasDerivedFrom'] = [ 'urn:ucp:be-gov:policy:d81b8118-af99-4ab3-b2a7-63f8477b6386 '] + // TODO: test-private error: this container does not exist and unauth does not have append perms + const instantiatedPolicyContainer = 'http://localhost:3000/ruben/settings/policies/instantiated/'; + const policyCreationResponse = await fetch(instantiatedPolicyContainer, { + method: 'POST', + headers: { 'content-type': 'application/ld+json' }, + body: JSON.stringify(contract, null, 2) + }); - return ticket; - } + if (policyCreationResponse.status !== 201) { this.logger.warn('Adding a policy did not succeed...') } - /** - * Logs and throws an error - * - * @param {HttpErrorClass} constructor - The error constructor. - * @param {string} message - The error message. - * - * @throws An Error constructed with the provided constructor with the - * provided message - */ - private error(constructor: HttpErrorClass, message: string): never { - this.logger.warn(message); - throw new constructor(message); + // TODO:: dynamic contract link to stored signed contract. + // If needed we can always embed here directly into the return JSON + return ({ + access_token: token, + token_type: tokenType, + }); } } From 246d1bf2ce6389a75eedcb94e8bfd45166ff8805 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Fri, 6 Jun 2025 10:45:42 +0200 Subject: [PATCH 5/6] refactor: Remove unused files --- package.json | 3 +- packages/css/src/index.ts | 2 - .../AccountSettingsStorageDescriber.ts | 76 ------ packages/ucp/docs/crud_full.n3 | 76 ------ packages/ucp/docs/getting-started.md | 174 -------------- packages/ucp/docs/log-engine.ts | 51 ---- packages/ucp/docs/simple-engine.ts | 52 ---- packages/ucp/rules/data-crud-rules.n3 | 69 ------ packages/ucp/rules/data-crud-temporal.n3 | 117 --------- packages/ucp/rules/log-usage-rule.n3 | 80 ------- packages/ucp/src/Explanation.ts | 147 ------------ packages/ucp/src/PolicyExecutor.ts | 75 ------ packages/ucp/src/Request.ts | 53 ----- packages/ucp/src/UMAinterfaces.ts | 37 --- packages/ucp/src/UcpPatternEnforcement.ts | 142 ----------- packages/ucp/src/index.ts | 20 -- packages/ucp/src/plugins/UCPLogPlugin.ts | 19 -- packages/ucp/src/plugins/UCPPlugin.ts | 13 - packages/ucp/src/util/Constants.ts | 3 - .../test/integration/ContainerRulesStorage.ts | 203 ---------------- packages/ucp/test/integration/CrudEngine.ts | 179 -------------- .../test/integration/CrudEngineTemporal.ts | 181 -------------- packages/ucp/test/integration/LogEngine.ts | 185 --------------- packages/ucp/test/memory.json | 37 --- packages/ucp/test/odrl.ts | 22 -- packages/ucp/test/util/Constants.ts | 67 ------ packages/ucp/test/util/StorageUtil.ts | 36 --- packages/ucp/test/util/Validation.ts | 143 ----------- packages/uma/config/rules/n3/crud.n3 | 163 ------------- packages/uma/config/rules/n3/purpose-time.n3 | 79 ------- packages/uma/src/index.ts | 1 - .../authorizers/PolicyBasedAuthorizer.ts | 136 ----------- scripts/test-ucp-enforcement.ts | 223 ------------------ 33 files changed, 1 insertion(+), 2863 deletions(-) delete mode 100644 packages/css/src/server/description/AccountSettingsStorageDescriber.ts delete mode 100644 packages/ucp/docs/crud_full.n3 delete mode 100644 packages/ucp/docs/getting-started.md delete mode 100644 packages/ucp/docs/log-engine.ts delete mode 100644 packages/ucp/docs/simple-engine.ts delete mode 100644 packages/ucp/rules/data-crud-rules.n3 delete mode 100644 packages/ucp/rules/data-crud-temporal.n3 delete mode 100644 packages/ucp/rules/log-usage-rule.n3 delete mode 100644 packages/ucp/src/Explanation.ts delete mode 100644 packages/ucp/src/PolicyExecutor.ts delete mode 100644 packages/ucp/src/Request.ts delete mode 100644 packages/ucp/src/UMAinterfaces.ts delete mode 100644 packages/ucp/src/UcpPatternEnforcement.ts delete mode 100644 packages/ucp/src/plugins/UCPLogPlugin.ts delete mode 100644 packages/ucp/src/plugins/UCPPlugin.ts delete mode 100644 packages/ucp/src/util/Constants.ts delete mode 100644 packages/ucp/test/integration/ContainerRulesStorage.ts delete mode 100644 packages/ucp/test/integration/CrudEngine.ts delete mode 100644 packages/ucp/test/integration/CrudEngineTemporal.ts delete mode 100644 packages/ucp/test/integration/LogEngine.ts delete mode 100644 packages/ucp/test/memory.json delete mode 100644 packages/ucp/test/odrl.ts delete mode 100644 packages/ucp/test/util/Constants.ts delete mode 100644 packages/ucp/test/util/StorageUtil.ts delete mode 100644 packages/ucp/test/util/Validation.ts delete mode 100644 packages/uma/config/rules/n3/crud.n3 delete mode 100644 packages/uma/config/rules/n3/purpose-time.n3 delete mode 100644 packages/uma/src/policies/authorizers/PolicyBasedAuthorizer.ts delete mode 100644 scripts/test-ucp-enforcement.ts diff --git a/package.json b/package.json index 8167682c..7fd60dee 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,9 @@ "script:public": "yarn exec ts-node ./scripts/test-public.ts", "script:private": "yarn exec ts-node ./scripts/test-private.ts", "script:registration": "yarn exec ts-node ./scripts/test-registration.ts", - "script:ucp-enforcement": "yarn exec ts-node ./scripts/test-ucp-enforcement.ts", "script:uma-ucp": "yarn exec ts-node ./scripts/test-uma-ucp.ts", "script:uma-odrl": "yarn exec ts-node ./scripts/test-uma-ODRL.ts", - "script:flow": "yarn run script:public && yarn run script:private && yarn run script:uma-ucp && yarn run script:registration && yarn run script:ucp-enforcement", + "script:flow": "yarn run script:public && yarn run script:private && yarn run script:uma-ucp && yarn run script:registration", "sync:list": "syncpack list-mismatches", "sync:fix": "syncpack fix-mismatches" }, diff --git a/packages/css/src/index.ts b/packages/css/src/index.ts index dc844c56..22113daf 100644 --- a/packages/css/src/index.ts +++ b/packages/css/src/index.ts @@ -10,8 +10,6 @@ export * from './identity/interaction/account/util/UmaAccountStore'; export * from './init/UmaSeededAccountInitializer'; -export * from './server/description/AccountSettingsStorageDescriber'; - export * from './server/middleware/JwksHandler'; export * from './uma/ResourceRegistrar'; diff --git a/packages/css/src/server/description/AccountSettingsStorageDescriber.ts b/packages/css/src/server/description/AccountSettingsStorageDescriber.ts deleted file mode 100644 index e3d606d4..00000000 --- a/packages/css/src/server/description/AccountSettingsStorageDescriber.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { NamedNode, Quad, Quad_Object, Term } from '@rdfjs/types'; -import type { AccountSettings, AccountStore, PodStore, ResourceIdentifier } from '@solid/community-server'; -import { StorageDescriber } from '@solid/community-server'; -import { DataFactory } from 'n3'; -import { stringToTerm } from 'rdf-string'; - -const {quad, namedNode} = DataFactory - -/** - * Adds triples to the storage description resource, based on the settings of - * the account that created the storage. - * - * The resource identifier of the storage is used as subject. - */ -export class AccountSettingsStorageDescriber extends StorageDescriber { - private readonly terms: ReadonlyMap; - - public constructor( - private podStore: PodStore, - private accountStore: AccountStore, - terms: Record, - ) { - super(); - - const termMap = new Map(); - for (const [ predicate, settingsKey ] of Object.entries(terms)) { - - const predTerm = stringToTerm(predicate); - if (predTerm.termType !== 'NamedNode') { - throw new Error('Predicate needs to be a named node.'); - } - - termMap.set(predTerm, settingsKey); - } - - this.terms = termMap; - } - - public async handle(target: ResourceIdentifier): Promise { - const subject = namedNode(target.path); - const pod = await this.podStore.findByBaseUrl(target.path); - if (!pod) throw new Error(`Cannot find pod for storage path ${target.path}`); - - const quads: Quad[] = []; - for await (const quad of this.generateTriples(subject, pod.accountId)) { - quads.push(quad); - } - - return quads; - } - - private async* generateTriples(subject: NamedNode, account: string): AsyncGenerator { - - for (const [ predicate, settingsKey ] of this.terms.entries()) { - - const settingsValue = await this.accountStore.getSetting(account, settingsKey); - if (settingsValue === undefined) continue; - - const objects = (Array.isArray(settingsValue) ? settingsValue : [ settingsValue ]).map((value): Quad_Object => { - let term: Term; - - try { - term = stringToTerm(`${value}`); - } catch { - term = stringToTerm(`"${value}"`); - } - - return term as Quad_Object; - }); - - for (const object of objects) { - yield quad(subject, predicate, object); - } - } - } -} diff --git a/packages/ucp/docs/crud_full.n3 b/packages/ucp/docs/crud_full.n3 deleted file mode 100644 index ac79f4c9..00000000 --- a/packages/ucp/docs/crud_full.n3 +++ /dev/null @@ -1,76 +0,0 @@ -@prefix odrl: . -@prefix : . -@prefix acl: . -@prefix fno: . -@prefix log: . -@prefix string: . -@prefix list: . -# 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 - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - # 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 . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Read. -}. - -# Update ODRL Rule (odrl:modify: new asset is not created, not same as acl:write) -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - odrl:assignee ?requestedParty; - odrl:assigner ?resourceOwner . - - ?action list:in (odrl:use odrl:modify). # multiple options - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # context of a request - ?context - :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission acl:Write. - - :uuid5 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Write. -}. - . - . - . - . - . - . - . - -# odrl:constraint . # Note: uncommenting this rule leads to error - . - . - . - . diff --git a/packages/ucp/docs/getting-started.md b/packages/ucp/docs/getting-started.md deleted file mode 100644 index c50e0bb0..00000000 --- a/packages/ucp/docs/getting-started.md +++ /dev/null @@ -1,174 +0,0 @@ -This tutorial is about how to install and use the [@solidlab/ucp](https://github.com/SolidLabResearch/user-managed-access/tree/main/packages/ucp) library utilities. - -## Installing - -As of today (2 Februari 2024), this package is not on npm. -Which means installing via `npm install @solidlab/ucp` does **not** work yet. - -However, it can still easily installed by adding `"@solidlab/ucp": "https://gitpkg.now.sh/SolidLabResearch/user-managed-access/packages/css?main"` to your dependencies in package.json, or executing the corresponding command of your favourite package manager, e.g. `yarn add @solidlab/ucp@"https://gitpkg.now.sh/SolidLabResearch/user-managed-access/packages/css?main"`. - -## Using the library - -First, a brief reminder of what the goal is of this library. -What this library gives is a set of tools for creating **usage control decision** engines that are fully customizable. -A **usage control decision** engine evaluates an *action request* against a set of *Usage Control Policies* to get a conclusion of *access grants*. - -### Example - -As an example, imagine that Ruben wants to know Wout his age. -The age of Wout can be found in a *resource*, `urn:wout:age` (a unique identifier), and is safeguarded by Wout his *policy* that says Ruben has read access to the *resource*. -oncretely, this policy could be modelled as a tuple (Requesting Party (RP), action, target resource, resource owner), which in this example would be (`urn:ruben`, `urn:modes:read`, `urn:wout:age`, `urn:wout`). -The **usage control decision** engine, would then be able to interpret both the request and the policy to give as conclusion a grant: `urn:modes:read`. -This means Ruben is able to know Wout his age and we know that the access is allowed conforming to the policies of Wout thanks to the engine. - -Note: that this example does not contain any usage control yet, but is rather access control. -When a policy has additional to the current tuple expression and enforcement for e.g. Ruben has to delete the data after 30 days and/or that he can only use it for the purpose of for example buying a gift for Wout his Birthday, then we are talking about Usage Control. -Though, you could see the current use case as preventive usage control without purposes. - -### High level Architecture - -There are two functions implemented to calculate the grants, both are part of the interface `UconEnforcementDecision`. - -1. `calculateAccessModes`: Calculate the *access grants* based on the set of *Usage Control Policies* , the *request* and how to *interpret the policies* (the algorithm). -2. `calculateAndExplainAccessModes`: The same as `calculateAccessModes`, but also provides an **Explanation** of how the *access grants* are calculated. - -Both functions have as input a `UconRequest`. This is an interface that formalizes the action request e.g. (RP, requested action, resource) (`urn:ruben`, `urn:modes:read`, `urn:wout:age`). - -As output, `calculateAccessModes` has a list of strings (*access grants*) and `calculateAndExplainAccessModes` has an `Explanation` (which will be elaborated later). - -### The first usage control decision engine - -The implementation of the `UconEnforcementDecision` interface that this library provides is `UcpPatternEnforcement`. - -The first instantiation of this interface is what is referred here as the first **usage control decision** engine. -Before this instantiation is given, an explanation of how it works is given in [the following section](#usage-control-decision-engine). - -#### Usage Control Decision engine - -The (`UcpPatternEnforcement`) engine has three components and can be consulted with the methods described in [the high level architecture](#high-level-architecture). - -The three components: - -- A **[storage](https://github.com/SolidLabResearch/user-managed-access/tree/main/packages/ucp/src/plugins)** to the set of *Usage Control Policies* -- A **storage** to the set of [Notation3](https://w3c.github.io/N3/spec/) (N3) [interpretation rules](https://github.com/SolidLabResearch/user-managed-access/tree/main/packages/ucp/rules) -- A configured instance of **[Koreografeye](https://github.com/eyereasoner/Koreografeye)** consisting of: - - An **N3 reasoner** ([eye-js](https://github.com/eyereasoner/eye-js)) - - A [Koreografeye policy/plugin executor](https://github.com/SolidLabResearch/user-managed-access/blob/main/packages/ucp/src/PolicyExecutor.ts) + [plugins](https://github.com/SolidLabResearch/user-managed-access/tree/main/packages/ucp/src/plugins) - -When a method (e.g. `calculateAccessModes`) is then called with an *action request* the following steps are then executed: - -1. The N3 Reasoner runs with as input: - 1. The set of *Usage Control Policies* - 2. The request (`UconRequest` serialized as RDF) - 3. The N3 *interpretation rules* -2. The conclusion from the Reasoner is then extracted by the Koreografeye policy/plugin executor (configured with plugins) -3. The result is then returned - -This modular approach allows for fast prototyping of a formal Usage Control Policy language, which can then immediately be evaluated. - -#### Instantiation - -This first policy engine instantiation can perform an evaluation of policies modelled with [Open Digital Rights Language (ODRL)](https://www.w3.org/TR/odrl-model/). -More specifically, with a subset that only can interpret `odrl:Permission`s with as **action** `odrl:modify`, `odrl:read` and `odrl:use` (which means both modify and read) against action requests and where there are **no Constraints**. - -To initialise `UcpPatternEnforcement` as this engine, the following code is required: -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/simple-engine.ts#L1-L18 - - -At this point, the engine is ready to be used. Which means that now you can use the `calculateAccessModes` function to request the **grants** for following *action request*: "Ruben wants to know the age of Wout". - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/simple-engine.ts#L20-L27 - -Unfortunately, this results into an empty list `[]`. Which means no **grants** are given and thus Ruben cannot know the age of Wout. - -The reason for this is very simple, there is **no** *Usage Control Policy* in the storage. - -This can however be resolved by simply adding such a policy to the **Usage Control Rule Storage**: - -```ttl -@prefix odrl: . -@prefix : . - -:permission - a odrl:Permission ; - odrl:action odrl:read ; - odrl:target ; - odrl:assignee ; - odrl:assigner . -``` - -To add this rule to the storage, the following code can be used: - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/simple-engine.ts#L30-L41 - - -From now on, when the access modes are calculated again, the following list of grants is received: - -```sh -[ 'http://www.w3.org/ns/auth/acl#Read' ] -``` - -With current initialisation of the **UC Decision engine**, grants are modelled with the [Access Control List (ACL)](https://www.w3.org/ns/auth/acl) Ontology. -The reason for this is that this component is going to be used in an Authorisation Server (AS) for a [Solid](https://solidproject.org/) Resource Server (RS) using the [User-Managed Access (UMA)](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html) specification. In the [Solid Protocol](https://solidproject.org/TR/protocol), ACL is the ontology used for **access modes** both its authorization specifications: the [Web Access Control (WAC)](https://solidproject.org/TR/wac) specification and the [Access Control Policy (ACP)](https://solidproject.org/TR/acp) specification. - -In the engine, this grant is calculated within the N3 interpretation rules, more specifically [here](https://github.com/SolidLabResearch/user-managed-access/blob/949917faa015195369e430f77200bf3273f79283/packages/ucp/rules/data-crud-rules.n3#L12-L38). - -The full code sample for this example can be found in [appendix I](#appendix-I-Full-code-snippet) - -### - - - -### Engine with explanation - -First an elaboration is given for what is meant by explanation. - - -#### Explanation - -So what is the explanation exactly? It is an interface which contains four components: - - -An `Explanation` consists of four components: - -- `decision`: This is the same as the grants (array of access modes) from `calculateAccessModes`. It is the **result** of the evaluation -- `request`: The input *action request* -- `conclusions`: The conclusions of the reasoner. A conclusion itself consists of four parts: the **Rule Identifier**, the **Interpration N3 Rule Identifier**, the **grants** allowed (the actual conclusion) and the **timestamp** at which the conclusion was generated. A conclusion can be seen as the proof of following function: $interpretation(rule, context, timestamp) -> grants$ -- `algorithm`: Which algorithm is used to interpret the set of conclusions - - Note: only the **union** operator is currently implemented. That is: $\forall c \in C. grant \in c \Rightarrow grant \in D$
- For all conclusions in `conclusions`, if a grant is in conclusion, then it is part of the list of grants in `decision`. - -Having the **Explanation** after an evaluation thus allows for logging with provenance/proof of why a certain action was granted at a certain time. - -#### Instantiation - -To have a **usage control decision** engine that can give **Explanations**, it needs to be instantiated correctly. This can be done with following code: - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/log-engine.ts#L1-L18 - -Not that compared to the previous engines, a different **plugin** and a different **N3 interpretation rules** are used. -These allow for retrieving a proper explanation during the calculation of the requests. - -Another difference is the fact that know the method `calculateAndExplainAccessModes` has to be used. -Luckily, this still uses a `UconRequest` as input: - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/log-engine.ts#L34-L41 -When a policy is added to the storage, the above code print out a Javascript Object, which is not very nice. - -Luckily, it is possible to have a nice RDF serialization as output: - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/log-engine.ts#L46-L49 - -Or to be used as RDF in code with the N3 Store: - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/log-engine.ts#L43-L44 - - -## appendix I: Full code snippet - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/simple-engine.ts#L1-L52 - - -## appendix III: Full code snippet Explanation - -https://github.com/SolidLabResearch/user-managed-access/blob/1c0410b6f1c3f133843430f60d4f8c690273dff0/packages/ucp/docs/log-engine.ts#L1-L51 \ No newline at end of file diff --git a/packages/ucp/docs/log-engine.ts b/packages/ucp/docs/log-engine.ts deleted file mode 100644 index bff5ce77..00000000 --- a/packages/ucp/docs/log-engine.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { PolicyExecutor, UcpPatternEnforcement, UCPLogPlugin, MemoryUCRulesStorage, explanationToRdf, serializeFullExplanation, turtleStringToStore } from "@solidlab/ucp"; -import { EyeJsReasoner } from "koreografeye"; - -async function main() { - // load plugin(s) - const plugins = { "http://example.org/dataUsageLog": new UCPLogPlugin() } - // instantiate koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // ucon storage - const uconRulesStorage = new MemoryUCRulesStorage() - // load N3 Rules from a directory - const response = await fetch('https://raw.githubusercontent.com/woutslabbinck/ucp-enforcement/main/rules/log-usage-rule.n3'); // loading from the github repo - const n3Rules: string[] = [await response.text()] - // instantiate the enforcer using the policy executor, - const ucpEvaluator = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - // add Usage Control Rule to Usage Control Rule Storage - const ucr = `@prefix odrl: . -@prefix : . - -:permission - a odrl:Permission ; - odrl:action odrl:read ; - odrl:target ; - odrl:assignee ; - odrl:assigner . - ` - const policyStore = await turtleStringToStore(ucr); - await uconRulesStorage.addRule(policyStore); - - // calculate grants based on a request - const explanation = await ucpEvaluator.calculateAndExplainAccessModes({ - subject: "https://pod.rubendedecker.be/profile/card#me", - action: ["http://www.w3.org/ns/auth/acl#Read"], - resource: "urn:wout:age", - owner: "https://pod.woutslabbinck.com/profile/card#me" - }); - console.log(explanation); - - // use of explanationToRdf - const explanationStore = explanationToRdf(explanation); - - // use of serializeFullExplanation - const uconRules = await uconRulesStorage.getStore(); - const serialized = serializeFullExplanation(explanation, uconRules, n3Rules.join('\n')); - console.log(serialized); -} -main() \ No newline at end of file diff --git a/packages/ucp/docs/simple-engine.ts b/packages/ucp/docs/simple-engine.ts deleted file mode 100644 index e32154be..00000000 --- a/packages/ucp/docs/simple-engine.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { PolicyExecutor, UcpPatternEnforcement, UcpPlugin, MemoryUCRulesStorage, turtleStringToStore } from "@solidlab/ucp"; -import { EyeJsReasoner } from "koreografeye"; - -async function main() { - // load plugin(s) - const plugins = { "http://example.org/dataUsage": new UcpPlugin() } - // Initialise koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // Initialise Usage Control Rule Storage - const uconRulesStorage = new MemoryUCRulesStorage(); - // load N3 Rules - const response = await fetch('https://raw.githubusercontent.com/woutslabbinck/ucp-enforcement/main/rules/data-crud-rules.n3'); // loading from the github repo - const n3Rules: string[] = [await response.text()] - // instantiate the enforcer using the policy executor, - const ucpEvaluator = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - // calculate grants based on a request - const noAccessModes = await ucpEvaluator.calculateAccessModes({ - subject: "https://pod.rubendedecker.be/profile/card#me", - action: ["http://www.w3.org/ns/auth/acl#Read"], - resource: "urn:wout:age", - owner: "https://pod.woutslabbinck.com/profile/card#me" - }); - console.log(noAccessModes); - - // add Usage Control Rule to Usage Control Rule Storage - const ucr = `@prefix odrl: . -@prefix : . - -:permission - a odrl:Permission ; - odrl:action odrl:read ; - odrl:target ; - odrl:assignee ; - odrl:assigner . - ` - const policyStore = await turtleStringToStore(ucr); - await uconRulesStorage.addRule(policyStore); - - // calculate grants based on a request - const accessModes = await ucpEvaluator.calculateAccessModes({ - subject: "https://pod.rubendedecker.be/profile/card#me", - action: ["http://www.w3.org/ns/auth/acl#Read"], - resource: "urn:wout:age", - owner: "https://pod.woutslabbinck.com/profile/card#me" - }); - console.log(accessModes); -} -main() \ No newline at end of file diff --git a/packages/ucp/rules/data-crud-rules.n3 b/packages/ucp/rules/data-crud-rules.n3 deleted file mode 100644 index 1d04265d..00000000 --- a/packages/ucp/rules/data-crud-rules.n3 +++ /dev/null @@ -1,69 +0,0 @@ -@prefix odrl: . -@prefix : . -@prefix acl: . -@prefix fno: . -@prefix log: . -@prefix string: . -@prefix list: . -# Create ODRL Rule: doesn't exist (`odrl:write` and `odrl:append` are deprecated) - - -# 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 - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # 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 . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Read. -}. - -# Update ODRL Rule (odrl:modify: new asset is not created, not same as acl:write) -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - odrl:assignee ?requestedParty; - odrl:assigner ?resourceOwner . - - ?action list:in (odrl:use odrl:modify). # multiple options - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # context of a request - ?context - :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission acl:Write. - - :uuid6 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Write. -}. - -# Delete ODRL Rule (odrl:delete does exist, but there is no acl:Delete) \ No newline at end of file diff --git a/packages/ucp/rules/data-crud-temporal.n3 b/packages/ucp/rules/data-crud-temporal.n3 deleted file mode 100644 index 9736eaff..00000000 --- a/packages/ucp/rules/data-crud-temporal.n3 +++ /dev/null @@ -1,117 +0,0 @@ -@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 2 . - - :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 . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Read. - ?dataUsagePolicyExecution ?currentTime . - -}. - -# Update ODRL Rule (odrl:modify: new asset is not created, not same as acl:write) -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - odrl:assignee ?requestedParty; - odrl:assigner ?resourceOwner . - - ?action list:in (odrl:use odrl:modify). # multiple options - - # context of a request - ?context - :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission acl:Write. - - :uuid6 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 2 . - - :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 . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Write. - ?dataUsagePolicyExecution ?currentTime . -}. - -# Delete ODRL Rule (odrl:delete does exist, but there is no acl:Delete) \ No newline at end of file diff --git a/packages/ucp/rules/log-usage-rule.n3 b/packages/ucp/rules/log-usage-rule.n3 deleted file mode 100644 index 260a9f11..00000000 --- a/packages/ucp/rules/log-usage-rule.n3 +++ /dev/null @@ -1,80 +0,0 @@ -@prefix : . -@prefix acl: . -@prefix fno: . -@prefix list: . -@prefix log: . -@prefix odrl: . -@prefix string: . -@prefix dct: . -@prefix time: . - -# backwards rule to get the time -{ :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 - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # context of a request - ?context - :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission acl:Read. - - :currentTime :is ?currentTime . - - :uuid5 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Read. - ?dataUsagePolicyExecution dct:issued ?currentTime. - ?dataUsagePolicyExecution :N3Identifier :odrlRead. - ?dataUsagePolicyExecution :UCrule ?permission. -}. - -# Update ODRL Rule (odrl:modify: new asset is not created, not same as acl:write) -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - odrl:assignee ?requestedParty; - odrl:assigner ?resourceOwner . - - ?action list:in (odrl:use odrl:modify). # multiple options - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # context of a request - ?context - :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission acl:Write. - - :currentTime :is ?currentTime . - - :uuid4 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Write. - ?dataUsagePolicyExecution dct:issued ?currentTime. - ?dataUsagePolicyExecution :N3Identifier :odrlWrite. - ?dataUsagePolicyExecution :UCrule ?permission. -}. \ No newline at end of file diff --git a/packages/ucp/src/Explanation.ts b/packages/ucp/src/Explanation.ts deleted file mode 100644 index 0d3bae4e..00000000 --- a/packages/ucp/src/Explanation.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Store, DataFactory } from "n3"; -import { randomUUID } from 'node:crypto'; -import { UconRequest, createContext } from "./Request" -import { ACCESS_MODES_ALLOWED } from "./util/Constants"; -import { SimplePolicy } from "./policy/UsageControlPolicy"; -import { storeToString } from "./util/Conversion"; -const { namedNode, literal } = DataFactory - -export interface Explanation { - /** - * The access modes allowed. - */ - decision: string[], - /** - * The input request used as premise. - */ - request: { - raw: UconRequest - }, - /** - * The algorithm used to calculate the decision based on the conclusions. - */ - algorithm: DecisionAlgorithm, - /** - * The conclusions of the reasoner. - * Knowledge base: the input request + all the usage control rules. - * Reasoning rules: N3 rules. - */ - conclusions: Conclusion[] -} - -/** - * Different kinds of decision algorithms which can be used to calculate the grant decisions for the {@link Explanation} - */ -export enum DecisionAlgorithm { - /** - * The decision will be based on the **union** of all the grants of the {@link Conclusion | conclusions}. - */ - Union = "Union", - /** - * The decision will be based on the **intersection** of all the grants of the {@link Conclusion | conclusions}. - */ - Intersection = "Intersection", - /** - * The decision will be based on binary operators of the **policies** to which the {@link Conclusion | conclusions} belong. - * If the policies don't explicitly state on how to interpret the multiple rules, **intersection** on the grants of the rules (r1 AND r2 ... ri) will be used. - */ - Policy = "Policy" -} - -/** - * A rule that was actived through the reasoning together with it its conclusion (which is parsed). - */ -export interface Conclusion { - /** - * The identifier of the usage control rule. - */ - ruleIRI: string, - /** - * The identifier of an N3 rule. - * In particular the rule that was active on both the rule and the request. - */ - interpretationIRI: string, - /** - * The resulting grants allowed throught the reasoning. - * (part of the conclusion of the rule) - */ - grants: string[], - /** - * The time at which the reasoning happened. - * (part of the conclusion of the rule) - */ - timestamp: Date -} - -export function explanationToRdf(explanation: Explanation): Store { - const store = new Store() - const baseIRI = 'http://example.org/' - const explanationIRI = `${baseIRI}explanation` - const explanationNode = namedNode(explanationIRI) - - // decision - for (const accessMode of explanation.decision) { - store.addQuad(explanationNode, namedNode(ACCESS_MODES_ALLOWED), namedNode(accessMode)); - } - - // algorithm - store.addQuad(explanationNode, namedNode(baseIRI + 'algorithmUsed'), literal(explanation.algorithm)); - - // request - store.addQuad(explanationNode, namedNode(baseIRI + 'request'), namedNode('http://example.org/context')) // Note: currently hardcoded request IRI from createContext - store.addQuads(createContext(explanation.request.raw).getQuads(null, null, null, null)) - - // conclusions - for (const conclusion of explanation.conclusions) { - const conclusionIri = baseIRI + randomUUID(); - const conclusionPred = namedNode(baseIRI + 'conclusion'); - const conclusionNode = namedNode(conclusionIri); - store.addQuad(explanationNode, conclusionPred, conclusionNode); - store.addQuad(conclusionNode, namedNode(baseIRI + 'uconRule'), namedNode(conclusion.ruleIRI)); - store.addQuad(conclusionNode, namedNode(baseIRI + 'N3InterpetationRule'), namedNode(conclusion.interpretationIRI)); - store.addQuad(conclusionNode, namedNode('http://purl.org/dc/terms/issued'), literal(conclusion.timestamp.toISOString(), namedNode('http://www.w3.org/2001/XMLSchema#dateTime'))); - - for (const accessMode of conclusion.grants) { - store.addQuad(conclusionNode, namedNode(ACCESS_MODES_ALLOWED), namedNode(accessMode)); - } - - } - return store -} -/** - * Serialize the request together with a set of ucon rules and set of N3 rules interpreting into a single string. - * - * This string can be given to an N3 reasoning engine. - * When there is a conclusion, one or multiple rule activations will occur. (rule must be seen in the context of a policy rule) - * Each rule activation contains permissions granted. - * @param policies - * @param request - * @param rules - */ - -export function serializePremises(policies: SimplePolicy[], request: UconRequest, n3Rules: string): string { - // get string representation of the policies - let policiesString = ""; - for (const createdPolicy of policies) { - policiesString += storeToString(createdPolicy.representation); - } // create context request - const context = storeToString(createContext(request)); - // create file with N3 rules, context and policy - return [policiesString, context, n3Rules].join('\n'); -} -/** - * Serialize the full explanation with the whole context into Notation3. - * Whole context includes: - * - request - * - ucon rules set - * - n3 intepretation rules - * @param explanation - * @param uconRules - * @param n3InterpretationRules - * @returns - */ -export function serializeFullExplanation(explanation: Explanation, uconRules: Store, n3InterpretationRules: string): string { - const explanationString = storeToString(explanationToRdf(explanation)); - const uconRulesString = storeToString(uconRules); - return [explanationString, uconRulesString, n3InterpretationRules].join('\n'); -} diff --git a/packages/ucp/src/PolicyExecutor.ts b/packages/ucp/src/PolicyExecutor.ts deleted file mode 100644 index 3f968449..00000000 --- a/packages/ucp/src/PolicyExecutor.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as N3 from 'n3'; -import * as RDF from '@rdfjs/types'; -import { Store } from 'n3'; -import { Logger, getLogger } from 'log4js'; -import { extractGraph, extractPolicies } from 'koreografeye'; - -export type IPolicyType = { - node: N3.NamedNode | N3.BlankNode, - // Policy node - path: string, // Input file - policy: string, // Policy identifier - target: string, // Name of execution target (the idenfier of the policy function) - order: number, // Execution order of policy - args: { // Name/Value pairs of policy arguments - [key: string]: RDF.Term[] - } -}; - -export abstract class PolicyPlugin { - - constructor() { - - } - - public abstract execute(mainStore: N3.Store, policyStore: N3.Store, policy: IPolicyType): Promise; -} - -export type IPolicyExecution = { - policy: IPolicyType, - result: any -}; - -export interface IPolicyExecutor { - - /** - * Extracts policies out of the graph based on the {@link https://fno.io/spec/#fn-execution | `fno:Execution`} and {@link https://fno.io/spec/#fn-executes | `fno:executes` } triples. - * When they are extracted, the Koreografeye Plugins are executed. - * - * Note that the graph must contain both the input given to the reasoner (minus the N3 rules) and the conclusions. - * @param store - */ - executePolicies(store: Store): Promise -} - -export class PolicyExecutor implements IPolicyExecutor { - private logger: Logger; - - constructor(private plugins: { [n: string]: PolicyPlugin }, logger?: Logger) { - this.logger = logger ?? getLogger() - - } - - async executePolicies(store: Store): Promise { - // this method is a rewrite without ordering of https://github.com/eyereasoner/Koreografeye/blob/3c47764951f20360125a36536c17c7bf28560c98/src/policy/Executor.ts#L39-L86 - const policies = await extractPolicies(store, "none", {}, this.logger); - - const executions: IPolicyExecution[] = [] - - for (const policy of Object.values(policies)) { - const implementation = this.plugins[policy.target] - const policyStore = extractGraph(store, policy.node) - let result - try { - // callImplementation, but this time with a result - result = await implementation.execute(store, policyStore, policy); - } catch (e) { - console.log(policy.target, "could not be executed.", e); - - } - executions.push({ policy, result }) - } - return executions - } - -} \ No newline at end of file diff --git a/packages/ucp/src/Request.ts b/packages/ucp/src/Request.ts deleted file mode 100644 index 292bbea0..00000000 --- a/packages/ucp/src/Request.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Store, DataFactory } from "n3"; -const { quad, namedNode, literal } = DataFactory - -/** - * - * @property subject - The identifier of the entity that wants to execute an action on a resource (e.g. a {@link https://solid.github.io/webid-profile/ WebID}) - * @property action - The type of action(s) that the entity wants to perform on the resource (e.g. a CRUD action) - * @property resource - The resource identifier that is governed by a usage control policy - * @property claims - Extra information supplied (can be the purpose of use, extra claims, ...) - * @property owner - The owner/providerof the resource (e.g. a {@link https://solid.github.io/webid-profile/ WebID}) - */ -export interface UconRequest { - subject: string; - action: string[]; - resource: string; - owner?: string; - claims?: NodeJS.Dict; -} - -/** - * Creates an N3 Store based on the context of an UMA Access Request. - * Currently, the access request also contain ACL access modes. - * @param context - */ -export function createContext(request: UconRequest): Store { - const contextStore = new Store(); - const { owner, subject: requestingParty, action: requestedAccessModes, resource, claims } = request; - const contextIRI = 'http://example.org/context'; - contextStore.addQuads([ - quad(namedNode(contextIRI), namedNode('http://example.org/requestingParty'), namedNode(requestingParty)), - quad(namedNode(contextIRI), namedNode('http://example.org/target'), namedNode(resource)) - ]); - if (owner) contextStore.addQuads([ - quad(namedNode(contextIRI), namedNode('http://example.org/resourceOwner'), namedNode(owner)), - ]); - - for (const accessMode of requestedAccessModes) { - contextStore.addQuad(namedNode(contextIRI), namedNode('http://example.org/requestPermission'), namedNode(accessMode)); - } - - for (const [claim, value] of Object.entries(claims ?? {})) { - if (typeof value !== 'string') { - console.log(`[Request.createContext]: Skipping claim ${claim} because only string values are supported.`); - continue; // TODO: support full RDF - } - - let object; - try { object = namedNode(value) } catch { object = literal(value) } - contextStore.addQuad(namedNode(contextIRI), namedNode(claim), object); - } - - return contextStore; -} diff --git a/packages/ucp/src/UMAinterfaces.ts b/packages/ucp/src/UMAinterfaces.ts deleted file mode 100644 index 995c0b7d..00000000 --- a/packages/ucp/src/UMAinterfaces.ts +++ /dev/null @@ -1,37 +0,0 @@ -// copied from UMA Laurens -export interface ResourceIdentifier { - /** - * Resource IRI - */ - iri: string; -} - -export enum AccessMode { - read = "http://www.w3.org/ns/auth/acl#Read", - append = "http://www.w3.org/ns/auth/acl#Append", - write = "http://www.w3.org/ns/auth/acl#Write", - create = "http://www.w3.org/ns/auth/acl#Create", - delete = "http://www.w3.org/ns/auth/acl#Delete" -} - -export interface Ticket { - sub: ResourceIdentifier; - owner: string; - requested: Set; -} - -/** - * The Principal object serializes the authorization - * request made by the client to the UMA AS. - */ -export interface Principal { - /** - * The WebID of the RP - */ - webId: string; - /** - * The ClientID of the Application used by the RP - */ - clientId?: string; -} -// end copy laurens \ No newline at end of file diff --git a/packages/ucp/src/UcpPatternEnforcement.ts b/packages/ucp/src/UcpPatternEnforcement.ts deleted file mode 100644 index bd817125..00000000 --- a/packages/ucp/src/UcpPatternEnforcement.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Reasoner, rdfTransformStore } from "koreografeye"; -import { Store } from "n3"; -import { Conclusion, DecisionAlgorithm, Explanation } from "./Explanation"; -import { PolicyExecutor as IPolicyExecutor } from "./PolicyExecutor"; -import { UconRequest } from "./Request"; -import { UCRulesStorage } from "./storage/UCRulesStorage"; -import { createContext } from "./Request"; - -/** - * Can calculate Access Modes based on an UMA request, ODRL Rules and N3 Rules using the Koreografeye flow. - * Currently, prohibitions are not taken into account. - */ -export class UcpPatternEnforcement implements UconEnforcementDecision { - - constructor( - private uconRulesStorage: UCRulesStorage, - private koreografeyeOdrlRules: string[], - private reasoner: Reasoner, - private executor: IPolicyExecutor - ) {} - - /** - * Calculates the access modes allowed based using Koreografeye - * The instantiation of the Usage Control Policies are given to this class as an RDF Graph which contain {@link https://www.w3.org/TR/odrl-model/#rule | ODRL Rules}. - * On these, reasoning is applied with N3 Rules. Those N3 rules have as conclusion a koreografeye Policy. - * The conclusion of the reasoning results into a graph with 0..n policies, which are then executed by the plugins. - * This executioner and plugins are given by the {@link IPolicyExecutor}. - * If there are zero policies in the conclusion, a request will be sent to the Resource Owner. - * Furthermore, the user is notified that more information is needed. TODO: - * Otherwise, {@link string | Access modes} are obtained from all the Koreografeye Plugin executions. - * Finally, all the obtained {@link string | Access modes} are returned - * - * @param context Context about the client and the request, parsed by an UMA Server. - * @returns - */ - async calculateAccessModes(request: UconRequest): Promise { - // go from context to an RDF graph that contains all context - const contextStore = createContext(request) - - const reasoningInputStore = new Store() - - reasoningInputStore.addQuads((await this.uconRulesStorage.getStore()).getQuads(null, null, null, null)) - reasoningInputStore.addQuads(contextStore.getQuads(null, null, null, null)) - - // TODO: remove in production - // console.log("rules:"); - // console.log(this.koreografeyeOdrlRules[0]); - // - // console.log("input:"); - // console.log(await rdfTransformStore(reasoningInputStore, 'text/turtle')); - - // Reason using ODRL Rules and N3 Rules - const reasoningResult = await this.reasoner.reason(reasoningInputStore, this.koreografeyeOdrlRules); - - // TODO: remove in production - // console.log("reasoning output:"); - // console.log(await rdfTransformStore(reasoningResult, 'text/turtle')); - - // Execute policies - const accessModes: string[] = [] - const executedPolicies = await this.executor.executePolicies(reasoningResult) - // if no policies -> ask owner for request -> plugin? - if (executedPolicies.length === 0) { - // no access - // TODO: ask owner access - // TODO: let user know that there is no access - console.log(`[${new Date().toISOString()}] - UcpPatternEnforcement: no policies`); - } - // if policies -> executePolicy: return value accessmodes in an object somehow? - for (const executedPolicy of executedPolicies) { - accessModes.push(...executedPolicy.result) - } - return accessModes - } - - async calculateAndExplainAccessModes(request: UconRequest): Promise { - const contextStore = createContext(request) - - const knowledgeBase = new Store() - - knowledgeBase.addQuads((await this.uconRulesStorage.getStore()).getQuads(null, null, null, null)) - knowledgeBase.addQuads(contextStore.getQuads(null, null, null, null)) - - const reasoningResult = await this.reasoner.reason(knowledgeBase, this.koreografeyeOdrlRules); - - const conclusions: Conclusion[] = [] - const executedPolicies = await this.executor.executePolicies(reasoningResult) - - if (executedPolicies.length === 0) { - // based on all the rules, no access - } - - for (const executedPolicy of executedPolicies) { - // expect that the result of the executedPolicy consists of: - // * usage control rule identifier - // * N3 interpretation rule identifier - // * time issued - // * access modes granted - - // if not, throw Error -> the wrong plugin is used | Note: throwing error is not happening yet - - conclusions.push(executedPolicy.result) - } - - // calculation of decision based on the algorithm and the conclusions - // Note: currently hardcoded to be the union. - const grants = conclusions.map(conclusion => conclusion.grants).flat() - // remove the duplicates - const decision = Array.from(new Set(grants)) - return { - decision: decision, - request: { - raw: request - }, - algorithm: DecisionAlgorithm.Union, - conclusions: conclusions, - } - } -} - -export interface UconEnforcementDecision { - /** - * Calculates the modes granted (i.e. the `actions`) based on the request and the configured Usage Control Rules - * and how they are interpreted. - * - * @param request A parsed Usage Request containing `who` wants to perform `which action` on a given `resource` - * with a given `context`. - * @returns A list of Access Modes - */ - calculateAccessModes: (request: UconRequest) => Promise; - - /** - * Calculates the modes granted (i.e. which `actions`) based on the request and the configured Usage Control Rules - * and how they are interpreted. - * Furthermore, it provides a proof + algorithm of how the decision for an access grant is calculated. - * - * @param request A parsed Usage Request containing `who` wants to perform `which action` on a given `resource` - * with a given `context`. - * @returns The explanation (which includes the Access Modes) - */ - calculateAndExplainAccessModes: (request: UconRequest) => Promise; -} diff --git a/packages/ucp/src/index.ts b/packages/ucp/src/index.ts index 5f9ae026..93fe160c 100644 --- a/packages/ucp/src/index.ts +++ b/packages/ucp/src/index.ts @@ -1,7 +1,3 @@ -// plugins (koreografeye) -export * from './plugins/UCPLogPlugin' -export * from './plugins/UCPPlugin' - // storage export * from './storage/ContainerUCRulesStorage' export * from './storage/DirectoryUCRulesStorage' @@ -14,21 +10,5 @@ export * from './policy/UsageControlPolicy' // util export * from './util/Conversion' -export * from './util/Constants' export * from './util/Util' export * from './util/Vocabularies' - -// explanation -export * from './Explanation' - -// koreografeye extension -export * from './PolicyExecutor' - -// request (all information known from a Requesting Party) -export * from './Request' - -// interfaces as defined by Laurens Debackere (https://github.com/laurensdeb/interoperability) -export * from './UMAinterfaces' - -// decision component -export * from './UcpPatternEnforcement' diff --git a/packages/ucp/src/plugins/UCPLogPlugin.ts b/packages/ucp/src/plugins/UCPLogPlugin.ts deleted file mode 100644 index f9497ca6..00000000 --- a/packages/ucp/src/plugins/UCPLogPlugin.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Store } from "n3"; -import { IPolicyType, PolicyPlugin } from "../PolicyExecutor"; -import { ACCESS_MODES_ALLOWED } from "../util/Constants"; -import { Conclusion } from "../Explanation"; - -export const ucpLogPluginIdentifier = 'http://example.org/dataUsageLog' - -export class UCPLogPlugin extends PolicyPlugin { - public async execute(mainStore: Store, policyStore: Store, policy: IPolicyType): Promise { - // TODO: think about no permission (explicit) - - return { - ruleIRI: policy.args['http://example.org/UCrule'][0].value, - interpretationIRI: policy.args['http://example.org/N3Identifier'][0].value, - grants: policy.args[ACCESS_MODES_ALLOWED].map(term => term.value), - timestamp: new Date(policy.args['http://purl.org/dc/terms/issued'][0].value), - } - } -} diff --git a/packages/ucp/src/plugins/UCPPlugin.ts b/packages/ucp/src/plugins/UCPPlugin.ts deleted file mode 100644 index 817e70fb..00000000 --- a/packages/ucp/src/plugins/UCPPlugin.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Store } from "n3"; -import { IPolicyType, PolicyPlugin } from "../PolicyExecutor"; -import { ACCESS_MODES_ALLOWED } from "../util/Constants"; - -export const ucpPluginIdentifier = 'http://example.org/dataUsage' - -export class UcpPlugin extends PolicyPlugin { - public async execute(mainStore: Store, policyStore: Store, policy: IPolicyType): Promise { - // TODO: think about no permission (explicit) - - return policy.args[ACCESS_MODES_ALLOWED].map(term => term.value); - } -} diff --git a/packages/ucp/src/util/Constants.ts b/packages/ucp/src/util/Constants.ts deleted file mode 100644 index a8fa9fd1..00000000 --- a/packages/ucp/src/util/Constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -// TODO: maybe make enum -export const ACCESS_MODES_ALLOWED = 'http://example.org/accessModesAllowed' -export const ACCESS_MODES_PROHOBITID = 'http://example.org/accessModesProhobited' diff --git a/packages/ucp/test/integration/ContainerRulesStorage.ts b/packages/ucp/test/integration/ContainerRulesStorage.ts deleted file mode 100644 index 4e8d2221..00000000 --- a/packages/ucp/test/integration/ContainerRulesStorage.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { EyeJsReasoner, readText } from "koreografeye"; -import * as Path from 'path'; -import { Explanation, explanationToRdf } from "../../src/Explanation"; -import { PolicyExecutor } from "../../src/PolicyExecutor"; -import { AccessMode } from '../../src/UMAinterfaces'; -import { UcpPatternEnforcement } from "../../src/UcpPatternEnforcement"; -import { UCPLogPlugin } from "../../src/plugins/UCPLogPlugin"; -import { ContainerUCRulesStorage } from "../../src/storage/ContainerUCRulesStorage"; -import { storeToString } from "../../src/util/Conversion"; -import { configSolidServer, validateAndExplain } from "../util/Validation"; -import { purgePolicyStorage } from "../util/StorageUtil"; -import { readPolicy, readPolicyRequest, temporalReadPolicyOutOfBound, temporalReadPolicyWithinBound, temporalWritePolicyOutOfBound, temporalWritePolicyWithinBound, usePolicy, usePolicyRequest, writePolicy, writePolicyRequest } from "../util/Constants"; - -// uses the log engine with container rules storage -async function main() { - const portNumber = 3123 - const containerURL = `http://localhost:${portNumber}/` - - // start server - // configured as following command: $ npx @solid/community-server -p 3123 -c config/memory.json - const server = await configSolidServer(portNumber) - await server.start() - - // set up policy container - const uconRulesContainer = `${containerURL}ucon/` - await fetch(uconRulesContainer, { - method: "PUT" - }).then(res => console.log("status creating ucon container:", res.status)) - console.log(); - - // load plugin - const plugins = { "http://example.org/dataUsageLog": new UCPLogPlugin() } - // instantiate koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // ucon storage - const uconRulesStorage = new ContainerUCRulesStorage(uconRulesContainer) - // load N3 Rules from a directory | TODO: utils are needed - const rulesDirectory = Path.join(__dirname, "..", "..", "rules") - const n3Rules: string[] = [readText(Path.join(rulesDirectory, 'log-usage-rule.n3'))!] - // instantiate the enforcer using the policy executor, - const ucpPatternEnforcement = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - let result: { successful: boolean, explanation: Explanation } - let results: { successful: boolean, explanation: Explanation }[] = [] - - // ask read access without policy present | should fail - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while no policy present.", - }) - results.push(result) - - // ask write access without policy present | should fail - - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while no policy present.", - }) - results.push(result) - - // ask read access while write policy present - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while 'write' policy present.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - - // ask write access while read policy present - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while 'read' policy present.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask read access while temporal policy present (and no others) | out of bound - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [temporalReadPolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Out of bound, so no access", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask read access while temporal policy present (and no others) | within bound - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [temporalReadPolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Within bound. However, no N3 rule for interpretation.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask write access while temporal policy present (and no others) | out of bound - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [temporalWritePolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Out of bound, so no access", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask write access while temporal policy present (and no others) | within bound - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [temporalWritePolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Within bound. However, no N3 rule for interpretation.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask read access while read policy present - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.read], - descriptionMessage: "'read' access request while 'read' policy present.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask write access while write policy present - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write], - descriptionMessage: "'write' access request while 'write' policy present.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - // ask read and write access while use policy present - result = await validateAndExplain({ - request: usePolicyRequest, - policies: [usePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write, AccessMode.read], - descriptionMessage: "'read' and 'write' access request while 'use' policy present.", - }) - results.push(result) - await purgePolicyStorage(uconRulesContainer); - - let amountErrors = results.filter(result => !result.successful).length - if (amountErrors) { - console.log("Amount of errors:", amountErrors); // only log amount of errors if there are any - for (const result of results.filter(result => !result.successful)) { - console.log(storeToString(explanationToRdf(result.explanation))); - } - } - // stop server - await server.stop() -} -main() - diff --git a/packages/ucp/test/integration/CrudEngine.ts b/packages/ucp/test/integration/CrudEngine.ts deleted file mode 100644 index 4831147b..00000000 --- a/packages/ucp/test/integration/CrudEngine.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { EyeJsReasoner, readText } from "koreografeye"; -import * as Path from 'path'; -import { PolicyExecutor } from "../../src/PolicyExecutor"; -import { AccessMode } from '../../src/UMAinterfaces'; -import { UcpPatternEnforcement } from "../../src/UcpPatternEnforcement"; -import { UcpPlugin } from "../../src/plugins/UCPPlugin"; -import { SimplePolicy } from "../../src/policy/UsageControlPolicy"; -import { MemoryUCRulesStorage } from "../../src/storage/MemoryUCRulesStorage"; -import { cleanStorage } from "../util/StorageUtil"; -import { validate } from "../util/Validation"; -import { readPolicy, readPolicyRequest, temporalReadPolicyOutOfBound, temporalReadPolicyWithinBound, temporalWritePolicyOutOfBound, temporalWritePolicyWithinBound, usePolicy, usePolicyRequest, writePolicy, writePolicyRequest } from "../util/Constants"; - -async function main() { - // load plugin - const plugins = { "http://example.org/dataUsage": new UcpPlugin() } - // instantiate koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // ucon storage - const uconRulesStorage = new MemoryUCRulesStorage() - // load N3 Rules from a directory | TODO: utils are needed - const rulesDirectory = Path.join(__dirname, "..", "..", "rules") - const n3Rules: string[] = [readText(Path.join(rulesDirectory, 'data-crud-rules.n3'))!] - // instantiate the enforcer using the policy executor, - const ucpPatternEnforcement = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - let result: { successful: boolean, createdPolicies: SimplePolicy[] } - let results: { successful: boolean, createdPolicies: SimplePolicy[] }[] = [] - - // ask read access without policy present | should fail - result = await validate({ - request: readPolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while no policy present.", - }) - results.push(result) - - // ask write access without policy present | should fail - result = await validate({ - request: writePolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while no policy present.", - }) - results.push(result) - // ask read access while write policy present - result = await validate({ - request: readPolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while 'write' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - - // ask write access while read policy present - result = await validate({ - request: writePolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while 'read' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read access while temporal policy present (and no others) | out of bound - result = await validate({ - request: readPolicyRequest, - policies: [temporalReadPolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Out of bound, so no access", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read access while temporal policy present (and no others) | within bound - result = await validate({ - request: readPolicyRequest, - policies: [temporalReadPolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Within bound. However, no N3 rule for interpretation.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask write access while temporal policy present (and no others) | out of bound - result = await validate({ - request: writePolicyRequest, - policies: [temporalWritePolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Out of bound, so no access", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask write access while temporal policy present (and no others) | within bound - result = await validate({ - request: writePolicyRequest, - policies: [temporalWritePolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Within bound. However, no N3 rule for interpretation.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read access while read policy present - result = await validate({ - request: readPolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.read], - descriptionMessage: "'read' access request while 'read' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask write access while write policy present - result = await validate({ - request: writePolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write], - descriptionMessage: "'write' access request while 'write' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read and write access while use policy present - result = await validate({ - request: usePolicyRequest, - policies: [usePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write, AccessMode.read], - descriptionMessage: "'read' and 'write' access request while 'use' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - let amountErrors = results.filter(result => !result.successful).length - if (amountErrors) { - console.log("Amount of errors:", amountErrors); // only log amount of errors if there are any - } -} -main() - diff --git a/packages/ucp/test/integration/CrudEngineTemporal.ts b/packages/ucp/test/integration/CrudEngineTemporal.ts deleted file mode 100644 index bc189976..00000000 --- a/packages/ucp/test/integration/CrudEngineTemporal.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { EyeJsReasoner, readText } from "koreografeye"; -import * as Path from 'path'; -import { PolicyExecutor } from "../../src/PolicyExecutor"; -import { AccessMode } from '../../src/UMAinterfaces'; -import { UcpPatternEnforcement } from "../../src/UcpPatternEnforcement"; -import { UcpPlugin } from "../../src/plugins/UCPPlugin"; -import { SimplePolicy } from "../../src/policy/UsageControlPolicy"; -import { MemoryUCRulesStorage } from "../../src/storage/MemoryUCRulesStorage"; -import { cleanStorage } from "../util/StorageUtil"; -import { validate } from "../util/Validation"; -import { readPolicy, readPolicyRequest, temporalReadPolicyOutOfBound, temporalReadPolicyWithinBound, temporalWritePolicyOutOfBound, temporalWritePolicyWithinBound, usePolicy, usePolicyRequest, writePolicy, writePolicyRequest } from "../util/Constants"; - -async function main() { - // load plugin - const plugins = { "http://example.org/dataUsage": new UcpPlugin() } - // instantiate koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // ucon storage - const uconRulesStorage = new MemoryUCRulesStorage() - // load N3 Rules from a directory | TODO: utils are needed - const rulesDirectory = Path.join(__dirname, "..", "..", "rules") - const n3Rules: string[] = [readText(Path.join(rulesDirectory, 'data-crud-rules.n3'))!, readText(Path.join(rulesDirectory, 'data-crud-temporal.n3'))!] - // instantiate the enforcer using the policy executor, - const ucpPatternEnforcement = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - let result: { successful: boolean, createdPolicies: SimplePolicy[] } - let results: { successful: boolean, createdPolicies: SimplePolicy[] }[] = [] - - - // ask read access without policy present | should fail - result = await validate({ - request: readPolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while no policy present.", - }) - results.push(result) - - // ask write access without policy present | should fail - result = await validate({ - request: writePolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while no policy present.", - }) - results.push(result) - - // ask read access while write policy present - result = await validate({ - request: readPolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while 'write' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - - // ask write access while read policy present - result = await validate({ - request: writePolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while 'read' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read access while temporal policy present (and no others) | out of bound - result = await validate({ - request: readPolicyRequest, - policies: [temporalReadPolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Out of bound, so no access", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read access while temporal policy present (and no others) | within bound - result = await validate({ - request: readPolicyRequest, - policies: [temporalReadPolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.read], - descriptionMessage: "'read' access request while temporal 'read' policy present. Within bound.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask write access while temporal policy present (and no others) | out of bound - result = await validate({ - request: writePolicyRequest, - policies: [temporalWritePolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Out of bound, so no access", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask write access while temporal policy present (and no others) | within bound - result = await validate({ - request: writePolicyRequest, - policies: [temporalWritePolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write], - descriptionMessage: "'write' access request while temporal 'write' policy present. Within bound.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read access while read policy present - result = await validate({ - request: readPolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.read], - descriptionMessage: "'read' access request while 'read' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask write access while write policy present - result = await validate({ - request: writePolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write], - descriptionMessage: "'write' access request while 'write' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - // ask read and write access while use policy present - result = await validate({ - request: usePolicyRequest, - policies: [usePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write, AccessMode.read], - descriptionMessage: "'read' and 'write' access request while 'use' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies) - - let amountErrors = results.filter(result => !result.successful).length - if (amountErrors) { - console.log("Amount of errors:", amountErrors); // only log amount of errors if there are any - } -} -main() - diff --git a/packages/ucp/test/integration/LogEngine.ts b/packages/ucp/test/integration/LogEngine.ts deleted file mode 100644 index e35e6275..00000000 --- a/packages/ucp/test/integration/LogEngine.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { MemoryUCRulesStorage } from "../../src/storage/MemoryUCRulesStorage"; -import { EyeJsReasoner, readText } from "koreografeye"; -import * as Path from 'path'; -import { Explanation, explanationToRdf } from "../../src/Explanation"; -import { PolicyExecutor } from "../../src/PolicyExecutor"; -import { AccessMode } from '../../src/UMAinterfaces'; -import { UcpPatternEnforcement } from "../../src/UcpPatternEnforcement"; -import { UCPLogPlugin } from "../../src/plugins/UCPLogPlugin"; -import { SimplePolicy } from "../../src/policy/UsageControlPolicy"; -import { storeToString } from "../../src/util/Conversion"; -import { validateAndExplain } from "../util/Validation"; -import { readPolicy, readPolicyRequest, temporalReadPolicyOutOfBound, temporalReadPolicyWithinBound, temporalWritePolicyOutOfBound, temporalWritePolicyWithinBound, usePolicy, usePolicyRequest, writePolicy, writePolicyRequest } from "../util/Constants"; -import { cleanStorage } from "../util/StorageUtil"; - -async function main() { - // load plugin - const plugins = { "http://example.org/dataUsageLog": new UCPLogPlugin() } - // instantiate koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // ucon storage - const uconRulesStorage = new MemoryUCRulesStorage() - // load N3 Rules from a directory | TODO: utils are needed - const rulesDirectory = Path.join(__dirname, "..", "..", "rules") - const n3Rules: string[] = [readText(Path.join(rulesDirectory, 'log-usage-rule.n3'))!] - // instantiate the enforcer using the policy executor, - const ucpPatternEnforcement = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - let result: { successful: boolean, explanation: Explanation, createdPolicies: SimplePolicy[] } - let results: { successful: boolean, explanation: Explanation, createdPolicies: SimplePolicy[] }[] = [] - - // ask read access without policy present | should fail - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while no policy present.", - }) - results.push(result) - - // ask write access without policy present | should fail - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while no policy present.", - }) - results.push(result) - - // ask read access while write policy present - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while 'write' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - - // ask write access while read policy present - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while 'read' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask read access while temporal policy present (and no others) | out of bound - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [temporalReadPolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Out of bound, so no access", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask read access while temporal policy present (and no others) | within bound - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [temporalReadPolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'read' access request while temporal 'read' policy present. Within bound. However, no N3 rule for interpretation.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask write access while temporal policy present (and no others) | out of bound - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [temporalWritePolicyOutOfBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Out of bound, so no access", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask write access while temporal policy present (and no others) | within bound - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [temporalWritePolicyWithinBound], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [], - descriptionMessage: "'write' access request while temporal 'write' policy present. Within bound. However, no N3 rule for interpretation.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask read access while read policy present - result = await validateAndExplain({ - request: readPolicyRequest, - policies: [readPolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.read], - descriptionMessage: "'read' access request while 'read' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask write access while write policy present - result = await validateAndExplain({ - request: writePolicyRequest, - policies: [writePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write], - descriptionMessage: "'write' access request while 'write' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - // ask read and write access while use policy present - result = await validateAndExplain({ - request: usePolicyRequest, - policies: [usePolicy], - ucpExecutor: ucpPatternEnforcement, - storage: uconRulesStorage, - n3Rules: n3Rules, - expectedAccessModes: [AccessMode.write, AccessMode.read], - descriptionMessage: "'read' and 'write' access request while 'use' policy present.", - }) - results.push(result) - await cleanStorage(uconRulesStorage, result.createdPolicies); - - let amountErrors = results.filter(result => !result.successful).length - if (amountErrors) { - console.log("Amount of errors:", amountErrors); // only log amount of errors if there are any - for (const result of results.filter(result => !result.successful)) { - console.log(storeToString(explanationToRdf(result.explanation))); - } - } -} -main() - diff --git a/packages/ucp/test/memory.json b/packages/ucp/test/memory.json deleted file mode 100644 index 5bf225ea..00000000 --- a/packages/ucp/test/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/packages/ucp/test/odrl.ts b/packages/ucp/test/odrl.ts deleted file mode 100644 index a30ddeec..00000000 --- a/packages/ucp/test/odrl.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { storeToString } from "../src/util/Conversion" -import { UCPPolicy } from "../src/policy/UsageControlPolicy" -import { basicPolicy } from "../src/policy/ODRL" - -const odrlRead = "http://www.w3.org/ns/odrl/2/read" - -const owner = "https://pod.woutslabbinck.com/profile/card#me" -const resource = "http://localhost:3000/test.ttl" -const requestingParty = "https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me" -const temporalReadPolicyOutOfBound: UCPPolicy = { - rules: [ - { - action: odrlRead, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date("2024-01-01") }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date("2024-01-02") }, // to: must be smaller than given date - ] - } - ] -} - -console.log(storeToString(basicPolicy(temporalReadPolicyOutOfBound).representation)) \ No newline at end of file diff --git a/packages/ucp/test/util/Constants.ts b/packages/ucp/test/util/Constants.ts deleted file mode 100644 index 7735517e..00000000 --- a/packages/ucp/test/util/Constants.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { UconRequest } from "../../src/Request" -import { UCPPolicy } from "../../src/policy/UsageControlPolicy" - -export const owner = "https://pod.woutslabbinck.com/profile/card#me" -export const resource = "http://localhost:3000/test.ttl" -export const requestingParty = "https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me" - -// acl actions -export const aclRead = "http://www.w3.org/ns/auth/acl#Read" -export const aclWrite = "http://www.w3.org/ns/auth/acl#Write" - -// odrl actions -export const odrlRead = "http://www.w3.org/ns/odrl/2/read" -export const odrlWrite = "http://www.w3.org/ns/odrl/2/modify" -export const odrlUse = "http://www.w3.org/ns/odrl/2/use" - -// requests -export const readPolicyRequest: UconRequest = { - subject: requestingParty, action: [aclRead], resource: resource, owner: owner -} -export const writePolicyRequest: UconRequest = { - subject: requestingParty, action: [aclWrite], resource: resource, owner: owner -} -export const usePolicyRequest: UconRequest = { - subject: requestingParty, action: [aclWrite, aclRead], resource: resource, owner: owner -} - -// policies -export const readPolicy: UCPPolicy = { rules: [{ action: odrlRead, owner, resource, requestingParty }] } -export const writePolicy: UCPPolicy = { rules: [{ action: odrlWrite, owner, resource, requestingParty }] } -export const temporalReadPolicyOutOfBound: UCPPolicy = { - rules: [{ - action: odrlRead, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date("2024-01-01") }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date("2024-01-02") }, // to: must be smaller than given date - ] - }] -} -export const temporalReadPolicyWithinBound: UCPPolicy = { - rules: [{ - action: odrlRead, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date(0) }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date(new Date().valueOf() + 30_000) }, // to: must be smaller than given date - ] - }] -} -export const temporalWritePolicyOutOfBound: UCPPolicy = { - rules: [{ - action: odrlWrite, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date("2024-01-01") }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date("2024-01-02") }, // to: must be smaller than given date - ] - }] -} -export const temporalWritePolicyWithinBound: UCPPolicy = { - rules: [{ - action: odrlWrite, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date(0) }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date(new Date().valueOf() + 30_000) }, // to: must be smaller than given date - ] - }] -} -export const usePolicy: UCPPolicy = { rules: [{ action: odrlUse, owner, resource, requestingParty }] } \ No newline at end of file diff --git a/packages/ucp/test/util/StorageUtil.ts b/packages/ucp/test/util/StorageUtil.ts deleted file mode 100644 index 14c76608..00000000 --- a/packages/ucp/test/util/StorageUtil.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { basicPolicy } from "../../src/policy/ODRL"; -import { SimplePolicy, UCPPolicy } from "../../src/policy/UsageControlPolicy"; -import { readLdpRDFResource } from "../../src/storage/ContainerUCRulesStorage"; -import { UCRulesStorage } from "../../src/storage/UCRulesStorage"; - - -export async function cleanStorage(storage: UCRulesStorage, policies: SimplePolicy[]) { - for (const policy of policies) { - await storage.deleteRule(policy.policyIRI); - } -}/** - * Create an instantiated usage control policy using ODRL and add it to the policy container - * @param uconStorage - * @param type - * @returns - */ - -export async function createPolicy(uconStorage: UCRulesStorage, type: UCPPolicy): Promise { - const policyIRI: string = `http://example.org/${new Date().valueOf()}#`; - let SimplePolicy = basicPolicy(type, policyIRI); - await uconStorage.addRule(SimplePolicy.representation); - return SimplePolicy; -} - -export async function purgePolicyStorage(containerURL: string): Promise { - const container = await readLdpRDFResource(fetch, containerURL); - const children = container.getObjects(containerURL, "http://www.w3.org/ns/ldp#contains", null).map(value => value.value); - for (const childURL of children) { - try { - await fetch(childURL, { method: "DELETE" }); - } catch (e) { - console.log(`${childURL} could not be deleted`); - } - } -} - diff --git a/packages/ucp/test/util/Validation.ts b/packages/ucp/test/util/Validation.ts deleted file mode 100644 index 3da29fac..00000000 --- a/packages/ucp/test/util/Validation.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { App, AppRunner, AppRunnerInput } from "@solid/community-server"; -import * as fs from 'fs'; -import * as Path from 'path'; -import { Explanation, serializePremises } from "../../src/Explanation"; -import { UconRequest } from "../../src/Request"; -import { AccessMode } from "../../src/UMAinterfaces"; -import { UconEnforcementDecision } from "../../src/UcpPatternEnforcement"; -import { SimplePolicy, UCPPolicy } from "../../src/policy/UsageControlPolicy"; -import { UCRulesStorage } from "../../src/storage/UCRulesStorage"; -import { createPolicy } from "./StorageUtil"; - -export 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 -} - -// util function that checks whether lists contain the same elements -export function eqList(as: any[], bs: any[]): boolean { - return as.length === bs.length && as.every(a => bs.includes(a)) -} - -/** - * Really needs a better name. - * It stores combined request (context) + policies and the rules interpreting those two. - * Print out the file name - * Print out instructions for eye to reason over it (assuming eye is locally installed) - */ -export function storeToReason(combined: string): void { - const debugDirectory = Path.join(__dirname, "..", 'debug') - const fileName = Path.join(debugDirectory, `fullRequest-${new Date().valueOf()}.n3`); - console.log('execute with eye:', `\neye --quiet --nope --pass-only-new ${fileName}`); - - // note: not very efficient to this all the time - createDirIfNotExists(debugDirectory); - fs.writeFileSync(fileName, combined) -} -// create debug directory if it doesn't exist yet -const createDirIfNotExists = (dir: fs.PathLike) => - !fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined; -/** - * Util function to debug why a certain test went wrong - * @param policies - * @param request - * @param n3Rules - */ -export function debug(policies: SimplePolicy[], request: UconRequest, n3Rules: string): void { - const combined = serializePremises(policies, request, n3Rules) - storeToReason(combined) -} - -/** - * Validates a request to an ucon rules set and its interepretation. - * Will produce a proper log when the test fails. - * To do the decision calculation `calculateAccessModes` from {@link UconEnforcementDecision} is used. - * - * Note: Currently does not clean up the ucon rules storage (it only adds). - * @param input - * @returns - */ -export async function validate(input: { - request: UconRequest, - policies: UCPPolicy[], - ucpExecutor: UconEnforcementDecision, - storage: UCRulesStorage, - descriptionMessage?: string, - validationMessage?: string, - expectedAccessModes: AccessMode[], - n3Rules: string[] -}): Promise<{ successful: boolean, createdPolicies: SimplePolicy[] }> { - const { request, policies, ucpExecutor, storage, expectedAccessModes } = input; - // add policies - const createdPolicies: SimplePolicy[] = []; - for (const policy of policies) { - const created = await createPolicy(storage, policy); - createdPolicies.push(created) - } - // ucp decision - const explanation = await ucpExecutor.calculateAccessModes(request); - - // debug info - if (input.descriptionMessage) console.log(input.descriptionMessage); - const validationMessage = input.validationMessage ?? "Access modes present:" - console.log(validationMessage, explanation, "Access modes that should be present:", expectedAccessModes); - - const successful = eqList(explanation, expectedAccessModes) - if (!successful) { - console.log("This policy is wrong."); - debug(createdPolicies, request, input.n3Rules.join('\n')) - } - console.log(); - return {successful, createdPolicies} -} - -/** - * Validates a request to an ucon rules set and its interepretation. - * Will produce a proper log when the test fails. - * To do the decision calculation `calculateAndExplainAccessModes` from {@link UconEnforcementDecision} is used. - * - * Note: Currently does not clean up the ucon rules storage (it only adds). - * @param input - * @returns - */ -export async function validateAndExplain(input: { - request: UconRequest, - policies: UCPPolicy[], - ucpExecutor: UconEnforcementDecision, - storage: UCRulesStorage, - descriptionMessage?: string, - validationMessage?: string, - expectedAccessModes: AccessMode[], - n3Rules: string[] -}): Promise<{ successful: boolean, explanation: Explanation, createdPolicies: SimplePolicy[] }> { - const { request, policies, ucpExecutor, storage, expectedAccessModes } = input; - // add policies - const createdPolicies: SimplePolicy[] = []; - for (const policy of policies) { - const created = await createPolicy(storage, policy); - createdPolicies.push(created) - } - // ucp decision - const explanation = await ucpExecutor.calculateAndExplainAccessModes(request); - - // debug info - if (input.descriptionMessage) console.log(input.descriptionMessage); - const validationMessage = input.validationMessage ?? "Access modes present:" - console.log(validationMessage, explanation.decision, "Access modes that should be present:", expectedAccessModes); - - const successful = eqList(explanation.decision, expectedAccessModes) - if (!successful) { - console.log("This policy is wrong."); - debug(createdPolicies, request, input.n3Rules.join('\n')) - } - console.log(); - return { successful, explanation, createdPolicies } -} \ No newline at end of file diff --git a/packages/uma/config/rules/n3/crud.n3 b/packages/uma/config/rules/n3/crud.n3 deleted file mode 100644 index 2c240210..00000000 --- a/packages/uma/config/rules/n3/crud.n3 +++ /dev/null @@ -1,163 +0,0 @@ -@prefix odrl: . -@prefix : . -@prefix acl: . -@prefix fno: . -@prefix log: . -@prefix string: . -@prefix list: . - -# Read ODRL rule -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - # odrl:assigner ?resourceOwner ; - odrl:assignee ?requestedParty. - - ?action list:in (odrl:use odrl:read) . # multiple options - - - # context of a request - ?context - # :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission . - - :uuid5 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . - - # Constraint checking - # No odrl:constraints may be present - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed . -}. - -# Append ODRL Rule -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - # odrl:assigner ?resourceOwner ; - odrl:assignee ?requestedParty. - - ?action list:in (odrl:use odrl:modify). # multiple options - - # context of a request - ?context - # :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission . - - :uuid6 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . - - # Constraint checking - # No odrl:constraints may be present - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed . -}. - -# Write ODRL Rule -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - # odrl:assigner ?resourceOwner ; - odrl:assignee ?requestedParty. - - ?action list:in (odrl:use odrl:modify). # multiple options - - # context of a request - ?context - # :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission . - - :uuid6 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . - - # Constraint checking - # No odrl:constraints may be present - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed . -}. - -# Create ODRL Rule -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - # odrl:assigner ?resourceOwner ; - odrl:assignee ?requestedParty . - - ?action list:in (odrl:use odrl:modify). # multiple options - - # context of a request - ?context - # :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission . - - :uuid6 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . - - # Constraint checking - # No odrl:constraints may be present - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed . -}. - -# Delete ODRL Rule -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - # odrl:assigner ?resourceOwner ; - odrl:assignee ?requestedParty. - - ?action list:in (odrl:use odrl:delete). # multiple options - - # context of a request - ?context - # :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission . - - :uuid6 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . - - # Constraint checking - # No odrl:constraints may be present - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed . -}. diff --git a/packages/uma/config/rules/n3/purpose-time.n3 b/packages/uma/config/rules/n3/purpose-time.n3 deleted file mode 100644 index 8ffea2cd..00000000 --- a/packages/uma/config/rules/n3/purpose-time.n3 +++ /dev/null @@ -1,79 +0,0 @@ -@prefix xsd: . -@prefix odrl: . -@prefix : . -@prefix acl: . -@prefix fno: . -@prefix log: . -@prefix string: . -@prefix list: . -@prefix time: . -@prefix math: . - -# ... -{ :currentTime :is ?currentTime } <= { "" time:localTime ?currentTime }. - -# Read ODRL rule -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - # odrl:assigner ?resourceOwner ; - odrl:assignee ?requestedParty. - - ?action list:in (odrl:use odrl:read) . # multiple options - - # context of a request - ?context - # :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission ; - ?purposeValue. - - :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 ; - ?currentTime . - -}. - -# No ODRL rules for other access modes (`odrl:write` and `odrl:append` are deprecated) diff --git a/packages/uma/src/index.ts b/packages/uma/src/index.ts index e6e1d480..5e756704 100644 --- a/packages/uma/src/index.ts +++ b/packages/uma/src/index.ts @@ -24,7 +24,6 @@ export * from './policies/authorizers/AllAuthorizer'; export * from './policies/authorizers/NamespacedAuthorizer'; export * from './policies/authorizers/NoneAuthorizer'; export * from './policies/authorizers/OdrlAuthorizer'; -export * from './policies/authorizers/PolicyBasedAuthorizer'; export * from './policies/authorizers/WebIdAuthorizer'; // Contracts diff --git a/packages/uma/src/policies/authorizers/PolicyBasedAuthorizer.ts b/packages/uma/src/policies/authorizers/PolicyBasedAuthorizer.ts deleted file mode 100644 index 054fd9a2..00000000 --- a/packages/uma/src/policies/authorizers/PolicyBasedAuthorizer.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Authorizer } from './Authorizer'; -import { Permission } from '../../views/Permission'; -import { Requirements, type ClaimVerifier } from '../../credentials/Requirements'; -import { ClaimSet } from '../../credentials/ClaimSet'; -import { ODRL, PolicyExecutor, UconRequest, - UcpPatternEnforcement, UcpPlugin, UCRulesStorage } from '@solidlab/ucp'; -import { EyeJsReasoner } from "koreografeye"; -import { lstatSync, readFileSync, readdirSync } from 'fs'; -import path from 'path'; -import { WEBID } from '../../credentials/Claims'; -import { getLoggerFor, RDF } from '@solid/community-server'; - -/** - * An Authorizer granting access according to Usage Control Policies. - */ -export class PolicyBasedAuthorizer implements Authorizer { - protected readonly logger = getLoggerFor(this); - - private reasoner: EyeJsReasoner = new EyeJsReasoner(["--quiet", "--nope", "--pass"]); - private plugins = { "http://example.org/dataUsage": new UcpPlugin() }; - private executor = new PolicyExecutor(this.plugins); - private enforcer: UcpPatternEnforcement; - - /** - * Creates a PublicNamespaceAuthorizer with the given public namespaces. - * - * @param rules - A store containing the UCP policy rules. - * @param enforcer - The UcpPatternEnforcement engine. - */ - constructor( - // protected enforcer: UcpPatternEnforcement - private readonly policies: UCRulesStorage, - private readonly rulesDir: string, - ) { - if (!lstatSync(this.rulesDir).isDirectory()) { - throw Error(`${this.rulesDir} does not resolve to a directory`) - } - const ruleSet = readdirSync(this.rulesDir).map(file => { - return readFileSync(path.join(this.rulesDir, file)).toString(); - }); - this.enforcer = new UcpPatternEnforcement(this.policies, ruleSet, this.reasoner, this.executor) - } - - - /** @inheritdoc */ - public async permissions(claims: ClaimSet, query?: Partial[]): Promise { - this.logger.info(`Calculating permissions. ${JSON.stringify({ claims, query })}`); - - if (!query) { - this.logger.warn('The PolicyBasedAuthorizer can only calculate permissions for explicit queries.') - return []; - } - - const requests: UconRequest[] = []; - for (const { resource_id, resource_scopes} of query) { - - if (!resource_id) { - this.logger.warn('The PolicyBasedAuthorizer can only calculate permissions for explicit resources.'); - continue; - } - - requests.push({ - subject: typeof claims[WEBID] === 'string' ? claims[WEBID] : 'urn:solidlab:uma:id:anonymous', - resource: resource_id, - action: resource_scopes ?? [ "http://www.w3.org/ns/odrl/2/use" ], - claims - }); - } - - const permissions: Permission[] = await Promise.all(requests.map( - async (request) => ({ - resource_id: request.resource, - resource_scopes: await this.enforcer.calculateAccessModes(request) - }) - )); - - return permissions; - } - - /** @inheritdoc */ - public async credentials(permissions: Permission[], query?: Requirements): Promise { - this.logger.info(`Calculating credentials. ${JSON.stringify({ permissions, query })}`); - - // No permissions => empty requirements - if (permissions.length === 0) return [{}]; - - const policyStore = await this.policies.getStore(); - const requirements: Requirements[] = []; - - const policyPermissions = policyStore.getSubjects(RDF.terms.type, ODRL.terms.Permission, null); - - // No policies => no solvable requirements - if (policyPermissions.length === 0) return []; - - for (const policyPermission of policyPermissions) { - const verifiers: Record = {}; - - // TODO: remove this default and treat webid as any claim - const webids = policyStore.getObjects(policyPermission, ODRL.terms.assignee, null).map(webid => webid.value); - verifiers[WEBID] = async (webid: string) => webids.includes(webid); - - const constraints = policyStore.getObjects(policyPermission, ODRL.terms.constraint, null); - - for (const constraint of constraints) { - const leftOperand = policyStore.getObjects(constraint, ODRL.terms.leftOperand, null)[0]; - const operator = policyStore.getObjects(constraint, ODRL.terms.operator, null)[0]; - const rightOperand = policyStore.getObjects(constraint, ODRL.terms.rightOperand, null)[0]; - - if (operator.value !== ODRL.lt && operator.value !== ODRL.gt && operator.value !== ODRL.eq) { - this.logger.warn(`Cannot handle operator <${operator.value}>.`); - continue; - } - - // Skip dateTime constraints, since this is included in the rules. - if (leftOperand.value === ODRL.dateTime) continue; - - // TODO: Support any ODRL constraint - verifiers[leftOperand.value] = async (arg: any) => { - switch (operator.value) { - case ODRL.lt: return arg < rightOperand.value; - case ODRL.gt: return arg > rightOperand.value; - default: return arg === rightOperand.value; - } - }; - } - - requirements.push(verifiers); - } - - if (query && !Object.keys(requirements).every(r => Object.keys(query).includes(r))) { - return []; - } - - return requirements; - } -} diff --git a/scripts/test-ucp-enforcement.ts b/scripts/test-ucp-enforcement.ts deleted file mode 100644 index fd7090d4..00000000 --- a/scripts/test-ucp-enforcement.ts +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env ts-node - -const crud = ` -@prefix odrl: . -@prefix : . -@prefix acl: . -@prefix fno: . -@prefix log: . -@prefix string: . -@prefix list: . - - -# 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 - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # 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 . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Read. -}. - -# Update ODRL Rule (odrl:modify: new asset is not created, not same as acl:write) -{ - ?permission a odrl:Permission; - odrl:action ?action ; - odrl:target ?targetResource ; - odrl:assignee ?requestedParty; - odrl:assigner ?resourceOwner . - - ?action list:in (odrl:use odrl:modify). # multiple options - - ?SCOPE log:notIncludes { ?permission odrl:constraint ?anything }. # No odrl:constraints may be present - - # context of a request - ?context - :resourceOwner ?resourceOwner; - :requestingParty ?requestedParty; - :target ?targetResource; - :requestPermission acl:Write. - - :uuid6 log:uuid ?uuidStringdataUsagePolicyExecution. - ( "urn:uuid:" ?uuidStringdataUsagePolicyExecution) string:concatenation ?urnUuidStringdataUsagePolicyExecution. - ?dataUsagePolicyExecution log:uri ?urnUuidStringdataUsagePolicyExecution . -} => -{ - ?dataUsagePolicyExecution a fno:Execution; - fno:executes ; - :accessModesAllowed acl:Write. -}. -` - -import { EyeJsReasoner } from "koreografeye"; -import { PolicyExecutor, SimplePolicy, UconRequest, UcpPatternEnforcement, UcpPlugin, turtleStringToStore, MemoryUCRulesStorage } from "@solidlab/ucp"; - - - -/** - * Interface for a Usage Control Policy. - * Note: a Usage Control policy currently only has one rule. - */ -export interface UCPPolicy { - action: string, - owner: string, - resource: string, - requestingParty: string, - constraints?: Constraint[] -} - -/** - * Interface for a Usage Control Policy Constraint - */ -export interface Constraint { - type: string, - operator: string, - value: any -} - -/** - * Create a simple policy with an agreement and one rule - * Note: should and can be made synchronous - * @param type - * @param baseIri - * @returns - */ -export async function basicPolicy(type: UCPPolicy, baseIri?: string): Promise { - baseIri = baseIri ?? `http://example.org/${new Date().valueOf()}#` // better would be uuid - const agreement = baseIri + "usagePolicy"; - const rule = baseIri + "permission"; - const policy = `@prefix odrl: . - @prefix acl: . - - <${agreement}> - a odrl:Agreement ; - odrl:permission <${rule}>. - - <${rule}> - a odrl:Permission ; - odrl:action <${type.action}> ; - odrl:target <${type.resource}>; - odrl:assignee <${type.requestingParty}> ; - odrl:assigner <${type.owner}> .` - - const constraints = createConstraints(rule, type.constraints ?? []) - - const policyStore = await turtleStringToStore([policy, constraints].join("\n")) - - return { representation: policyStore, policyIRI: agreement, ruleIRIs: [rule] } -} - -export function createConstraints(ruleIRI: string, constraints: Constraint[]): string { - let constraintsString = "" - for (const constraint of constraints) { - // note: only temporal constraints currently, so the type is not checked - constraintsString += `@prefix xsd: . - @prefix odrl: . - <${ruleIRI}> odrl:constraint [ - odrl:leftOperand odrl:dateTime ; - odrl:operator <${constraint.operator}> ; - odrl:rightOperand "${(constraint.value as Date).toISOString()}"^^xsd:dateTime ] . - ` - } - return constraintsString -} - -async function main() { - // Note: assumption - Solid server is set up with public read and write access on port 3123 - // $ npx @solid/community-server -p 3123 -c memory.json - - // constants - const aclRead = "http://www.w3.org/ns/auth/acl#Read" - const aclWrite = "http://www.w3.org/ns/auth/acl#Write" - const odrlRead = "http://www.w3.org/ns/odrl/2/read" - const odrlWrite = "http://www.w3.org/ns/odrl/2/modify" - const odrlUse = "http://www.w3.org/ns/odrl/2/use" - - const owner = "https://pod.woutslabbinck.com/profile/card#me" - const resource = "http://localhost:3000/test.ttl" - const requestingParty = "https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me" - - // requests - const readPolicyRequest: UconRequest = { - subject: requestingParty, action: [aclRead], resource: resource, owner: owner - } - const writePolicyRequest: UconRequest = { - subject: requestingParty, action: [aclWrite], resource: resource, owner: owner - } - const usePolicyRequest: UconRequest = { - subject: requestingParty, action: [aclWrite, aclRead], resource: resource, owner: owner - } - - // policies - const readPolicy: UCPPolicy = { action: odrlRead, owner, resource, requestingParty } - const writePolicy: UCPPolicy = { action: odrlWrite, owner, resource, requestingParty } - const temporalReadPolicyOutOfBound: UCPPolicy = { - action: odrlRead, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date("2024-01-01") }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date("2024-01-02") }, // to: must be smaller than given date - ] - } - const temporalReadPolicyWithinBound: UCPPolicy = { - action: odrlRead, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date(0) }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date(new Date().valueOf() + 30_000) }, // to: must be smaller than given date - ] - } - const temporalWritePolicyOutOfBound: UCPPolicy = { - action: odrlWrite, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date("2024-01-01") }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date("2024-01-02") }, // to: must be smaller than given date - ] - } - const temporalWritePolicyWithinBound: UCPPolicy = { - action: odrlWrite, owner, resource, requestingParty, - constraints: [ - { operator: "http://www.w3.org/ns/odrl/2/gt", type: "temporal", value: new Date(0) }, // from: must be greater than given date - { operator: "http://www.w3.org/ns/odrl/2/lt", type: "temporal", value: new Date(new Date().valueOf() + 30_000) }, // to: must be smaller than given date - ] - } - const usePolicy: UCPPolicy = { action: odrlUse, owner, resource, requestingParty } - - // load plugin - const plugins = { "http://example.org/dataUsage": new UcpPlugin() } - // instantiate koreografeye policy executor - const policyExecutor = new PolicyExecutor(plugins) - // ucon storage - const uconRulesStorage = new MemoryUCRulesStorage() - // load N3 Rules from a directory | TODO: utils are needed - const n3Rules: string[] = [crud] - // instantiate the enforcer using the policy executor, - const ucpPatternEnforcement = new UcpPatternEnforcement(uconRulesStorage, n3Rules, new EyeJsReasoner([ - "--quiet", - "--nope", - "--pass"]), policyExecutor) - - const policy = await basicPolicy(readPolicy) - await uconRulesStorage.addRule(policy.representation) - const result = await ucpPatternEnforcement.calculateAccessModes(readPolicyRequest) - console.log(result); -} -main() From 7150ffdb7be0e47053818ef9d41e02152a568b59 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Fri, 6 Jun 2025 11:04:21 +0200 Subject: [PATCH 6/6] docs: Update README --- README.md | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e4d87ba8..c0a588eb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository contains SolidLab research artefacts on use of UMA in the Solid - [`@solidlab/uma-css`](packages/css): UMA modules for the [Community Solid Server](https://github.com/CommunitySolidServer/CommunitySolidServer/). -- [`@solidlab/ucp`](packages/ucp): Usage Control Policy decision/enforcement component. +- [`@solidlab/ucp`](packages/ucp): Usage Control Policy utility component. ## Getting started @@ -28,7 +28,6 @@ You can then execute the following flows: - `yarn script:private`: `PUT` some text to the private `/alice/private/resource.txt`, protected by a simple WebID check; - `yarn script:uma-ucp`: `PUT` some text to the private `/alice/other/resource.txt`, protected by a UCP enforcer checking WebIDs according to policies in `packages/uma/config/rules/policy/`. - `yarn script:registration`: `POST`, `GET` and `DELETE` some text to/from `/alice/public/resource.txt` to test the correct creation and deletion of resource registrations on the UNA server. -- `yarn script:ucp-enforcement`: Run the UCP enforcer in a script (`scripts/test-ucp-enforcement.ts`). This does not need the servers to be started. `yarn script:flow` runs all flows in sequence. @@ -46,24 +45,13 @@ which runs with experimental contracts. The packages in this project currently only support a fixed UMA AS per CSS RS. Authorization can be done with a simple, unverified, WebID embedded in the ticket using the [WebIdAuthorizer](packages/uma/src/policies/authorizers/WebIdAuthorizer.ts) -or the [PolicyBasedAuthorizer](packages/uma/src/policies/authorizers/PolicyBasedAuthorizer.ts) +or the [OdrlAuthorizer](packages/uma/src/policies/authorizers/OdrlAuthorizer.ts) which supports simple ODRL policies. +A [NamespacedAuthorizer](packages/uma/src/policies/authorizers/NamespacedAuthorizer.ts) +is used to apply different authorizers to different containers. -### Usage control policy enforcement +## ODRL -Used for creating a modular engine that calculates which access modes are granted based on: - -- Usage Control Rules -- Interpretation of those rules -- The request of the Requested Party together with all its claims - -For more information, you can check out its [own repository](https://github.com/woutslabbinck/ucp-enforcement) which has three engines that use [ODRL rules](https://www.w3.org/TR/odrl-model/). - -A test script is provided for a CRUD ODRL engine: `yarn script:ucp-enforcement`. -In the [script](./scripts/test-ucp-enforcement.ts) a read Usage Control Rule (in ODRL) is present together with N3 interpretation rules. -Then a read request is performed using the engine, which results in a list of grants. This list is then printed to the console. - - -## Next steps - -More advanced ODRL evaluation can be found in the `feat/ODRL-evaluator` branch. +A variant of the server that only uses ODRL for authorization can be started with `yarn start:odrl`. +A corresponding script can then be executed with `yarn script:uma-odrl`. +The test policies can be found in [packages/uma/config/rules/odrl](packages/uma/config/rules/odrl).