Skip to content

Commit

Permalink
WIP: use RDFJS API
Browse files Browse the repository at this point in the history
  • Loading branch information
jeswr committed Nov 10, 2023
1 parent 557c846 commit 9d5bc4c
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 138 deletions.
88 changes: 71 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@
"@inrupt/solid-client-authn-browser": "^1.12.2",
"@inrupt/solid-client-authn-node": "^1.12.1",
"@playwright/test": "^1.34.3",
"@rdfjs/types": "^1.1.0",
"@rushstack/eslint-patch": "^1.1.4",
"@types/auth-header": "^1.0.2",
"@types/dotenv-flow": "^3.1.0",
"@types/jest": "^29.2.2",
"@types/n3": "^1.16.4",
"@types/uuid": "^9.0.4",
"dotenv-flow": "^3.2.0",
"eslint": "^8.18.0",
Expand All @@ -132,11 +134,12 @@
},
"dependencies": {
"@inrupt/solid-client": "^1.25.1",
"@inrupt/solid-client-vc": "^0.7.0",
"@inrupt/solid-client-vc": "file:../solid-client-vc-js",
"@inrupt/universal-fetch": "^1.0.1",
"@types/rdfjs__dataset": "^1.0.5",
"auth-header": "^1.0.0",
"base64url": "^3.0.1",
"n3": "^1.17.2",
"rdf-namespaces": "^1.9.2"
},
"engines": {
Expand Down
62 changes: 62 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Term, Quad, NamedNode, BlankNode, Literal } from "@rdfjs/types";

Check warning on line 1 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

missing header

Check failure on line 1 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Unable to resolve path to module '@rdfjs/types'

Check warning on line 1 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

missing header

Check failure on line 1 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Unable to resolve path to module '@rdfjs/types'
import { DataFactory } from "n3";
import { rdf } from "rdf-namespaces";

Check failure on line 3 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Expected 1 empty line after import statement not followed by another import

Check failure on line 3 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Expected 1 empty line after import statement not followed by another import
const { namedNode } = DataFactory;

export const cred = "https://www.w3.org/2018/credentials#";
export const gc = "https://w3id.org/GConsent#";
export const acl = "http://www.w3.org/ns/auth/acl#";
export const odrl2 = "http://www.w3.org/ns/odrl/2/";
export const solidOdrl = "https://www.w3.org/ns/solid/odrl/";
export const vc = "http://www.w3.org/ns/solid/vc#";
export const xsd = "http://www.w3.org/2001/XMLSchema#";

export const XSD_BOOLEAN = namedNode(`${xsd}boolean`);

export const SOLID_ACCESS_GRANT = namedNode(`${vc}SolidAccessGrant`);
export const CREDENTIAL_SUBJECT = namedNode(`${cred}credentialSubject`);
export const TYPE = namedNode(rdf.type);
export const PROVIDED_CONSENT = namedNode(`${gc}providedConsent`);
export const HAS_CONSENT = namedNode(`${gc}hasConsent`);
export const IS_PROVIDED_TO = namedNode(`${gc}isProvidedTo`);
export const PERMISSION = namedNode(`${odrl2}permission`);
// Check this
export const CONSTRAINT = namedNode(`${odrl2}constraint`);
export const ACTION = namedNode(`${odrl2}action`);
export const LEFT_OPERAND = namedNode(`${odrl2}leftOperand`);
export const OPERATOR = namedNode(`${odrl2}operator`);
export const RIGHT_OPERAND = namedNode(`${odrl2}rightOperand`);
export const AGREEMENT = namedNode(`${odrl2}Agreement`);
export const PROHIBITION = namedNode(`${odrl2}prohibition`);
export const PROFILE = namedNode(`${odrl2}profile`);
export const ASSIGNER = namedNode(`${odrl2}assigner`);
export const ASSIGNEE = namedNode(`${odrl2}assignee`);
export const TARGET = namedNode(`${odrl2}target`);
export const ACCESS = namedNode(`${solidOdrl}access`);
export const MODE = namedNode(`${acl}mode`);
export const READ = namedNode(`${acl}Read`);
export const WRITE = namedNode(`${acl}Write`);
export const APPEND = namedNode(`${acl}Append`);
export const ISSUANCE_DATE = namedNode(`${cred}issuanceDate`);
export const EXPIRATION_DATE = namedNode(`${cred}expirationDate`);
export const ISSUER = namedNode(`${cred}issuer`);
export const INHERIT = namedNode("urn:uuid:71ab2f68-a68b-4452-b968-dd23e0570227");

Check failure on line 43 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `"urn:uuid:71ab2f68-a68b-4452-b968-dd23e0570227"` with `⏎··"urn:uuid:71ab2f68-a68b-4452-b968-dd23e0570227",⏎`

Check failure on line 43 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `"urn:uuid:71ab2f68-a68b-4452-b968-dd23e0570227"` with `⏎··"urn:uuid:71ab2f68-a68b-4452-b968-dd23e0570227",⏎`

export function assertTermType(term: Term): NamedNode

Check failure on line 45 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Insert `;`

Check failure on line 45 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Insert `;`
export function assertTermType(term: Term, type: 'NamedNode'): NamedNode

Check failure on line 46 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'NamedNode'):·NamedNode` with `"NamedNode"):·NamedNode;`

Check failure on line 46 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'NamedNode'):·NamedNode` with `"NamedNode"):·NamedNode;`
export function assertTermType(term: Term, type: 'BlankNode'): BlankNode

Check failure on line 47 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'BlankNode'):·BlankNode` with `"BlankNode"):·BlankNode;`

Check failure on line 47 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'BlankNode'):·BlankNode` with `"BlankNode"):·BlankNode;`
export function assertTermType(term: Term, type: 'Literal'): Literal

Check failure on line 48 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'Literal'):·Literal` with `"Literal"):·Literal;`

Check failure on line 48 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'Literal'):·Literal` with `"Literal"):·Literal;`
export function assertTermType(term: Term, type?: Term['termType']): Term

Check failure on line 49 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'termType']):·Term` with `"termType"]):·Term;`

Check failure on line 49 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `'termType']):·Term` with `"termType"]):·Term;`
export function assertTermType(term: Term, type: Term['termType'] = 'NamedNode'): Term {

Check failure on line 50 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `term:·Term,·type:·Term['termType']·=·'NamedNode'` with `⏎··term:·Term,⏎··type:·Term["termType"]·=·"NamedNode",⏎`

Check failure on line 50 in src/common/constants.ts

View workflow job for this annotation

GitHub Actions / lint / lint

Replace `term:·Term,·type:·Term['termType']·=·'NamedNode'` with `⏎··term:·Term,⏎··type:·Term["termType"]·=·"NamedNode",⏎`
if (term.termType !== type) {
throw new Error(`Expected [${term.value}] to be a ${type}. Found ${term.termType}`);
}
return term;
}
export function getSingleQuad(quads: Quad[]): Quad {
if (quads.length !== 1) {
throw new Error(`Expected exactly one result. Found ${quads.length}`);
}
return quads[0];
}

79 changes: 56 additions & 23 deletions src/common/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ import type { AccessModes } from "../type/AccessModes";
import { resourceAccessToAccessMode } from "../type/AccessModes";
import type { AccessGrantOdrl } from "../odrl";
import { isCredentialAccessGrantOdrl } from "../odrl";
import { DataFactory } from "n3";
import { NamedNode, DatasetCore, Quad_Subject, Quad_Predicate, Quad_Object, Term, BlankNode, Literal } from "@rdfjs/types";
import { CREDENTIAL_SUBJECT, READ, WRITE, APPEND, PERMISSION, ACTION, PROVIDED_CONSENT, MODE, HAS_CONSENT, ISSUANCE_DATE, EXPIRATION_DATE, ISSUER, INHERIT, XSD_BOOLEAN, ASSIGNEE, IS_PROVIDED_TO } from "./constants";
import { assertTermType, getSingleQuad } from "./constants";
import { GC_IS_PROVIDED_TO } from "../gConsent/constants";
const { namedNode, defaultGraph, quad, literal } = DataFactory;

/**
* Get the resources to which an Access Grant/Request applies.
Expand Down Expand Up @@ -94,13 +100,22 @@ export function getResourceOwner(
export function getRequestor(
vc: AccessGrantGConsent | AccessRequestGConsent | AccessGrantOdrl,
): string {
const credentialSubject = getSingleObject(vc, namedNode(vc.id), CREDENTIAL_SUBJECT);

Check warning on line 103 in src/common/getters.ts

View workflow job for this annotation

GitHub Actions / lint / lint

'getSingleObject' was used before it was defined

Check warning on line 103 in src/common/getters.ts

View workflow job for this annotation

GitHub Actions / lint / lint

'getSingleObject' was used before it was defined
if (isCredentialAccessGrantOdrl(vc)) {
return vc.credentialSubject.assignee;
return getSingleObject(vc, credentialSubject, ASSIGNEE).value;
}
if (isGConsentAccessGrant(vc)) {
return vc.credentialSubject.providedConsent.isProvidedTo;
const providedConsent = getSingleObject(vc, credentialSubject, PROVIDED_CONSENT);
return getSingleObject(vc, providedConsent, IS_PROVIDED_TO).value;
}
return vc.credentialSubject.id;
return credentialSubject.value;
}

function getSingleObject<T extends Term['termType']>(vc: DatasetCore, subject: Term, predicate: Term): NamedNode;
function getSingleObject<T extends Term['termType']>(vc: DatasetCore, subject: Term, predicate: Term, type: 'BlankNode'): BlankNode;
function getSingleObject<T extends Term['termType']>(vc: DatasetCore, subject: Term, predicate: Term, type: 'Literal'): Literal;
function getSingleObject<T extends Term['termType']>(vc: DatasetCore, subject: Term, predicate: Term, type?: T) {
return assertTermType(getSingleQuad([...vc.match(subject, predicate, null, defaultGraph())]).object, type);
}

/**
Expand All @@ -118,18 +133,23 @@ export function getRequestor(
export function getAccessModes(
vc: AccessGrantGConsent | AccessRequestGConsent | AccessGrantOdrl,
): AccessModes {
const credentialSubject = getSingleObject(vc, namedNode(vc.id), CREDENTIAL_SUBJECT);

if (isCredentialAccessGrantOdrl(vc)) {
const modes = vc.credentialSubject.permission
.map((permission) => permission.action)
.flat();
return resourceAccessToAccessMode(modes);
const permissions = [...vc.match(credentialSubject, PERMISSION, null, defaultGraph())];
return {
read: permissions.some((permission) => vc.has(quad(permission.object as Quad_Subject, ACTION, READ, defaultGraph()))),
write: permissions.some((permission) => vc.has(quad(permission.object as Quad_Subject, ACTION, WRITE, defaultGraph()))),
append: permissions.some((permission) => vc.has(quad(permission.object as Quad_Subject, ACTION, APPEND, defaultGraph()))),
};
}
if (isGConsentAccessGrant(vc)) {
return resourceAccessToAccessMode(
vc.credentialSubject.providedConsent.mode,
);
}
return resourceAccessToAccessMode(vc.credentialSubject.hasConsent.mode);

const consent = getSingleObject(vc, credentialSubject, isGConsentAccessGrant(vc) ? PROVIDED_CONSENT : HAS_CONSENT);
return {
read: vc.has(quad(consent, MODE, READ, defaultGraph())),
write: vc.has(quad(consent, MODE, WRITE, defaultGraph())),
append: vc.has(quad(consent, MODE, APPEND, defaultGraph())),
};
}

/**
Expand Down Expand Up @@ -168,6 +188,13 @@ export function getTypes(
return vc.type;
}

function wrapDate(date: Literal) {
if (!date.datatype.equals(literal('http://www.w3.org/2001/XMLSchema#dateTime'))) {
throw new Error(`Expected date to be a dateTime; recieved [${date.datatype.value}]`);
}
return new Date(date.value);
}

/**
* Get the issuance date of an Access Grant/Request.
*
Expand All @@ -183,7 +210,7 @@ export function getTypes(
export function getIssuanceDate(
vc: AccessGrantGConsent | AccessRequestGConsent | AccessGrantOdrl,
): Date {
return new Date(vc.issuanceDate);
return wrapDate(getSingleObject(vc, namedNode(vc.id), ISSUANCE_DATE, 'Literal'));
}

/**
Expand All @@ -201,9 +228,17 @@ export function getIssuanceDate(
export function getExpirationDate(
vc: AccessGrantGConsent | AccessRequestGConsent | AccessGrantOdrl,
): Date | undefined {
return typeof vc.expirationDate === "string"
? new Date(vc.expirationDate)
: undefined;
const expirationDate = [...vc.match(namedNode(vc.id), EXPIRATION_DATE, null, defaultGraph())];

if (expirationDate.length > 1) {
throw new Error(`Expected at most one expiration date. Found ${expirationDate.length}`);
}

if (expirationDate.length === 1) {
return wrapDate(assertTermType(expirationDate[0].object, 'Literal'));
}

return undefined;
}

/**
Expand All @@ -221,7 +256,7 @@ export function getExpirationDate(
export function getIssuer(
vc: AccessGrantGConsent | AccessRequestGConsent | AccessGrantOdrl,
): string {
return vc.issuer;
return getSingleObject(vc, namedNode(vc.id), ISSUER).value;
}

/**
Expand All @@ -242,11 +277,9 @@ export function getInherit(
if (isCredentialAccessGrantOdrl(vc)) {
return true;
}
if (isGConsentAccessGrant(vc)) {
// Inherit defaults to true.
return vc.credentialSubject.providedConsent.inherit ?? true;
}
return vc.credentialSubject.hasConsent.inherit ?? true;
const credentialSubject = getSingleObject(vc, namedNode(vc.id), CREDENTIAL_SUBJECT);
const consent = getSingleObject(vc, credentialSubject, isGConsentAccessGrant(vc) ? PROVIDED_CONSENT : HAS_CONSENT);
return !vc.has(quad(consent, INHERIT, literal('false', XSD_BOOLEAN), defaultGraph()));
}

/**
Expand Down
Loading

0 comments on commit 9d5bc4c

Please sign in to comment.