Skip to content
Open
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
11 changes: 0 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,6 @@ jobs:

steps:

- name: Install prolog
uses: logtalk-actions/setup-swi-prolog@master

- name: Clone EYE repo
uses: actions/checkout@v4
with:
repository: eyereasoner/eye

- name: Build EYE
run: bash install.sh --prefix=$HOME/.local # This folder is available on $PATH already

- name: Checkout main branch
uses: actions/checkout@v4

Expand Down
7 changes: 0 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
FROM node:22
ENV NODE_ENV=production

# 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

WORKDIR /usr/src/app
COPY . .

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ This repository contains SolidLab research artefacts on use of UMA in the Solid

In order to run this project you need to perform the following steps.

1. Install the [eye reasoner](https://github.com/eyereasoner/eye/) and have `eye` available on your path.
2. Ensure that you are using Node.js 20 or higher, e.g. by running `nvm use`. (see [.nvmrc](./.nvmrc))
1. Ensure that you are using Node.js 20 or higher, e.g. by running `nvm use`. (see [.nvmrc](./.nvmrc))
2. Enable Node.js Corepack with `corepack enable`.
3. Run `yarn install` in the project root (this will automatically call `yarn build`).
4. Run `yarn start`.
Expand Down
1 change: 0 additions & 1 deletion packages/uma/bin/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const launch = async () => {

// variables['urn:uma:variables:policyDir'] = path.join(rootDir, './config/rules/policy');
variables['urn:uma:variables:policyContainer'] = 'http://localhost:3000/settings/policies/';
variables['urn:uma:variables:eyePath'] = 'eye';
variables['urn:uma:variables:backupFilePath'] = '';

const configPath = path.join(rootDir, './config/demo.json');
Expand Down
1 change: 0 additions & 1 deletion packages/uma/bin/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const launch = async () => {

variables['urn:uma:variables:port'] = argv.port;
variables['urn:uma:variables:baseUrl'] = argv.baseUrl ?? `http://localhost:${argv.port}/uma`;
variables['urn:uma:variables:eyePath'] = 'eye';
variables['urn:uma:variables:backupFilePath'] = argv.backupFilePath;

const configPath = path.join(rootDir, './config/default.json');
Expand Down
1 change: 0 additions & 1 deletion packages/uma/bin/odrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const launch = async () => {

variables['urn:uma:variables:policyBaseIRI'] = 'http://localhost:3000/';
variables['urn:uma:variables:policyDir'] = path.join(rootDir, './config/rules/odrl');
variables['urn:uma:variables:eyePath'] = 'eye';
variables['urn:uma:variables:backupFilePath'] = '';

const configPath = path.join(rootDir, './config/odrl.json');
Expand Down
1 change: 0 additions & 1 deletion packages/uma/config/odrl.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"overrideInstance": { "@id": "urn:uma:default:Authorizer" },
"overrideParameters": {
"@type": "OdrlAuthorizer",
"eyePath": { "@id": "urn:uma:variables:eyePath" },
"policies": {
"@id": "urn:uma:default:RulesStorage"
}
Expand Down
1 change: 0 additions & 1 deletion packages/uma/config/policies/authorizers/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"fallback": {
"@id": "urn:uma:default:OdrlAuthorizer",
"@type": "OdrlAuthorizer",
"eyePath": { "@id": "urn:uma:variables:eyePath" },
"policies": {
"@id": "urn:uma:default:RulesStorage",
"@type": "FileBackupUCRulesStorage",
Expand Down
5 changes: 0 additions & 5 deletions packages/uma/config/variables/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"@id": "urn:uma:variables:policyBaseIRI",
"@type": "Variable"
},
{
"comment": "Path of the local eye reasoner.",
"@id": "urn:uma:variables:eyePath",
"@type": "Variable"
},
{
"comment": "URL of container where policies are stored.",
"@id": "urn:uma:variables:policyContainer",
Expand Down
3 changes: 2 additions & 1 deletion packages/uma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"logform": "^2.6.0",
"ms": "^2.1.3",
"n3": "^1.17.2",
"odrl-evaluator": "^0.5.0",
"odrl-evaluator": "^0.6.0",
"policy-conflict-resolver": "^0.0.3",
"rdf-string": "^2.0.1",
"rdf-vocabulary": "^1.0.1",
"uri-template-lite": "^23.4.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/uma/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export * from './util/http/validate/RequestValidator';

// UCP
export * from './ucp/policy/ODRL';
export * from './ucp/policy/Strategy'
export * from './ucp/policy/PrioritizeProhibitionStrategy'
export * from './ucp/policy/UsageControlPolicy';
export * from './ucp/storage/ContainerUCRulesStorage';
export * from './ucp/storage/DirectoryUCRulesStorage';
Expand Down
213 changes: 34 additions & 179 deletions packages/uma/src/policies/authorizers/OdrlAuthorizer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DC, RDF } from '@solid/community-server';
import { BadRequestHttpError, DC, RDF } from '@solid/community-server';
import { getLoggerFor } from 'global-logger-factory';
import { DataFactory, Literal, NamedNode, Quad, Quad_Subject, Store, Writer } from 'n3';
import { EyeReasoner, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator'
import { createVocabulary } from 'rdf-vocabulary';
import { DataFactory, Quad, Store, Writer } from 'n3';
import { EyelingReasoner, EyeReasoner, ODRLEngineMultipleSteps, ODRLEvaluator } from 'odrl-evaluator';
import { CLIENTID, WEBID } from '../../credentials/Claims';
import { ClaimSet } from '../../credentials/ClaimSet';
import { basicPolicy } from '../../ucp/policy/ODRL';
import { PrioritizeProhibitionStrategy } from '../../ucp/policy/PrioritizeProhibitionStrategy';
import { Strategy } from '../../ucp/policy/Strategy';
import { UCPPolicy } from '../../ucp/policy/UsageControlPolicy';
import { UCRulesStorage } from '../../ucp/storage/UCRulesStorage';
import { ODRL } from '../../ucp/util/Vocabularies';
Expand Down Expand Up @@ -33,26 +34,23 @@ const { quad, namedNode, literal, blankNode } = DataFactory
export class OdrlAuthorizer implements Authorizer {
protected readonly logger = getLoggerFor(this);
private readonly odrlEvaluator: ODRLEvaluator;
private readonly strategy: Strategy;

/**
* 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);
this.odrlEvaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps({ reasoner: new EyelingReasoner()}));
this.strategy = new PrioritizeProhibitionStrategy();
}

public async permissions(claims: ClaimSet, query?: Permission[]): Promise<Permission[]> {
this.logger.info(`Calculating permissions. ${JSON.stringify({claims, query})}`);
this.logger.info(`Calculating permissions. ${JSON.stringify({ claims, query })}`);
if (!query) {
this.logger.warn('The OdrlAuthorizer can only calculate permissions for explicit queries.')
return [];
Expand All @@ -67,9 +65,9 @@ export class OdrlAuthorizer implements Authorizer {
// 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"))),
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';
Expand All @@ -91,7 +89,7 @@ export class OdrlAuthorizer implements Authorizer {
// });
}

for (const {resource_id, resource_scopes} of query) {
for (const { resource_id, resource_scopes } of query) {
grantedPermissions[resource_id] = [];
for (const scope of resource_scopes) {
// TODO: why is this transformation happening (here)?
Expand All @@ -114,9 +112,9 @@ export class OdrlAuthorizer implements Authorizer {
// Adding context triples for the client identifier, if there is one
if (clientQuads.length > 0) {
requestStore.addQuad(quad(
namedNode(request.ruleIRIs[0]),
namedNode('https://w3id.org/force/sotw#context'),
clientSubject,
namedNode(request.ruleIRIs[0]),
namedNode('https://w3id.org/force/sotw#context'),
clientSubject,
));
requestStore.addQuads(clientQuads);
}
Expand All @@ -126,19 +124,19 @@ export class OdrlAuthorizer implements Authorizer {
[...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(scope);
}
// handle potential conflicts with a strategy
const allowed = await this.strategy.handleSafe({
request: {
request: [...requestStore],
identifier: namedNode(request.policyIRI)
},
policies: [...policyStore],
reports: reports
})

if (allowed) {
grantedPermissions[resource_id].push(scope);
}
}
}
Expand All @@ -147,156 +145,13 @@ export class OdrlAuthorizer implements Authorizer {
resource_id => permissions.push({
resource_id,
resource_scopes: grantedPermissions[resource_id],
}) );
}));
return permissions;
}
}
const scopeCssToOdrl: Map<string, string> = 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<string, string> = new Map(Array.from(scopeCssToOdrl, entry => [entry[1], entry[0]]));

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',
)
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');
Loading