Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,5 @@ tmp

# Misc
.DS_Store
.vscode/
.vscode/
.idea
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20
22
20 changes: 10 additions & 10 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -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
# "@types/node": ^20
41 changes: 20 additions & 21 deletions demo/flow-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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: <http://purl.org/dc/terms/>.
@prefix eu-gdpr: <https://w3id.org/dpv/legal/eu/gdpr#>.
@prefix oac: <https://w3id.org/oac#>.
Expand All @@ -94,29 +94,28 @@ const healthcare_patient_policy =

@prefix ex: <http://example.org/>.

<http://example.org/HCPX-request> a odrl:Request ;
odrl:uid ex:HCPX-request ;
<http://example.org/HCPX-agreement> 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 <http://example.org/HCPX-request-permission> .
odrl:permission <http://example.org/HCPX-agreement-permission> .

<http://example.org/HCPX-request-permission> a odrl:Permission ;
<http://example.org/HCPX-agreement-permission> a odrl:Permission ;
odrl:action odrl:read ;
odrl:target <http://example.org/medical-data-access-collection> ;
odrl:assigner <${terms.agents.ruben}> ;
odrl:assignee <${terms.agents.alice}> ;
odrl:constraint <http://example.org/HCPX-request-permission-purpose>,
<http://example.org/HCPX-request-permission-lb> .
odrl:constraint <http://example.org/HCPX-agreement-permission-purpose>,
<http://example.org/HCPX-agreement-permission-lb> .

<http://example.org/medical-data-access-collection> a odrl:AssetCollection;
odrl:source <${terms.resources.collectionSource}> .

<http://example.org/HCPX-request-permission-purpose> a odrl:Constraint ;
<http://example.org/HCPX-agreement-permission-purpose> 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 .

<http://example.org/HCPX-request-permission-lb> a odrl:Constraint ;
<http://example.org/HCPX-agreement-permission-lb> a odrl:Constraint ;
odrl:leftOperand oac:LegalBasis ;
odrl:operator odrl:eq ;
odrl:rightOperand eu-gdpr:A9-2-a .`
Expand Down Expand Up @@ -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
Expand All @@ -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, {
Expand All @@ -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();
Expand Down Expand Up @@ -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}`)
Expand All @@ -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();
Expand Down
48 changes: 23 additions & 25 deletions demo/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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: <http://purl.org/dc/terms/>
PREFIX eu-gdpr: <https://w3id.org/dpv/legal/eu/gdpr#>
PREFIX oac: <https://w3id.org/oac#>
Expand All @@ -83,26 +83,25 @@ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

PREFIX ex: <http://example.org/>

<http://example.org/HCPX-request> a odrl:Request ;
odrl:uid ex:HCPX-request ;
<http://example.org/HCPX-agreement> 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 <http://example.org/HCPX-request-permission> .
odrl:permission <http://example.org/HCPX-agreement-permission> .

<http://example.org/HCPX-request-permission> a odrl:Permission ;
<http://example.org/HCPX-agreement-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 <http://example.org/HCPX-request-permission-purpose>,
<http://example.org/HCPX-request-permission-lb> .
odrl:constraint <http://example.org/HCPX-agreement-permission-purpose>,
<http://example.org/HCPX-agreement-permission-lb> .

<http://example.org/HCPX-request-permission-purpose> a odrl:Constraint ;
<http://example.org/HCPX-agreement-permission-purpose> 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 .

<http://example.org/HCPX-request-permission-lb> a odrl:Constraint ;
<http://example.org/HCPX-agreement-permission-lb> a odrl:Constraint ;
odrl:leftOperand oac:LegalBasis ;
odrl:operator odrl:eq ;
odrl:rightOperand eu-gdpr:A9-2-a .`
Expand All @@ -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:
Expand Down Expand Up @@ -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}.`)
Expand Down Expand Up @@ -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
Expand All @@ -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.`))
Expand All @@ -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, {
Expand All @@ -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();
Expand Down Expand Up @@ -301,4 +300,3 @@ async function initContainer(policyContainer: string): Promise<void> {
}
}
}

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions packages/css/config/uma/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 1 addition & 8 deletions packages/css/config/uma/overrides/authorization-handler.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
}
}
Expand Down
Loading