diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index c8dc3c3b..efcaea13 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -13,12 +13,15 @@ jobs:
fail-fast: false
matrix:
node-version:
- - 18.x # Maintenance
- - 20.x # Active
- - 21.x # Current
+ - 20.x # Maintenance
+ - 22.x # Active
+ - 23.x # Current
steps:
+ - name: Checkout main branch
+ uses: actions/checkout@v4
+
- name: Enable Node.js Corepack
run: corepack enable
@@ -27,8 +30,5 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- - name: Checkout main branch
- uses: actions/checkout@v4
-
- name: Yarn install
run: yarn install
diff --git a/.gitignore b/.gitignore
index 257bbd16..0003d53f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,4 +67,5 @@ tmp
# Misc
.DS_Store
-.vscode/
\ No newline at end of file
+.vscode/
+.idea
diff --git a/.nvmrc b/.nvmrc
index 2edeafb0..2bd5a0a9 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20
\ No newline at end of file
+22
diff --git a/.yarnrc.yml b/.yarnrc.yml
index 348a705c..4e256646 100644
--- a/.yarnrc.yml
+++ b/.yarnrc.yml
@@ -1,30 +1,30 @@
-nodeLinker: "node-modules"
+nodeLinker: node-modules
# packageExtensions:
# "@comunica/actor-abstract-parse@*":
# dependencies:
-# "@types/node": ^18
+# "@types/node": ^20
# "@types/readable-stream": ^4
# "@comunica/types@*":
# dependencies:
-# "@types/node": ^18
+# "@types/node": ^20
# "@solid/community-server@*":
# dependencies:
# "@types/koa": ^2
-# rdf-js: ^2
+# "rdf-js": ^2
# asynciterator@*:
# dependencies:
-# "@types/node": ^18
+# "@types/node": ^20
# rdf-parse@*:
# dependencies:
-# "@types/node": ^18
+# "@types/node": ^20
# "@types/readable-stream": ^4
-# rdf-js: ^2
+# "rdf-js": ^2
# undici-types@*:
# dependencies:
-# "@types/node": ^18
+# "@types/node": ^20
# winston-transport@*:
# dependencies:
-# "@types/node": ^18
+# "@types/node": ^20
# winston@*:
# dependencies:
-# "@types/node": ^18
\ No newline at end of file
+# "@types/node": ^20
diff --git a/demo/flow-test.ts b/demo/flow-test.ts
index db9f999f..53ed6e5c 100644
--- a/demo/flow-test.ts
+++ b/demo/flow-test.ts
@@ -65,7 +65,7 @@ async function main() {
log('Error fetching WebID data:', e);
return;
}
-
+
const umaServer = webIdData.getObjects(terms.agents.ruben, terms.solid.umaServer, null)[0].value;
const configUrl = new URL('.well-known/uma2-configuration', umaServer);
const umaConfig = await (await fetch(configUrl)).json();
@@ -84,8 +84,8 @@ Target Resource: ${terms.resources.smartwatch}`)
log('To protect this data, a policy is added restricting access to a specific healthcare employee for the purpose of bariatric care.');
log(chalk.italic(`Note: Policy management is out of scope for POC1, right now they are just served from a public container on the pod.
additionally, selecting relevant policies is not implemented at the moment, all policies are evaluated, but this is a minor fix in the AS.`))
-
-const healthcare_patient_policy =
+
+const healthcare_patient_policy =
`@prefix dcterms: .
@prefix eu-gdpr: .
@prefix oac: .
@@ -94,29 +94,28 @@ const healthcare_patient_policy =
@prefix ex: .
- a odrl:Request ;
- odrl:uid ex:HCPX-request ;
+ a odrl:Agreement ;
+ odrl:uid ex:HCPX-agreement ;
odrl:profile oac: ;
- dcterms:description "HCP X requests to read Alice's health data for bariatric care.";
- odrl:permission .
+ odrl:permission .
- a odrl:Permission ;
+ a odrl:Permission ;
odrl:action odrl:read ;
odrl:target ;
odrl:assigner <${terms.agents.ruben}> ;
odrl:assignee <${terms.agents.alice}> ;
- odrl:constraint ,
- .
+ odrl:constraint ,
+ .
a odrl:AssetCollection;
odrl:source <${terms.resources.collectionSource}> .
- a odrl:Constraint ;
+ a odrl:Constraint ;
odrl:leftOperand odrl:purpose ; # can also be oac:Purpose, to conform with OAC profile
odrl:operator odrl:eq ;
odrl:rightOperand ex:bariatric-care .
- a odrl:Constraint ;
+ a odrl:Constraint ;
odrl:leftOperand oac:LegalBasis ;
odrl:operator odrl:eq ;
odrl:rightOperand eu-gdpr:A9-2-a .`
@@ -211,7 +210,7 @@ const healthcare_patient_policy =
],
} ],
// claims: [{
- claim_token: claim_token,
+ claim_token: claim_token,
claim_token_format: "urn:solidlab:uma:claims:formats:jwt",
// }],
// UMA specific fields
@@ -224,15 +223,15 @@ const healthcare_patient_policy =
if (response2.failed) {
throw new Error(`Resource request for ${terms.resources.smartwatch} should not have failed with claims: ${response}`)
}
-
+
const access_token = parseJwt(response2.access_token)
- log(`The UMA server checks the claims with the relevant policy, and returns the agent an access token with the requested permissions.`,
+ log(`The UMA server checks the claims with the relevant policy, and returns the agent an access token with the requested permissions.`,
JSON.stringify(access_token.permissions, null, 2));
-
- log(`and the accompanying agreement:`,
+
+ log(`and the accompanying agreement:`,
JSON.stringify(access_token.contract, null, 2));
-
+
log(chalk.italic(`Future work: at a later stage, this agreements will be signed by both parties to form a binding contract.`))
const accessWithTokenResponse = await fetch(terms.resources.smartwatch, {
@@ -242,7 +241,7 @@ const healthcare_patient_policy =
log(`Now the doctor can retrieve the resource:`, await accessWithTokenResponse.text());
if (accessWithTokenResponse.status !== 200) { log(`Access with token failed...`); throw 0; }
-
+
}
main();
@@ -270,7 +269,7 @@ async function executeReadWithClaims(target: string, request: any, options: { to
method: "GET",
headers: { "content-type": "application/json" },
});
-
+
const umaHeader = await res.headers.get('WWW-Authenticate')
log(`Resource request to ${target} results in ${umaHeader}`)
@@ -287,7 +286,7 @@ async function executeReadWithClaims(target: string, request: any, options: { to
headers: { "content-type": "application/json" },
body: JSON.stringify(request),
});
-
+
// if (response.status !== 403) { log('Access request succeeded without claims...', await response.text()); throw 0; }
const responseJSON = await response.json();
diff --git a/demo/flow.ts b/demo/flow.ts
index d3cb0709..b88bca26 100644
--- a/demo/flow.ts
+++ b/demo/flow.ts
@@ -54,7 +54,7 @@ const policyContainer = 'http://localhost:3000/ruben/settings/policies/';
async function main() {
const webIdData = new Store(parser.parse(await (await fetch(terms.agents.ruben)).text()));
-
+
const umaServer = webIdData.getObjects(terms.agents.ruben, terms.solid.umaServer, null)[0].value;
const configUrl = new URL('.well-known/uma2-configuration', umaServer);
const umaConfig = await (await fetch(configUrl)).json();
@@ -73,8 +73,8 @@ Target Resource: ${terms.resources.smartwatch}`)
log('To protect this data, a policy is added restricting access to a specific healthcare employee for the purpose of bariatric care.');
log(chalk.italic(`Note: Policy management is out of scope for POC1, right now they are just served from a public container on the pod.
additionally, selecting relevant policies is not implemented at the moment, all policies are evaluated, but this is a minor fix in the AS.`))
-
- const healthcare_patient_policy =
+
+ const healthcare_patient_policy =
`PREFIX dcterms:
PREFIX eu-gdpr:
PREFIX oac:
@@ -83,26 +83,25 @@ PREFIX xsd:
PREFIX ex:
- a odrl:Request ;
- odrl:uid ex:HCPX-request ;
+ a odrl:Agreement ;
+ odrl:uid ex:HCPX-agreement ;
odrl:profile oac: ;
- dcterms:description "HCP X requests to read Alice's health data for bariatric care.";
- odrl:permission .
+ odrl:permission .
- a odrl:Permission ;
+ a odrl:Permission ;
odrl:action odrl:read ;
odrl:target <${terms.resources.smartwatch}> ;
odrl:assigner <${terms.agents.ruben}> ;
odrl:assignee <${terms.agents.alice}> ;
- odrl:constraint ,
- .
+ odrl:constraint ,
+ .
- a odrl:Constraint ;
+ a odrl:Constraint ;
odrl:leftOperand odrl:purpose ; # can also be oac:Purpose, to conform with OAC profile
odrl:operator odrl:eq ;
odrl:rightOperand ex:bariatric-care .
- a odrl:Constraint ;
+ a odrl:Constraint ;
odrl:leftOperand oac:LegalBasis ;
odrl:operator odrl:eq ;
odrl:rightOperand eu-gdpr:A9-2-a .`
@@ -129,7 +128,7 @@ on the condition of the purpose of the request being "http://example.org/bariatr
method: "GET",
headers: { "content-type": "application/json" },
});
-
+
const umaHeader = await res.headers.get('WWW-Authenticate')
log(`First, a resource request is done without authorization that results in a 403 response and accompanying UMA ticket in the WWW-Authenticate header according to the UMA specification:
@@ -164,12 +163,12 @@ ${umaHeader}`)
headers: { "content-type": "application/json" },
body: JSON.stringify(smartWatchAccessRequestNoClaimsODRL),
});
-
+
if (doctor_needInfoResponse.status !== 403) { log('Access request succeeded without claims...', await doctor_needInfoResponse.text()); throw 0; }
const { ticket: ticket2, required_claims: doctor_claims } = await doctor_needInfoResponse.json();
ticket = ticket2
-
+
log(`Based on the policy set above, the Authorization Server requests the following claims from the doctor:`);
doctor_claims.claim_token_format[0].forEach((format: string) => log(` - ${format}`))
log(`accompanied by an updated ticket: ${ticket}.`)
@@ -225,7 +224,7 @@ ${umaHeader}`)
],
} ],
// claims: [{
- claim_token: claim_token,
+ claim_token: claim_token,
claim_token_format: "urn:solidlab:uma:claims:formats:jwt",
// }],
// UMA specific fields
@@ -235,7 +234,7 @@ ${umaHeader}`)
log('Together with the UMA grant_type and ticket requirements, these are bundled as an ODRL Request and sent back to the Authorization Server')
log(JSON.stringify(smartWatchAccessRequestODRL, null, 2))
-
+
log(chalk.italic(`Note: the ODRL Request constraints are not yet evaluated as claims, only the passed claim token is.
There are two main points of work here: right now the claim token gathers all claims internally, as only a single token can be passed.
This is problematic when claims and OIDC tokens have to be passed. It might be worth looking deeper into ODRL requests to carry these claims instead of an UMA token.`))
@@ -246,19 +245,19 @@ This is problematic when claims and OIDC tokens have to be passed. It might be w
body: JSON.stringify(smartWatchAccessRequestODRL)
});
- if (accessGrantedResponse.status !== 200) {
- log('Access request failed despite policy...', JSON.stringify(await accessGrantedResponse.text(), null, 2)); throw 0;
+ if (accessGrantedResponse.status !== 200) {
+ log('Access request failed despite policy...', JSON.stringify(await accessGrantedResponse.text(), null, 2)); throw 0;
}
const tokenParams = await accessGrantedResponse.json();
const access_token = parseJwt(tokenParams.access_token)
- log(`The UMA server checks the claims with the relevant policy, and returns the agent an access token with the requested permissions.`,
+ log(`The UMA server checks the claims with the relevant policy, and returns the agent an access token with the requested permissions.`,
JSON.stringify(access_token.permissions, null, 2));
-
- log(`and the accompanying agreement:`,
+
+ log(`and the accompanying agreement:`,
JSON.stringify(access_token.contract, null, 2));
-
+
log(chalk.italic(`Future work: at a later stage, this agreements will be signed by both parties to form a binding contract.`))
const accessWithTokenResponse = await fetch(terms.resources.smartwatch, {
@@ -268,7 +267,7 @@ This is problematic when claims and OIDC tokens have to be passed. It might be w
log(`Now the doctor can retrieve the resource:`, await accessWithTokenResponse.text());
if (accessWithTokenResponse.status !== 200) { log(`Access with token failed...`); throw 0; }
-
+
}
main();
@@ -301,4 +300,3 @@ async function initContainer(policyContainer: string): Promise {
}
}
}
-
diff --git a/package.json b/package.json
index 583749ff..8167682c 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"build": "yarn workspaces foreach --include 'packages/*' -A -pi -j unlimited -t run build",
"test": "yarn workspaces foreach --include 'packages/*' -A -pi -j unlimited run test",
"start": "yarn workspaces foreach --include 'packages/*' -A -pi -j unlimited run start",
+ "start:odrl": "yarn workspace @solidlab/uma run start:odrl & yarn workspace @solidlab/uma-css run start",
"start:demo": "yarn workspaces foreach --include 'packages/*' -A -pi -j unlimited run demo",
"script:demo": "yarn exec tsx ./demo/flow.ts",
"script:demo-test": "yarn exec tsx ./demo/flow-test.ts",
@@ -64,6 +65,7 @@
"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",
"sync:list": "syncpack list-mismatches",
"sync:fix": "syncpack fix-mismatches"
diff --git a/packages/css/config/uma/default.json b/packages/css/config/uma/default.json
index 59fffc44..61ddfd5f 100644
--- a/packages/css/config/uma/default.json
+++ b/packages/css/config/uma/default.json
@@ -6,6 +6,7 @@
"uma-css:config/uma/overrides/account-seeding.json",
"uma-css:config/uma/overrides/account-store.json",
"uma-css:config/uma/overrides/authorization-handler.json",
+ "uma-css:config/uma/overrides/authorizer.json",
"uma-css:config/uma/overrides/jwks.json",
"uma-css:config/uma/overrides/token-extractor.json",
"uma-css:config/uma/overrides/www-auth.json",
diff --git a/packages/css/config/uma/overrides/authorization-handler.json b/packages/css/config/uma/overrides/authorization-handler.json
index b3a4d4a8..05fbc48f 100644
--- a/packages/css/config/uma/overrides/authorization-handler.json
+++ b/packages/css/config/uma/overrides/authorization-handler.json
@@ -18,14 +18,7 @@
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
"modesExtractor": { "@id": "urn:solid-server:default:ModesExtractor" },
"permissionReader": { "@id": "urn:solid-server:default:PermissionReader" },
- "authorizer": {
- "comment": "Requests UMA ticket when authorization fails.",
- "@id": "urn:solid-server:default:UmaAuthorizer",
- "@type": "UmaAuthorizer",
- "authorizer": { "@id": "urn:solid-server:default:Authorizer" },
- "umaClient": { "@id": "urn:solid-server:default:UmaClient" },
- "ownerUtil": { "@id": "urn:solid-server:default:OwnerUtil" }
- },
+ "authorizer": { "@id": "urn:solid-server:default:Authorizer" },
"operationHandler": { "@id": "urn:solid-server:default:OperationHandler" }
}
}
diff --git a/packages/css/config/uma/overrides/authorizer.json b/packages/css/config/uma/overrides/authorizer.json
new file mode 100644
index 00000000..10a337a6
--- /dev/null
+++ b/packages/css/config/uma/overrides/authorizer.json
@@ -0,0 +1,30 @@
+{
+ "@context": [
+ "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
+ "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/uma-css/^0.0.0/components/context.jsonld"
+ ],
+ "@graph": [
+ {
+ "@id": "urn:solid-server:overrides:AuthorizerOverride",
+ "@type": "Override",
+ "overrideInstance": {
+ "@id": "urn:solid-server:default:Authorizer"
+ },
+ "overrideParameters": {
+ "@type": "UmaAuthorizer",
+ "authorizer": {
+ "@type": "PermissionBasedAuthorizer",
+ "resourceSet": {
+ "@id": "urn:solid-server:default:CachedResourceSet"
+ }
+ },
+ "umaClient": {
+ "@id": "urn:solid-server:default:UmaClient"
+ },
+ "ownerUtil": {
+ "@id": "urn:solid-server:default:OwnerUtil"
+ }
+ }
+ }
+ ]
+}
diff --git a/packages/css/src/server/description/AccountSettingsStorageDescriber.ts b/packages/css/src/server/description/AccountSettingsStorageDescriber.ts
index 0444a6a8..e3d606d4 100644
--- a/packages/css/src/server/description/AccountSettingsStorageDescriber.ts
+++ b/packages/css/src/server/description/AccountSettingsStorageDescriber.ts
@@ -3,8 +3,8 @@ import type { AccountSettings, AccountStore, PodStore, ResourceIdentifier } from
import { StorageDescriber } from '@solid/community-server';
import { DataFactory } from 'n3';
import { stringToTerm } from 'rdf-string';
-import namedNode = DataFactory.namedNode;
-import quad = DataFactory.quad;
+
+const {quad, namedNode} = DataFactory
/**
* Adds triples to the storage description resource, based on the settings of
diff --git a/packages/ucp/src/util/Vocabularies.ts b/packages/ucp/src/util/Vocabularies.ts
index cf0d14a6..96479282 100644
--- a/packages/ucp/src/util/Vocabularies.ts
+++ b/packages/ucp/src/util/Vocabularies.ts
@@ -108,12 +108,13 @@ export const RDF = createVocabulary(
'type',
);
-
+
export const ODRL = createVocabulary(
'http://www.w3.org/ns/odrl/2/',
'Agreement',
'Offer',
'Permission',
+ 'Request',
'action',
'target',
'assignee',
diff --git a/packages/uma/bin/demo.ts b/packages/uma/bin/demo.ts
index c4414feb..3e7ee5b0 100644
--- a/packages/uma/bin/demo.ts
+++ b/packages/uma/bin/demo.ts
@@ -20,6 +20,7 @@ export const launch: () => Promise = async () => {
// 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');
diff --git a/packages/uma/bin/main.ts b/packages/uma/bin/main.ts
index 2e32ab73..61c43a8a 100644
--- a/packages/uma/bin/main.ts
+++ b/packages/uma/bin/main.ts
@@ -20,6 +20,7 @@ export const launch: () => Promise = async () => {
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');
diff --git a/packages/uma/bin/odrl.ts b/packages/uma/bin/odrl.ts
new file mode 100644
index 00000000..c4e1e64b
--- /dev/null
+++ b/packages/uma/bin/odrl.ts
@@ -0,0 +1,46 @@
+import { ServerInitializer, setGlobalLoggerFactory, WinstonLoggerFactory } from '@solid/community-server';
+import * as path from 'path';
+import { ComponentsManager } from 'componentsjs';
+
+const protocol = 'http';
+const host = 'localhost';
+const port = 4000;
+
+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'];
+
+ setGlobalLoggerFactory(new WinstonLoggerFactory('info'));
+
+ const manager = await ComponentsManager.build({
+ mainModulePath,
+ logLevel: 'silly',
+ typeChecking: false,
+ });
+
+ await manager.configRegistry.register(configPath);
+
+ const umaServer: ServerInitializer = await manager.instantiate('urn:uma:default:NodeHttpServer',{variables});
+ await umaServer.handleSafe();
+
+};
+
+launch();
diff --git a/packages/uma/config/default.json b/packages/uma/config/default.json
index a0ed3888..6ecaa44a 100644
--- a/packages/uma/config/default.json
+++ b/packages/uma/config/default.json
@@ -18,7 +18,7 @@
"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/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"
diff --git a/packages/uma/config/demo.json b/packages/uma/config/demo.json
index 878290dc..5ba92ebd 100644
--- a/packages/uma/config/demo.json
+++ b/packages/uma/config/demo.json
@@ -33,10 +33,7 @@
"@id": "urn:uma:default:AllAuthorizer"
}
}
- ],
- "fallback": {
- "@id": "urn:uma:default:PolicyBasedAuthorizer"
- }
+ ]
}
},
{
diff --git a/packages/uma/config/odrl.json b/packages/uma/config/odrl.json
new file mode 100644
index 00000000..ae2ffbcb
--- /dev/null
+++ b/packages/uma/config/odrl.json
@@ -0,0 +1,119 @@
+{
+ "@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"
+ ],
+ "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"
+ ],
+ "@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: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/default.json b/packages/uma/config/policies/authorizers/default.json
index 83c99660..c6daccc1 100644
--- a/packages/uma/config/policies/authorizers/default.json
+++ b/packages/uma/config/policies/authorizers/default.json
@@ -49,11 +49,9 @@
}
],
"fallback": {
- "@id": "urn:uma:default:PolicyBasedAuthorizer",
- "@type": "PolicyBasedAuthorizer",
- "rulesDir": {
- "@id": "urn:uma:variables:rulesDir"
- },
+ "@id": "urn:uma:default:OdrlAuthorizer",
+ "@type": "OdrlAuthorizer",
+ "eyePath": { "@id": "urn:uma:variables:eyePath" },
"policies": {
"@id": "urn:uma:default:RulesStorage",
"@type": "DirectoryUCRulesStorage",
diff --git a/packages/uma/config/policies/authorizers/odrl.json b/packages/uma/config/policies/authorizers/odrl.json
new file mode 100644
index 00000000..c42b2a13
--- /dev/null
+++ b/packages/uma/config/policies/authorizers/odrl.json
@@ -0,0 +1,20 @@
+{
+ "@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/rules/odrl/policy0.ttl b/packages/uma/config/rules/odrl/policy0.ttl
new file mode 100644
index 00000000..07b9f138
--- /dev/null
+++ b/packages/uma/config/rules/odrl/policy0.ttl
@@ -0,0 +1,43 @@
+@prefix ex: .
+@prefix odrl: .
+
+ex:usagePolicy1 a odrl:Agreement .
+ex:usagePolicy1 odrl:permission ex:permission1 .
+ex:permission1 a odrl:Permission .
+ex:permission1 odrl:action odrl:modify .
+ex:permission1 odrl:target .
+ex:permission1 odrl:assignee .
+ex:permission1 odrl:assigner .
+
+ex:usagePolicy1a a odrl:Agreement .
+ex:usagePolicy1a odrl:permission ex:permission1a .
+ex:permission1a a odrl:Permission .
+ex:permission1a odrl:action odrl:create .
+ex:permission1a odrl:target .
+ex:permission1a odrl:assignee .
+ex:permission1a odrl:assigner .
+
+ex:usagePolicy2 a odrl:Agreement .
+ex:usagePolicy2 odrl:permission ex:permission2a .
+ex:permission2 a odrl:Permission .
+ex:permission2 odrl:action odrl:modify .
+ex:permission2 odrl:target .
+ex:permission2 odrl:assignee .
+ex:permission2 odrl:assigner .
+
+ex:usagePolicy2a a odrl:Agreement .
+ex:usagePolicy2a odrl:permission ex:permission2 .
+ex:permission2a a odrl:Permission .
+ex:permission2a odrl:action odrl:create .
+ex:permission2a odrl:target .
+ex:permission2a odrl:assignee .
+ex:permission2a odrl:assigner .
+
+
+ex:usagePolicy3 a odrl:Agreement .
+ex:usagePolicy3 odrl:permission ex:permission3 .
+ex:permission3 a odrl:Permission .
+ex:permission3 odrl:action odrl:read .
+ex:permission3 odrl:target .
+ex:permission3 odrl:assignee .
+ex:permission3 odrl:assigner .
diff --git a/packages/uma/config/rules/policy/policy0.ttl b/packages/uma/config/rules/policy/policy0.ttl
index 340e0217..8a267103 100644
--- a/packages/uma/config/rules/policy/policy0.ttl
+++ b/packages/uma/config/rules/policy/policy0.ttl
@@ -4,7 +4,15 @@
ex:usagePolicy a odrl:Agreement .
ex:usagePolicy odrl:permission ex:permission .
ex:permission a odrl:Permission .
-ex:permission odrl:action odrl:read , odrl:modify .
+ex:permission odrl:action odrl:read , odrl:create , odrl:modify .
ex:permission odrl:target , .
ex:permission odrl:assignee .
ex:permission odrl:assigner .
+
+ex:usagePolicy2 a odrl:Agreement .
+ex:usagePolicy2 odrl:permission ex:permission2 .
+ex:permission2 a odrl:Permission .
+ex:permission2 odrl:action odrl:create , odrl:modify .
+ex:permission2 odrl:target , .
+ex:permission2 odrl:assignee .
+ex:permission2 odrl:assigner .
diff --git a/packages/uma/config/variables/default.json b/packages/uma/config/variables/default.json
index f71183b9..7b677185 100644
--- a/packages/uma/config/variables/default.json
+++ b/packages/uma/config/variables/default.json
@@ -26,6 +26,11 @@
"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",
+ "@type": "Variable"
}
]
}
diff --git a/packages/uma/package.json b/packages/uma/package.json
index 84d3c918..59a154c7 100644
--- a/packages/uma/package.json
+++ b/packages/uma/package.json
@@ -57,6 +57,7 @@
"build:components": "yarn run -T componentsjs-generator -r sai-uma -s src -c dist/components -i .componentsignore --lenient",
"test": "yarn run -T jest --coverage",
"start": "yarn run -T ts-node bin/main.ts",
+ "start:odrl": "yarn run -T ts-node bin/odrl.ts",
"demo": "yarn run -T ts-node bin/demo.ts"
},
"dependencies": {
@@ -72,6 +73,7 @@
"koreografeye": "^0.4.8",
"logform": "^2.6.0",
"n3": "^1.17.2",
+ "odrl-evaluator": "^0.3.0",
"ts-node": "^10.9.2",
"uri-template-lite": "^23.4.0",
"winston": "^3.11.0"
diff --git a/packages/uma/src/credentials/verify/JwtVerifier.ts b/packages/uma/src/credentials/verify/JwtVerifier.ts
index 4e16cef2..fe9c6da1 100644
--- a/packages/uma/src/credentials/verify/JwtVerifier.ts
+++ b/packages/uma/src/credentials/verify/JwtVerifier.ts
@@ -4,7 +4,7 @@ import { ClaimSet } from '../ClaimSet';
import { Credential } from "../Credential";
import { JWT } from '../Formats';
import { decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';
-import buildGetJwks from 'get-jwks';
+import buildGetJwks, {GetJwks} from 'get-jwks';
/**
* An UNSECURE Verifier that parses Tokens of the format `encode_uri(webId)[:encode_uri(clientId)]`,
@@ -12,7 +12,7 @@ import buildGetJwks from 'get-jwks';
*/
export class JwtVerifier implements Verifier {
protected readonly logger = getLoggerFor(this);
- protected jwks = buildGetJwks();
+ protected jwks:GetJwks = buildGetJwks();
constructor(
private readonly allowedClaims: string[],
diff --git a/packages/uma/src/index.ts b/packages/uma/src/index.ts
index 3d4dc657..e6e1d480 100644
--- a/packages/uma/src/index.ts
+++ b/packages/uma/src/index.ts
@@ -23,6 +23,7 @@ export * from './policies/authorizers/Authorizer';
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';
diff --git a/packages/uma/src/policies/authorizers/NamespacedAuthorizer.ts b/packages/uma/src/policies/authorizers/NamespacedAuthorizer.ts
index 2d199800..9236e4f3 100644
--- a/packages/uma/src/policies/authorizers/NamespacedAuthorizer.ts
+++ b/packages/uma/src/policies/authorizers/NamespacedAuthorizer.ts
@@ -42,7 +42,7 @@ export class NamespacedAuthorizer implements Authorizer {
}
// Find applicable authorizer
- const authorizer = ns ? this.authorizers[ns] : this.fallback;
+ const authorizer = (ns && this.authorizers[ns]) || this.fallback;
// Delegate to authorizer
return authorizer.permissions(claims, query);
diff --git a/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts b/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts
new file mode 100644
index 00000000..70bac1d8
--- /dev/null
+++ b/packages/uma/src/policies/authorizers/OdrlAuthorizer.ts
@@ -0,0 +1,300 @@
+import { createVocabulary, DC, getLoggerFor, RDF } from '@solid/community-server';
+import { basicPolicy, ODRL, UCPPolicy, UCRulesStorage } from '@solidlab/ucp';
+import { DataFactory, Literal, NamedNode, Quad_Subject, Store, Writer } from 'n3';
+import { EyeReasoner, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator'
+import { WEBID } from '../../credentials/Claims';
+import { ClaimSet } from '../../credentials/ClaimSet';
+import { Requirements } from '../../credentials/Requirements';
+import { Permission } from '../../views/Permission';
+import { Authorizer } from './Authorizer';
+
+const {quad, namedNode, literal} = DataFactory
+
+/**
+ * Permission evaluation is performed as follows:
+ *
+ * 1. Conversion of Permission queries to ODRL Requests.
+ * - A translation is performed to transform CSS actions to ODRL actions.
+ * - One ODRL Request per Action and target Resource.
+ *
+ * 2. ODRL Evaluator performs ODRL Evaluation
+ * - No policy selection is performed (all policies are inserted rather than all relevant).
+ * - No conflict resolution strategy is present (Prohibition policies are ignored).
+ * - No duties are checked.
+ *
+ * 3. Conversion from ODRL Policy Compliance Reports to Permissions
+ * - Selecting the ODRL actions from Active Permission Reports
+ * - Translation from ODRL actions to CSS actions
+ */
+export class OdrlAuthorizer implements Authorizer {
+ protected readonly logger = getLoggerFor(this);
+ private readonly odrlEvaluator: ODRLEvaluator;
+
+ /**
+ * Creates a OdrlAuthorizer enforcing policies using ODRL with the ODRL Evaluator.
+ *
+ *
+ * @param policies - A store containing the ODRL policy rules.
+ * @param eyePath - The path to run the local EYE reasoner, if there is one.
+ */
+ constructor(
+ private readonly policies: UCRulesStorage,
+ eyePath?: string,
+ ) {
+ const engine = eyePath ?
+ new ODRLEngineMultipleSteps({reasoner: new EyeReasoner(eyePath, ["--quiet", "--nope", "--pass-only-new"])}) :
+ new ODRLEngineMultipleSteps();
+ this.odrlEvaluator = new ODRLEvaluator(engine);
+ }
+
+ public async permissions(claims: ClaimSet, query?: Permission[]): Promise {
+ this.logger.info(`Calculating permissions. ${JSON.stringify({claims, query})}`);
+ if (!query) {
+ this.logger.warn('The OdrlAuthorizer can only calculate permissions for explicit queries.')
+ return [];
+ }
+
+ // key value store for building the permissions to be granted on a resource
+ const grantedPermissions: { [key: string]: string[] } = {};
+
+ // prepare policy
+ const policyStore = (await this.policies.getStore())
+
+ // prepare sotw
+ const sotw = new Store();
+ sotw.add(quad(namedNode('http://example.com/request/currentTime'), namedNode('http://purl.org/dc/terms/issued'), literal(new Date().toISOString(), namedNode("http://www.w3.org/2001/XMLSchema#dateTime"))));
+
+ const subject = typeof claims[WEBID] === 'string' ? claims[WEBID] : 'urn:solidlab:uma:id:anonymous';
+
+
+ for (const {resource_id, resource_scopes} of query) {
+ if (!resource_id) {
+ this.logger.warn('The OdrlAuthorizer can only calculate permissions for explicit resources.');
+ continue;
+ }
+
+ grantedPermissions[resource_id] = [];
+ const actions = resource_scopes ? transformActionsCssToOdrl(resource_scopes) : ["http://www.w3.org/ns/odrl/2/use"]
+ for (const action of actions) {
+ this.logger.info(`Evaluating Request [S R AR]: [${subject} ${resource_id} ${action}]`);
+ const requestPolicy: UCPPolicy = {
+ type: ODRL.Request,
+ rules: [
+ {
+ action: action,
+ resource: resource_id,
+ requestingParty: subject
+ }
+ ]
+ }
+ const requestStore = basicPolicy(requestPolicy).representation
+ // evaluate policies
+ const reports = await this.odrlEvaluator.evaluate(
+ [...policyStore],
+ [...requestStore],
+ [...sotw]);
+ const reportStore = new Store(reports);
+
+ // TODO: handle multiple reports -> possible to be generated
+ // NOTE: current strategy, add all actions of active reports generated by the request
+ // fetch active and attempted
+ const PolicyReportNodes = reportStore.getSubjects(RDF.type, CR.PolicyReport, null);
+ for (const policyReportNode of PolicyReportNodes) {
+ const policyReport = parseComplianceReport(policyReportNode, reportStore)
+ const activeReports = policyReport.ruleReport.filter(
+ (report) => report.activationState === ActivationState.Active);
+ if (activeReports.length > 0 && activeReports[0].type === RuleReportType.PermissionReport) {
+ grantedPermissions[resource_id].push(action);
+ }
+ }
+ }
+ }
+ const permissions: Permission[] = []
+ Object.keys(grantedPermissions).forEach(
+ resource_id => permissions.push({
+ resource_id,
+ resource_scopes: transformActionsOdrlToCss(grantedPermissions[resource_id])
+ }) );
+ return permissions;
+ }
+
+ public async credentials(permissions: Permission[], query?: Requirements | undefined): Promise {
+ throw new Error("Method not implemented.");
+ }
+
+}
+const scopeCssToOdrl: Map = new Map();
+scopeCssToOdrl.set('urn:example:css:modes:read','http://www.w3.org/ns/odrl/2/read');
+scopeCssToOdrl.set('urn:example:css:modes:append','http://www.w3.org/ns/odrl/2/append');
+scopeCssToOdrl.set('urn:example:css:modes:create','http://www.w3.org/ns/odrl/2/create');
+scopeCssToOdrl.set('urn:example:css:modes:delete','http://www.w3.org/ns/odrl/2/delete');
+scopeCssToOdrl.set('urn:example:css:modes:write','http://www.w3.org/ns/odrl/2/write');
+
+const scopeOdrlToCss : Map = new Map(Array.from(scopeCssToOdrl, entry => [entry[1], entry[0]]));
+
+/**
+ * Transform the Actions enforced by the Community Solid Server to equivalent ODRL Actions
+ * @param actions
+ */
+function transformActionsCssToOdrl(actions: string[]): string[] {
+ // scopes come from UmaClient.ts -> see CSS package
+
+ // in UMAPermissionReader, only the last part of the URN will be used, divided by a colon
+ // again, see CSS package
+ return actions.map(action => scopeCssToOdrl.get(action)!);
+}
+/**
+ * Transform ODRL Actions to equivalent Actions enforced by the Community Solid Server
+ * @param actions
+ */
+function transformActionsOdrlToCss(actions: string[]): string[] {
+ const cssActions = []
+ for (const action of actions) {
+ if (action === 'http://www.w3.org/ns/odrl/2/use'){
+ return Array.from(scopeCssToOdrl.keys());
+ }
+ cssActions.push(scopeOdrlToCss.get(action)!);
+ }
+ return cssActions;
+}
+
+type PolicyReport = {
+ id: NamedNode;
+ created: Literal;
+ request: NamedNode;
+ policy: NamedNode;
+ ruleReport: RuleReport[];
+}
+type RuleReport = {
+ id: NamedNode;
+ type: RuleReportType;
+ activationState: ActivationState
+ rule: NamedNode;
+ requestedRule: NamedNode;
+ premiseReport: PremiseReport[]
+}
+
+type PremiseReport = {
+ id: NamedNode;
+ type:PremiseReportType;
+ premiseReport: PremiseReport[];
+ satisfactionState: SatisfactionState
+}
+
+// is it possible to just use CR.namespace + "term"?
+// https://github.com/microsoft/TypeScript/issues/40793
+enum RuleReportType {
+ PermissionReport= 'https://w3id.org/force/compliance-report#PermissionReport',
+ ProhibitionReport= 'https://w3id.org/force/compliance-report#ProhibitionReport',
+ ObligationReport= 'https://w3id.org/force/compliance-report#ObligationReport',
+}
+enum SatisfactionState {
+ Satisfied= 'https://w3id.org/force/compliance-report#Satisfied',
+ Unsatisfied= 'https://w3id.org/force/compliance-report#Unsatisfied',
+}
+
+enum PremiseReportType {
+ ConstraintReport = 'https://w3id.org/force/compliance-report#ConstraintReport',
+ PartyReport = 'https://w3id.org/force/compliance-report#PartyReport',
+ TargetReport = 'https://w3id.org/force/compliance-report#TargetReport',
+ ActionReport = 'https://w3id.org/force/compliance-report#ActionReport',
+}
+
+enum ActivationState {
+ Active= 'https://w3id.org/force/compliance-report#Active',
+ Inactive= 'https://w3id.org/force/compliance-report#Inactive',
+}
+
+/**
+ * Parses an ODRL Compliance Report Model into a {@link PolicyReport}.
+ * @param identifier
+ * @param store
+ */
+function parseComplianceReport(identifier: Quad_Subject, store: Store): PolicyReport {
+ const exists = store.getQuads(identifier,RDF.type,CR.PolicyReport, null).length === 1;
+ if (!exists) { throw Error(`No Policy Report found with: ${identifier}.`); }
+ const ruleReportNodes = store.getObjects(identifier, CR.ruleReport, null) as NamedNode[];
+
+ return {
+ id: identifier as NamedNode,
+ created: store.getObjects(identifier, DC.namespace+"created", null)[0] as Literal,
+ policy: store.getObjects(identifier, CR.policy, null)[0] as NamedNode,
+ request: store.getObjects(identifier, CR.policyRequest, null)[0] as NamedNode,
+ ruleReport: ruleReportNodes.map(ruleReportNode => parseRuleReport(ruleReportNode, store))
+ }
+}
+
+/**
+ * Parses Rule Reports from a Compliance Report, including its premises
+ * @param identifier
+ * @param store
+ */
+function parseRuleReport(identifier: Quad_Subject, store: Store): RuleReport {
+ const premiseNodes = store.getObjects(identifier,CR.premiseReport, null) as NamedNode[];
+ return {
+ id: identifier as NamedNode,
+ type: store.getObjects(identifier, RDF.type, null)[0].value as RuleReportType,
+ activationState: store.getObjects(identifier, CR.activationState, null)[0].value as ActivationState,
+ requestedRule: store.getObjects(identifier, CR.ruleRequest, null)[0] as NamedNode,
+ rule: store.getObjects(identifier, CR.rule, null)[0] as NamedNode,
+ premiseReport: premiseNodes.map((prem) => parsePremiseReport(prem, store))
+ }
+}
+
+/**
+ * Parses Premise Reports, including premises of a Premise Report itself.
+ * Note that if for some reason there are circular premise reports, this will result into an infinite loop
+ * @param identifier
+ * @param store
+ */
+function parsePremiseReport(identifier: Quad_Subject, store: Store): PremiseReport {
+ const nestedPremises = store.getObjects(identifier, CR.PremiseReport, null) as NamedNode[];
+ return {
+ id: identifier as NamedNode,
+ type: store.getObjects(identifier, RDF.type, null)[0].value as PremiseReportType,
+ premiseReport: nestedPremises.map((prem) => parsePremiseReport(prem, store)),
+ satisfactionState: store.getObjects(identifier, CR.satisfactionState, null)[0].value as SatisfactionState
+ }
+}
+const CR = createVocabulary('https://w3id.org/force/compliance-report#',
+ 'PolicyReport',
+ 'RuleReport',
+ 'PermissionReport',
+ 'ProhibitionReport',
+ 'DutyReport',
+ 'PremiseReport',
+ 'ConstraintReport',
+ 'PartyReport',
+ 'ActionReport',
+ 'TargetReport',
+ 'ActivationState',
+ 'Active',
+ 'Inactive',
+ 'AttemptState',
+ 'Attempted',
+ 'NotAttempted',
+ 'PerformanceState',
+ 'Performed',
+ 'Unperformed',
+ 'Unknown',
+ 'DeonticState',
+ 'NonSet',
+ 'Violated',
+ 'Fulfilled',
+ 'SatisfactionState',
+ 'Satisfied',
+ 'Unsatisfied',
+ 'policy',
+ 'policyRequest',
+ 'ruleReport',
+ 'conditionReport',
+ 'premiseReport',
+ 'rule',
+ 'ruleRequest',
+ 'activationState',
+ 'attemptState',
+ 'performanceState',
+ 'deonticState',
+ 'constraint',
+ 'satisfactionState',
+ )
diff --git a/packages/uma/test/authz/OdrlAuthorizer.test.ts b/packages/uma/test/authz/OdrlAuthorizer.test.ts
new file mode 100644
index 00000000..1bbefa4b
--- /dev/null
+++ b/packages/uma/test/authz/OdrlAuthorizer.test.ts
@@ -0,0 +1,136 @@
+import {OdrlAuthorizer} from "../../src/policies/authorizers/OdrlAuthorizer";
+import {turtleStringToStore} from "odrl-evaluator";
+import {Store} from "n3";
+import {MemoryUCRulesStorage, UCRulesStorage} from "@solidlab/ucp";
+import {ClaimSet} from "../../src/credentials/ClaimSet";
+import {Permission} from "../../src/views/Permission";
+import {Authorizer} from "../../src/policies/authorizers/Authorizer";
+
+const resourceModifyPolicy = `
+@prefix ex: .
+@prefix odrl: .
+
+ex:usagePolicy1 a odrl:Agreement .
+ex:usagePolicy1 odrl:permission ex:permission1 .
+ex:permission1 a odrl:Permission .
+ex:permission1 odrl:action odrl:modify .
+ex:permission1 odrl:target .
+ex:permission1 odrl:assignee .
+ex:permission1 odrl:assigner .
+`;
+
+const resourceCreatePolicy = `
+@prefix ex: .
+@prefix odrl: .
+ex:usagePolicy1a a odrl:Agreement .
+ex:usagePolicy1a odrl:permission ex:permission1a .
+ex:permission1a a odrl:Permission .
+ex:permission1a odrl:action odrl:create .
+ex:permission1a odrl:target .
+ex:permission1a odrl:assignee .
+ex:permission1a odrl:assigner .
+`
+
+const containerModifyPolicy = `
+@prefix ex: .
+@prefix odrl: .
+ex:usagePolicy2 a odrl:Agreement .
+ex:usagePolicy2 odrl:permission ex:permission2a .
+ex:permission2 a odrl:Permission .
+ex:permission2 odrl:action odrl:modify .
+ex:permission2 odrl:target .
+ex:permission2 odrl:assignee .
+ex:permission2 odrl:assigner .
+`
+
+const containerCreatePolicy = `
+@prefix ex: .
+@prefix odrl: .
+ex:usagePolicy2a a odrl:Agreement .
+ex:usagePolicy2a odrl:permission ex:permission2 .
+ex:permission2a a odrl:Permission .
+ex:permission2a odrl:action odrl:create .
+ex:permission2a odrl:target .
+ex:permission2a odrl:assignee .
+ex:permission2a odrl:assigner .
+`
+
+const resourceReadPolicy = `
+@prefix ex: .
+@prefix odrl: .
+ex:usagePolicy3 a odrl:Agreement .
+ex:usagePolicy3 odrl:permission ex:permission3 .
+ex:permission3 a odrl:Permission .
+ex:permission3 odrl:action odrl:read .
+ex:permission3 odrl:target .
+ex:permission3 odrl:assignee .
+ex:permission3 odrl:assigner .
+`
+describe('Odrl Authorizer', () => {
+ let policyStore: Store;
+ let rulesStorage: UCRulesStorage = new MemoryUCRulesStorage();
+ let odrlAuthorizer: Authorizer;
+ let claims: ClaimSet;
+ let query: Permission[];
+
+ beforeAll(async () => {
+ policyStore = await turtleStringToStore(resourceModifyPolicy);
+ await rulesStorage.addRule(policyStore);
+ odrlAuthorizer = new OdrlAuthorizer(rulesStorage);
+ })
+
+ beforeEach(async () => {
+ claims = {'urn:solidlab:uma:claims:types:webid': 'https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me'}
+ query = [
+ {
+ resource_id: 'http://localhost:3000/alice/other/resource.txt',
+ resource_scopes: ['urn:example:css:modes:write', 'urn:example:css:modes:create']
+ },
+ {
+ resource_id: 'http://localhost:3000/alice/other/',
+ resource_scopes: ['urn:example:css:modes:create']
+ }
+ ]
+ })
+
+ test("for a modify policy, should give only write access when the claims match.", async () => {
+ const expectedPermission: Permission[] = [
+ {
+ resource_id: 'http://localhost:3000/alice/other/resource.txt',
+ resource_scopes: ['urn:example:css:modes:write']
+ },
+ {
+ resource_id: 'http://localhost:3000/alice/other/',
+ resource_scopes: []
+ }
+ ]
+ const calculatedPermissions = await odrlAuthorizer.permissions(claims, query);
+ expect(calculatedPermissions).toEqual(expectedPermission);
+ });
+
+ test("for a modify policy, should give no access due to lack of claims.", async () => {
+ claims = {}
+ const expectedPermission: Permission[] = [
+ {
+ resource_id: 'http://localhost:3000/alice/other/resource.txt',
+ resource_scopes: []
+ },
+ {
+ resource_id: 'http://localhost:3000/alice/other/',
+ resource_scopes: []
+ }
+ ]
+ const calculatedPermissions = await odrlAuthorizer.permissions(claims, query);
+ expect(calculatedPermissions).toEqual(expectedPermission);
+ });
+
+ test("for appropriate create resource policies, should give all access when the claims match.", async () => {
+ const policyStore = await turtleStringToStore(resourceModifyPolicy + resourceCreatePolicy + containerCreatePolicy + containerModifyPolicy + resourceReadPolicy);
+ const ruleStorage = new MemoryUCRulesStorage();
+ await ruleStorage.addRule(policyStore);
+ const odrlAuthorizer = new OdrlAuthorizer(ruleStorage);
+ const calculatedPermissions = await odrlAuthorizer.permissions(claims, query);
+ expect(calculatedPermissions).toEqual(query);
+
+ })
+})
diff --git a/scripts/test-uma-ODRL.ts b/scripts/test-uma-ODRL.ts
new file mode 100644
index 00000000..0c453bc9
--- /dev/null
+++ b/scripts/test-uma-ODRL.ts
@@ -0,0 +1,24 @@
+import {UserManagedAccessFetcher} from "./util/UMA-client";
+
+const resource = "http://localhost:3000/alice/other/resource.txt"
+const claim_token = "https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me"
+const claim_token_format = 'urn:solidlab:uma:claims:formats:webid'
+const fetcher = new UserManagedAccessFetcher({token:claim_token, token_format: claim_token_format});
+
+async function main() {
+ console.log(`Testing UMA flow using UMA Fetcher\n`)
+ const response = await fetcher.fetch(resource, {
+ method: "PUT",
+ body: "some text"
+ })
+
+ console.log(`Creating document with RPT, expecting HTTP status in 200 range: ${response.status}\n`);
+
+ const anonymousResponse = await fetch(resource)
+ console.log(`Reading document without RPT, expecting HTTP status in 400 range: ${anonymousResponse.status}\n`);
+
+ const readingResponse = await fetcher.fetch(resource)
+
+ console.log(`Reading document with RPT, expecting the content written away: ${await readingResponse.text()}\n`);
+}
+main()
diff --git a/scripts/util/UMA-client.ts b/scripts/util/UMA-client.ts
new file mode 100644
index 00000000..e8bb02eb
--- /dev/null
+++ b/scripts/util/UMA-client.ts
@@ -0,0 +1,124 @@
+import { fetch } from 'cross-fetch'
+
+/**
+ * Decodes a JSON Web Token (JWT) by parsing its payload.
+ *
+ * @param {string} token - The JSON Web Token to be parsed.
+ * @returns {Object} The decoded payload of the JWT as a JavaScript object.
+ *
+ */
+export function parseJwt(token: string): Object {
+ return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
+}
+
+/**
+ * Represents User-Managed Access (UMA) information, extracted from a RPT Request Response.
+ *
+ * @property {string} tokenEndpoint - The endpoint where the token can be requested.
+ * @property {string} ticket - The ticket used for the UMA session.
+ */
+export type UMA_Session = {
+ tokenEndpoint: string,
+ ticket: string
+}
+
+/**
+ * Parses the 'WWW-Authenticate' header from the given headers to extract UMA session details.
+ *
+ * @param {Headers} headers - The HTTP headers from which the 'WWW-Authenticate' header is to be extracted.
+ * @returns {UMA_Session} The parsed UMA session details.
+ * @throws Will throw an error if the 'WWW-Authenticate' header is not present.
+ */
+export function parseAuthenticateHeader(headers: Headers): UMA_Session {
+ const wwwAuthenticateHeader = headers.get("WWW-Authenticate")
+ if (!wwwAuthenticateHeader) throw Error("No WWW-Authenticate Header present");
+
+ const { as_uri, ticket } = Object.fromEntries(wwwAuthenticateHeader.replace(/^UMA /, '').split(', ').map(
+ param => param.split('=').map(s => s.replace(/"/g, ''))
+ ));
+
+ const tokenEndpoint = as_uri + "/token" // NOTE: should normally be retrieved from .well-known/uma2-configuration
+
+ return {
+ tokenEndpoint,
+ ticket
+ }
+}
+
+/**
+ * Represents a claim with a token and its format.
+ *
+ * @property {string} token - The claim token.
+ * @property {string} token_format - The format of the claim token.
+ */
+export type Claim = {
+ token: string,
+ token_format: string
+}
+
+/**
+ * Authenticated fetcher following the User Managed Access 2.0 Grant for Oauth 2.0 Authorization flow
+ * using one claim.
+ * (https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html)
+ */
+export class UserManagedAccessFetcher {
+ private readonly claim: Claim;
+ private readonly grant_type= 'urn:ietf:params:oauth:grant-type:uma-ticket';
+ public constructor(claim: Claim) {
+ this.claim = claim;
+ }
+
+ public async fetch(url: string, init: RequestInit = {}): Promise {
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.1
+ // 3.1 Client Requests Resource Without Providing an Access Token
+ const noTokenResponse = await fetch(url, init);
+ if (noTokenResponse.status > 199 && noTokenResponse.status < 300) {
+ console.log('No Authorization token was required.')
+ return noTokenResponse;
+ }
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.2
+ // 3.2 Resource Server Responds to Client's Tokenless Access Attempt
+
+ const { tokenEndpoint, ticket } = parseAuthenticateHeader(noTokenResponse.headers)
+
+ const content = {
+ grant_type: this.grant_type,
+ ticket,
+ claim_token: encodeURIComponent(this.claim.token),
+ claim_token_format: this.claim.token_format,
+ }
+
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.3.1
+ // 3.3.1 Client Request to Authorization Server for RPT
+ const asRequestResponse = await fetch(tokenEndpoint, {
+ method: "POST",
+ headers: {
+ "content-type": "application/json"
+ },
+ body: JSON.stringify(content),
+ });
+
+ if (asRequestResponse.status !== 200) {
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.3.6
+ // 3.3.6 Authorization Server Response to Client on Authorization Failure
+ // TODO: log properly
+ return asRequestResponse
+ throw Error("Authorization token not granted" + await asRequestResponse.text());
+ }
+
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.3.5
+ // 3.3.5 Authorization Server Response to Client on Authorization Success
+ const asResponse = await asRequestResponse.json();
+
+ // RPT added to header
+ const headers = new Headers(init.headers);
+ headers.set('Authorization', `${asResponse.token_type} ${asResponse.access_token}`);
+
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.4
+ // 3.4 Client Requests Resource and Provides an RPT
+ // https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#rfc.section.3.3.5
+ // 3.5 Resource Server Responds to Client's RPT-Accompanied Resource Request
+ return fetch(url, { ...init, headers });
+
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index a7b2adf1..e0b71c5b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4096,6 +4096,15 @@ __metadata:
languageName: node
linkType: hard
+"@rdfjs/types@npm:^2.0.0, @rdfjs/types@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "@rdfjs/types@npm:2.0.1"
+ dependencies:
+ "@types/node": "npm:*"
+ checksum: 10c0/81012b02e28737e15dfc27068664bbab7b989cc2ff82e0a6a341df492d9d97210b5e462d85bfb7882aa0ef5bf84e2c5e95647fb779b67b7b1910b1837d79c500
+ languageName: node
+ linkType: hard
+
"@rubensworks/saxes@npm:^6.0.1":
version: 6.0.1
resolution: "@rubensworks/saxes@npm:6.0.1"
@@ -4325,6 +4334,7 @@ __metadata:
koreografeye: "npm:^0.4.8"
logform: "npm:^2.6.0"
n3: "npm:^1.17.2"
+ odrl-evaluator: "npm:^0.3.0"
ts-node: "npm:^10.9.2"
uri-template-lite: "npm:^23.4.0"
winston: "npm:^3.11.0"
@@ -4439,6 +4449,18 @@ __metadata:
languageName: node
linkType: hard
+"@treecg/types@npm:^0.4.6":
+ version: 0.4.6
+ resolution: "@treecg/types@npm:0.4.6"
+ dependencies:
+ "@rdfjs/types": "npm:*"
+ loglevel: "npm:^1.8.1"
+ loglevel-plugin-prefix: "npm:^0.8.4"
+ rdf-data-factory: "npm:^1.1.0"
+ checksum: 10c0/956dfc34dbbcad4ef3099431be55bd1f5c8b1f5fca71f0bc90ca5823017ba25c70f6db1a066691541c900a59e1ad7522f041965162f66627e19e20c99bce5cf9
+ languageName: node
+ linkType: hard
+
"@tsconfig/node10@npm:^1.0.7":
version: 1.0.9
resolution: "@tsconfig/node10@npm:1.0.9"
@@ -4601,10 +4623,10 @@ __metadata:
languageName: node
linkType: hard
-"@types/emscripten@npm:^1.39.10":
- version: 1.39.10
- resolution: "@types/emscripten@npm:1.39.10"
- checksum: 10c0/c9adde9307d54efb5152931bfe99966fbe12fbd4d07663fb5cdc4cc1bd3a1f030882d50d4a27875b7b2d9713d160609e67b72e92177a021c9f4699ee5ac41035
+"@types/emscripten@npm:^1.39.10, @types/emscripten@npm:^1.39.13":
+ version: 1.40.1
+ resolution: "@types/emscripten@npm:1.40.1"
+ checksum: 10c0/0d6cd29e551f85ba49a0e7d58de16c857960d40e57553e7cc2860b7d80c4210c992ed292998ec3fd3bdc3b41d96541e91d01a6c232106ac0ad79b4710e87f38d
languageName: node
linkType: hard
@@ -4841,6 +4863,16 @@ __metadata:
languageName: node
linkType: hard
+"@types/n3@npm:^1.21.1":
+ version: 1.24.2
+ resolution: "@types/n3@npm:1.24.2"
+ dependencies:
+ "@rdfjs/types": "npm:*"
+ "@types/node": "npm:*"
+ checksum: 10c0/4d9fceea390134a1e4c3da63d493b841ffea8ef581264fdd6b764809403f99e94517770799fa53bfc01000b88a2627385502b20df7c0e7461869e694bb667b0a
+ languageName: node
+ linkType: hard
+
"@types/node@npm:^20.11.25":
version: 20.17.17
resolution: "@types/node@npm:20.17.17"
@@ -5022,6 +5054,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/uuid@npm:8.3.4":
+ version: 8.3.4
+ resolution: "@types/uuid@npm:8.3.4"
+ checksum: 10c0/b9ac98f82fcf35962317ef7dc44d9ac9e0f6fdb68121d384c88fe12ea318487d5585d3480fa003cf28be86a3bbe213ca688ba786601dce4a97724765eb5b1cf2
+ languageName: node
+ linkType: hard
+
"@types/uuid@npm:^9.0.0, @types/uuid@npm:^9.0.5":
version: 9.0.7
resolution: "@types/uuid@npm:9.0.7"
@@ -7024,6 +7063,20 @@ __metadata:
languageName: node
linkType: hard
+"eyereasoner@npm:^16.18.4":
+ version: 16.34.1
+ resolution: "eyereasoner@npm:16.34.1"
+ dependencies:
+ n3: "npm:^1.16.3"
+ swipl-wasm: "npm:4.0.13"
+ peerDependencies:
+ "@rdfjs/types": ^1.1.0
+ bin:
+ eyereasoner: dist/bin/index.js
+ checksum: 10c0/a1c78114edc20d94b7cb052305d97bc16e49ece53b7c221cda4309c6e92deb1ed485ba31fff728e4bd1195ee16421e1cf25f5da43225e52939ddac2bca9678ba
+ languageName: node
+ linkType: hard
+
"fast-check@npm:3.23.2, fast-check@npm:^3.21.0, fast-check@npm:^3.23.1":
version: 3.23.2
resolution: "fast-check@npm:3.23.2"
@@ -9117,6 +9170,20 @@ __metadata:
languageName: node
linkType: hard
+"loglevel-plugin-prefix@npm:^0.8.4":
+ version: 0.8.4
+ resolution: "loglevel-plugin-prefix@npm:0.8.4"
+ checksum: 10c0/357524eec4c165ff823b5bbf72e8373ff529e5cb95c1f4b20749847bd5b5b16ab328d6d33d1a9019f1a2dc52e28fca5d595e52f2ee20e24986182a6f9552a9ec
+ languageName: node
+ linkType: hard
+
+"loglevel@npm:^1.8.1":
+ version: 1.9.2
+ resolution: "loglevel@npm:1.9.2"
+ checksum: 10c0/1e317fa4648fe0b4a4cffef6de037340592cee8547b07d4ce97a487abe9153e704b98451100c799b032c72bb89c9366d71c9fb8192ada8703269263ae77acdc7
+ languageName: node
+ linkType: hard
+
"lowercase-keys@npm:^3.0.0":
version: 3.0.0
resolution: "lowercase-keys@npm:3.0.0"
@@ -9503,13 +9570,23 @@ __metadata:
languageName: node
linkType: hard
-"n3@npm:^1.16.1, n3@npm:^1.16.3, n3@npm:^1.16.4, n3@npm:^1.17.0, n3@npm:^1.17.1, n3@npm:^1.17.2, n3@npm:^1.6.3":
- version: 1.17.2
- resolution: "n3@npm:1.17.2"
+"n3@npm:^1.16.1, n3@npm:^1.16.3, n3@npm:^1.16.4, n3@npm:^1.17.0, n3@npm:^1.17.1, n3@npm:^1.17.2, n3@npm:^1.20.4, n3@npm:^1.6.3":
+ version: 1.25.1
+ resolution: "n3@npm:1.25.1"
dependencies:
- queue-microtask: "npm:^1.1.2"
+ buffer: "npm:^6.0.3"
+ readable-stream: "npm:^4.0.0"
+ checksum: 10c0/0a4efc3151d79c7200b57f78bb5d4411f6746d7f2d63ccc7fe068fa9a4d20afc03001957d9216ce2da62f2de266452713f9867112e9b3170307eb4ac059d5d1d
+ languageName: node
+ linkType: hard
+
+"n3@npm:^1.23.1":
+ version: 1.25.2
+ resolution: "n3@npm:1.25.2"
+ dependencies:
+ buffer: "npm:^6.0.3"
readable-stream: "npm:^4.0.0"
- checksum: 10c0/ab02073aa46b012cbaedc3027bcb081cec0421b19dd96c8c6d1fdb2c2dcf5ef23a3962e6f7638b78c50abf6ed5b9da05d2ef2ef6ef67887ce27ed8c3d1e174a6
+ checksum: 10c0/47cb03555ddceb98be9c5ed10d67c9a045afe13d1a4225fe2ace9a63dcf8e1b6afe6019d72cb03e320c2c1d2fb6942c18ec92cdf3673e792a2084d5b1b5e089f
languageName: node
linkType: hard
@@ -9721,6 +9798,26 @@ __metadata:
languageName: node
linkType: hard
+"odrl-evaluator@npm:^0.3.0":
+ version: 0.3.0
+ resolution: "odrl-evaluator@npm:0.3.0"
+ dependencies:
+ "@rdfjs/types": "npm:^1.1.0"
+ "@types/n3": "npm:^1.16.3"
+ eyereasoner: "npm:^16.18.4"
+ n3: "npm:^1.20.4"
+ rdf-isomorphic: "npm:^1.3.1"
+ rdf-lens: "npm:^1.3.5"
+ rdf-parse: "npm:^3.0.0"
+ rdf-store-stream: "npm:^2.0.1"
+ rdf-vocabulary: "npm:^1.0.1"
+ streamify-string: "npm:^1.0.1"
+ tmp: "npm:^0.2.3"
+ uuidv4: "npm:^6.2.13"
+ checksum: 10c0/a42fd82a48012ffbd8ff7165883bce10bce1a31fd5d8f82ba951cf67a9b438bbcf5d4fc6eb5ac52635ebc7c2411613509267cecf41ed3169ae5a5fe88da261f3
+ languageName: node
+ linkType: hard
+
"oidc-provider@npm:^8.4.0":
version: 8.4.3
resolution: "oidc-provider@npm:8.4.3"
@@ -10123,7 +10220,7 @@ __metadata:
languageName: node
linkType: hard
-"queue-microtask@npm:^1.1.2, queue-microtask@npm:^1.2.2":
+"queue-microtask@npm:^1.2.2":
version: 1.2.3
resolution: "queue-microtask@npm:1.2.3"
checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102
@@ -10190,6 +10287,15 @@ __metadata:
languageName: node
linkType: hard
+"rdf-data-factory@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "rdf-data-factory@npm:2.0.2"
+ dependencies:
+ "@rdfjs/types": "npm:^2.0.0"
+ checksum: 10c0/5c72ebc22f4910fd35bf3efb1e1def10785aca6f8d22dd8cee34c9ffb764225698ebdfeaf09a2afba6c25e17733940ab52cefd5c751cc2720b29b5ee56ef32ee
+ languageName: node
+ linkType: hard
+
"rdf-dereference@npm:^2.2.0":
version: 2.2.0
resolution: "rdf-dereference@npm:2.2.0"
@@ -10232,7 +10338,7 @@ __metadata:
languageName: node
linkType: hard
-"rdf-isomorphic@npm:^1.3.0":
+"rdf-isomorphic@npm:^1.3.0, rdf-isomorphic@npm:^1.3.1":
version: 1.3.1
resolution: "rdf-isomorphic@npm:1.3.1"
dependencies:
@@ -10253,6 +10359,19 @@ __metadata:
languageName: node
linkType: hard
+"rdf-lens@npm:^1.3.5":
+ version: 1.3.5
+ resolution: "rdf-lens@npm:1.3.5"
+ dependencies:
+ "@rdfjs/types": "npm:^2.0.1"
+ "@treecg/types": "npm:^0.4.6"
+ "@types/n3": "npm:^1.21.1"
+ n3: "npm:^1.23.1"
+ rdf-data-factory: "npm:^2.0.2"
+ checksum: 10c0/28522512d32569e913cefc37ee125f47942943a67e2daf407195162ba000b0b46317599ed78ec3432cbdf67118ba0a1b70126a116a8d023d2f99fac7f935b822
+ languageName: node
+ linkType: hard
+
"rdf-literal@npm:^1.2.0, rdf-literal@npm:^1.3.0":
version: 1.3.1
resolution: "rdf-literal@npm:1.3.1"
@@ -10308,6 +10427,38 @@ __metadata:
languageName: node
linkType: hard
+"rdf-parse@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "rdf-parse@npm:3.0.0"
+ dependencies:
+ "@comunica/actor-http-fetch": "npm:^2.0.1"
+ "@comunica/actor-http-proxy": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-html": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-html-microdata": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-html-rdfa": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-html-script": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-jsonld": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-n3": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-rdfxml": "npm:^2.0.1"
+ "@comunica/actor-rdf-parse-shaclc": "npm:^2.6.2"
+ "@comunica/actor-rdf-parse-xml-rdfa": "npm:^2.0.1"
+ "@comunica/bus-http": "npm:^2.0.1"
+ "@comunica/bus-init": "npm:^2.0.1"
+ "@comunica/bus-rdf-parse": "npm:^2.0.1"
+ "@comunica/bus-rdf-parse-html": "npm:^2.0.1"
+ "@comunica/config-query-sparql": "npm:^2.0.1"
+ "@comunica/core": "npm:^2.0.1"
+ "@comunica/mediator-combine-pipeline": "npm:^2.0.1"
+ "@comunica/mediator-combine-union": "npm:^2.0.1"
+ "@comunica/mediator-number": "npm:^2.0.1"
+ "@comunica/mediator-race": "npm:^2.0.1"
+ "@rdfjs/types": "npm:*"
+ readable-stream: "npm:^4.3.0"
+ stream-to-string: "npm:^1.2.0"
+ checksum: 10c0/c85c1caed0772e69a3862ca6875c986ad44ab3d46744c6781e46395333f751b4f505ba490a983771d2eeddf791cae5fefb404fd1f3ff414a493962c009016335
+ languageName: node
+ linkType: hard
+
"rdf-quad@npm:^1.5.0":
version: 1.5.0
resolution: "rdf-quad@npm:1.5.0"
@@ -10434,6 +10585,15 @@ __metadata:
languageName: node
linkType: hard
+"rdf-vocabulary@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "rdf-vocabulary@npm:1.0.1"
+ dependencies:
+ "@rdfjs/types": "npm:*"
+ checksum: 10c0/5f9f9a7c2dea9084b57a26b03989b24b00c2d606eefe18f8a12adf5425da11329f01e132ed3ce0363a399a854cacad38226865426ecab92dd0719846c6eea34e
+ languageName: node
+ linkType: hard
+
"rdfa-streaming-parser@npm:^2.0.1":
version: 2.0.1
resolution: "rdfa-streaming-parser@npm:2.0.1"
@@ -10718,7 +10878,7 @@ __metadata:
languageName: node
linkType: hard
-"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
+"rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
dependencies:
@@ -11365,6 +11525,17 @@ __metadata:
languageName: node
linkType: hard
+"swipl-wasm@npm:4.0.13":
+ version: 4.0.13
+ resolution: "swipl-wasm@npm:4.0.13"
+ dependencies:
+ "@types/emscripten": "npm:^1.39.13"
+ bin:
+ swipl-generate: dist/bin/index.js
+ checksum: 10c0/ea61942ceb60883bddd213d029ce21bb7010860621b69dfd1e13209b17948c90bc3c422153244a8662f5eb89275135b3905be5274d827ce4f2effac6a383f038
+ languageName: node
+ linkType: hard
+
"syncpack@npm:^13.0.2":
version: 13.0.2
resolution: "syncpack@npm:13.0.2"
@@ -11478,12 +11649,10 @@ __metadata:
languageName: node
linkType: hard
-"tmp@npm:^0.2.1":
- version: 0.2.1
- resolution: "tmp@npm:0.2.1"
- dependencies:
- rimraf: "npm:^3.0.0"
- checksum: 10c0/67607aa012059c9ce697bee820ee51bc0f39b29a8766def4f92d3f764d67c7cf9205d537d24e0cb1ce9685c40d4c628ead010910118ea18348666b5c46ed9123
+"tmp@npm:^0.2.1, tmp@npm:^0.2.3":
+ version: 0.2.3
+ resolution: "tmp@npm:0.2.3"
+ checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125
languageName: node
linkType: hard
@@ -11924,6 +12093,15 @@ __metadata:
languageName: node
linkType: hard
+"uuid@npm:8.3.2":
+ version: 8.3.2
+ resolution: "uuid@npm:8.3.2"
+ bin:
+ uuid: dist/bin/uuid
+ checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54
+ languageName: node
+ linkType: hard
+
"uuid@npm:^9.0.0, uuid@npm:^9.0.1":
version: 9.0.1
resolution: "uuid@npm:9.0.1"
@@ -11933,6 +12111,16 @@ __metadata:
languageName: node
linkType: hard
+"uuidv4@npm:^6.2.13":
+ version: 6.2.13
+ resolution: "uuidv4@npm:6.2.13"
+ dependencies:
+ "@types/uuid": "npm:8.3.4"
+ uuid: "npm:8.3.2"
+ checksum: 10c0/33287c7c71e19c5a9fe0d936c0df648338965442c80b5b26e51c13ec9a9c524a72c718355e0cea8a9431a78680d72a41a236b87046ecc0a05f518c8e22df9e35
+ languageName: node
+ linkType: hard
+
"v8-compile-cache-lib@npm:^3.0.1":
version: 3.0.1
resolution: "v8-compile-cache-lib@npm:3.0.1"