diff --git a/package-lock.json b/package-lock.json index 3f4b36973..5c8b706f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@inrupt/solid-client": "^1.25.1", - "@inrupt/solid-client-vc": "file:../solid-client-vc-js", + "@inrupt/solid-client-vc": "^0.8.0-beta.1", "@inrupt/universal-fetch": "^1.0.1", "@types/rdfjs__dataset": "^1.0.5", "auth-header": "^1.0.0", @@ -57,59 +57,6 @@ "fsevents": "^2.3.2" } }, - "../solid-client-vc-js": { - "name": "@inrupt/solid-client-vc", - "version": "0.7.2", - "license": "MIT", - "dependencies": { - "@inrupt/solid-client": "^1.25.2", - "@inrupt/universal-fetch": "^1.0.1", - "content-type": "^1.0.5", - "event-emitter-promisify": "^1.1.0", - "jsonld-context-parser": "^2.4.0", - "jsonld-streaming-parser": "^3.3.0", - "jsonld-streaming-serializer": "^2.1.0", - "md5": "^2.3.0", - "n3": "^1.17.0", - "rdf-namespaces": "^1.12.0" - }, - "devDependencies": { - "@inrupt/eslint-config-lib": "^2.0.0", - "@inrupt/internal-playwright-helpers": "^2.0.4", - "@inrupt/internal-test-env": "^2.0.4", - "@inrupt/jest-jsdom-polyfills": "^2.0.4", - "@playwright/test": "^1.28.1", - "@rdfjs/dataset": "2.0.1", - "@rdfjs/types": "^1.1.0", - "@rushstack/eslint-patch": "^1.1.4", - "@types/content-type": "^1.1.5", - "@types/dotenv-flow": "^3.1.1", - "@types/jest": "^29.2.2", - "@types/md5": "^2.3.4", - "@types/n3": "^1.16.0", - "@types/node": "^20.1.2", - "@types/rdfjs__dataset": "2.0.7", - "dotenv-flow": "^3.2.0", - "eslint": "^8.18.0", - "jest": "^29.3.0", - "jest-environment-jsdom": "^29.3.0", - "prettier": "^3.0.2", - "rdf-isomorphic": "^1.3.1", - "rollup": "^3.1.0", - "rollup-plugin-typescript2": "^0.35.0", - "ts-jest": "^29.0.3", - "ts-node": "^10.9.1", - "typedoc": "^0.25.1", - "typedoc-plugin-markdown": "^3.15.1", - "typescript": "^5.0.4" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || ^20.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -1110,8 +1057,27 @@ } }, "node_modules/@inrupt/solid-client-vc": { - "resolved": "../solid-client-vc-js", - "link": true + "version": "0.8.0-beta.1", + "resolved": "https://registry.npmjs.org/@inrupt/solid-client-vc/-/solid-client-vc-0.8.0-beta.1.tgz", + "integrity": "sha512-LZh8HhHNmdQfdEWx8Z4PgBFbbTe+5rzJekQYHYEwkaV6MIPw7vHszfej3rHRK8D0wRVSnt8lg5cTw9EgxrFckQ==", + "dependencies": { + "@inrupt/solid-client": "^1.25.2", + "@inrupt/universal-fetch": "^1.0.1", + "content-type": "^1.0.5", + "event-emitter-promisify": "^1.1.0", + "jsonld-context-parser": "^2.4.0", + "jsonld-streaming-parser": "^3.3.0", + "jsonld-streaming-serializer": "^2.1.0", + "md5": "^2.3.0", + "n3": "^1.17.0", + "rdf-namespaces": "^1.12.0" + }, + "engines": { + "node": "^16.0.0 || ^18.0.0 || ^20.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } }, "node_modules/@inrupt/universal-fetch": { "version": "1.0.3", @@ -3337,6 +3303,14 @@ "node": ">=10" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/chromium-bidi": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", @@ -3464,7 +3438,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -3568,6 +3541,14 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", @@ -4979,8 +4960,7 @@ "node_modules/event-emitter-promisify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/event-emitter-promisify/-/event-emitter-promisify-1.1.0.tgz", - "integrity": "sha512-uyHG8gjwYGDlKoo0Txtx/u1HI1ubj0FK0rVqI4O0s1EymQm4iAEMbrS5B+XFlSaS8SZ3xzoKX+YHRZk8Nk/bXg==", - "dev": true + "integrity": "sha512-uyHG8gjwYGDlKoo0Txtx/u1HI1ubj0FK0rVqI4O0s1EymQm4iAEMbrS5B+XFlSaS8SZ3xzoKX+YHRZk8Nk/bXg==" }, "node_modules/event-target-shim": { "version": "5.0.1", @@ -6066,6 +6046,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7279,6 +7264,18 @@ "readable-stream": "^4.0.0" } }, + "node_modules/jsonld-streaming-serializer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jsonld-streaming-serializer/-/jsonld-streaming-serializer-2.1.0.tgz", + "integrity": "sha512-COHdLoeMTnrqHMoFhN3PoAwqnrKrpPC7/ACb0WbELYvt+HSOIFN3v4IJP7fOtLNQ4GeaeYkvbeWJ7Jo4EjxMDw==", + "dependencies": { + "@rdfjs/types": "*", + "@types/readable-stream": "^2.3.13", + "buffer": "^6.0.3", + "jsonld-context-parser": "^2.0.0", + "readable-stream": "^4.0.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -7552,6 +7549,16 @@ "node": ">= 12" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8872,9 +8879,9 @@ } }, "node_modules/rdf-namespaces": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/rdf-namespaces/-/rdf-namespaces-1.11.0.tgz", - "integrity": "sha512-zSUyICtC5utLjY0FVegUdagsbirnCNmqkA9/cjErIPhGKbGnYrrNNzoJjCTAehTEC3pkXeJwoeywKb6QKHaXcQ==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/rdf-namespaces/-/rdf-namespaces-1.12.0.tgz", + "integrity": "sha512-Fk48ltssXTmyXeoLqC0y85CEAhhWH+wvu7bkr9WxsKUyFDcKwWSHOK7CvRq3XRampy1qhSrOsIQ8U1gQDCh5MA==" }, "node_modules/react-is": { "version": "18.2.0", diff --git a/package.json b/package.json index 1eb4bf450..0bbf98045 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, "dependencies": { "@inrupt/solid-client": "^1.25.1", - "@inrupt/solid-client-vc": "file:../solid-client-vc-js", + "@inrupt/solid-client-vc": "^0.8.0-beta.1", "@inrupt/universal-fetch": "^1.0.1", "@types/rdfjs__dataset": "^1.0.5", "auth-header": "^1.0.0", diff --git a/src/common/getters.test.ts b/src/common/getters.test.ts index 33b5cd3a2..86097ede2 100644 --- a/src/common/getters.test.ts +++ b/src/common/getters.test.ts @@ -44,6 +44,7 @@ import { } from "./getters"; import type { AccessGrant, AccessRequest } from "../gConsent"; import { TYPE, gc } from "./constants"; + const { quad, namedNode, literal, blankNode } = DataFactory; describe("getters", () => { @@ -362,12 +363,12 @@ describe("getters", () => { store.addQuad( namedNode(getId(mockedGConsentGrant)), - namedNode('https://www.w3.org/2018/credentials#expirationDate'), + namedNode("https://www.w3.org/2018/credentials#expirationDate"), namedNode("http://example.org/this/is/a/date"), ); expect(() => getExpirationDate(store as any)).toThrow( - "Expected expiration date to be a Literal. Found [http://example.org/this/is/a/date] of type [NamedNode]." + "Expected expiration date to be a Literal. Found [http://example.org/this/is/a/date] of type [NamedNode].", ); }); @@ -376,7 +377,7 @@ describe("getters", () => { store.addQuad( namedNode(getId(mockedGConsentGrant)), - namedNode('https://www.w3.org/2018/credentials#expirationDate'), + namedNode("https://www.w3.org/2018/credentials#expirationDate"), literal("boo"), ); diff --git a/src/common/getters.ts b/src/common/getters.ts index 09e244a17..39912479a 100644 --- a/src/common/getters.ts +++ b/src/common/getters.ts @@ -31,7 +31,7 @@ import { getCredentialSubject, getIssuer, getIssuanceDate, - getExpirationDate + getExpirationDate, } from "@inrupt/solid-client-vc"; import type { AccessGrantGConsent } from "../gConsent/type/AccessGrant"; import type { AccessRequestGConsent } from "../gConsent/type/AccessRequest"; @@ -116,7 +116,13 @@ function getSingleObject( return object; } -export { getId, getIssuer, getIssuanceDate, getCredentialSubject, getExpirationDate }; +export { + getId, + getIssuer, + getIssuanceDate, + getCredentialSubject, + getExpirationDate, +}; /** * @internal diff --git a/src/common/verify/isValidAccessGrant.test.ts b/src/common/verify/isValidAccessGrant.test.ts index 0dd5b4fce..00a43cf0a 100644 --- a/src/common/verify/isValidAccessGrant.test.ts +++ b/src/common/verify/isValidAccessGrant.test.ts @@ -90,9 +90,13 @@ describe("isValidAccessGrant", () => { const { verifiableCredentialToDataset } = jest.requireActual< typeof VcLibrary >("@inrupt/solid-client-vc"); - MOCK_ACCESS_GRANT = await verifiableCredentialToDataset( - MOCK_ACCESS_GRANT_BASE, - ); + MOCK_ACCESS_GRANT = + await verifiableCredentialToDataset( + MOCK_ACCESS_GRANT_BASE, + { + includeVcProperties: true, + }, + ); }); it("uses the provided fetch if any", async () => { diff --git a/src/fetch/index.test.ts b/src/fetch/index.test.ts index 6dee340c3..44ac9a901 100644 --- a/src/fetch/index.test.ts +++ b/src/fetch/index.test.ts @@ -21,7 +21,10 @@ import { jest, describe, it, expect, beforeAll } from "@jest/globals"; import { fetch as crossFetch, Response } from "@inrupt/universal-fetch"; -import type { VerifiableCredential } from "@inrupt/solid-client-vc"; +import type { + VerifiableCredential, + VerifiableCredentialBase, +} from "@inrupt/solid-client-vc"; import { verifiableCredentialToDataset } from "@inrupt/solid-client-vc"; import base64url from "base64url"; @@ -138,9 +141,13 @@ describe("getUmaConfiguration", () => { describe("exchangeTicketForAccessToken", () => { let MOCK_VC: VerifiableCredential; beforeAll(async () => { - MOCK_VC = await verifiableCredentialToDataset(MOCK_VC_BASE, { - baseIRI: MOCK_VC_BASE.id, - }); + MOCK_VC = await verifiableCredentialToDataset( + MOCK_VC_BASE, + { + baseIRI: MOCK_VC_BASE.id, + includeVcProperties: true, + }, + ); }); it("posts to the token endpoint and parses the resulting access token", async () => { @@ -261,9 +268,13 @@ describe("boundFetch", () => { describe("fetchWithVc", () => { let MOCK_VC: VerifiableCredential; beforeAll(async () => { - MOCK_VC = await verifiableCredentialToDataset(MOCK_VC_BASE, { - baseIRI: MOCK_VC_BASE.id, - }); + MOCK_VC = await verifiableCredentialToDataset( + MOCK_VC_BASE, + { + baseIRI: MOCK_VC_BASE.id, + includeVcProperties: true, + }, + ); }); // These tests may be fairly brittle due to the nature of the mocked calls. diff --git a/src/gConsent/guard/isAccessGrant.ts b/src/gConsent/guard/isAccessGrant.ts index bed095d6e..f4be56775 100644 --- a/src/gConsent/guard/isAccessGrant.ts +++ b/src/gConsent/guard/isAccessGrant.ts @@ -32,6 +32,7 @@ export const GC_CONSENT_STATUS_EXPLICITLY_GIVEN = export const GC_CONSENT_STATUS_REQUESTED = "https://w3id.org/GConsent#ConsentStatusRequested"; +// Implemented as isGConsentAccessGrant in src/common/getters export function isAccessGrant( vc: T, ): vc is T & AccessGrantBody { diff --git a/src/gConsent/guard/isGConsentAttributes.ts b/src/gConsent/guard/isGConsentAttributes.ts index 8a586adba..a5227b9c9 100644 --- a/src/gConsent/guard/isGConsentAttributes.ts +++ b/src/gConsent/guard/isGConsentAttributes.ts @@ -20,12 +20,18 @@ // import type { UrlString } from "@inrupt/solid-client"; +import { DataFactory } from "n3"; +// eslint-disable-next-line camelcase +import type { DatasetCore, Quad_Subject } from "@rdfjs/types"; import { ACCESS_STATUS } from "../constants"; import type { GConsentRequestAttributes } from "../type/AccessVerifiableCredential"; import type { ResourceAccessMode } from "../../type/ResourceAccessMode"; import { RESOURCE_ACCESS_MODE } from "../../type/ResourceAccessMode"; import { isUnknownObject } from "./isUnknownObject"; import type { GConsentStatus } from "../type/GConsentStatus"; +import { acl, gc } from "../../common/constants"; + +const { defaultGraph } = DataFactory; function isResourceAccessModeArray(x: unknown): x is Array { return Array.isArray(x) && x.every((y) => RESOURCE_ACCESS_MODE.has(y)); @@ -50,3 +56,51 @@ export function isGConsentAttributes( isStringArray(x.forPersonalData) ); } + +export function isRdfjsGConsentAttributes( + dataset: DatasetCore, + // eslint-disable-next-line camelcase + consent: Quad_Subject, +) { + // isResourceAccessModeArray + for (const { object } of dataset.match( + consent, + acl.mode, + null, + defaultGraph(), + )) { + if ( + ![acl.Append, acl.Read, acl.Write].some((mode) => mode.equals(object)) + ) { + return false; + } + } + + // isGConsentStatus + const statuses = [ + ...dataset.match(consent, gc.hasStatus, null, defaultGraph()), + ]; + if ( + statuses.length !== 1 || + [ + gc.ConsentStatusDenied, + gc.ConsentStatusExplicitlyGiven, + gc.ConsentStatusRequested, + ].some((e) => e.equals(statuses[0].object)) + ) { + return false; + } + + for (const { object } of dataset.match( + consent, + gc.forPersonalData, + null, + defaultGraph(), + )) { + if (object.termType === "NamedNode") { + return false; + } + } + + return true; +} diff --git a/src/gConsent/manage/approveAccessRequest.test.ts b/src/gConsent/manage/approveAccessRequest.test.ts index d6c32aba6..dcba5f6b4 100644 --- a/src/gConsent/manage/approveAccessRequest.test.ts +++ b/src/gConsent/manage/approveAccessRequest.test.ts @@ -843,7 +843,7 @@ describe("approveAccessRequest", () => { inherit: "true", }, }, - }); + } as VcClient.VerifiableCredential); await expect( approveAccessRequest(accessRequestVc, undefined, { fetch: jest.fn(global.fetch), diff --git a/src/gConsent/manage/getAccessGrant.test.ts b/src/gConsent/manage/getAccessGrant.test.ts index 27b0d1ecc..a166f0135 100644 --- a/src/gConsent/manage/getAccessGrant.test.ts +++ b/src/gConsent/manage/getAccessGrant.test.ts @@ -56,10 +56,10 @@ describe("getAccessGrant", () => { }), ); - await getAccessGrant("https://some.vc.url", { + await getAccessGrant("https://some.credential", { fetch: mockedFetch, }); - expect(mockedFetch).toHaveBeenCalledWith("https://some.vc.url"); + expect(mockedFetch).toHaveBeenCalledWith("https://some.credential"); }); it("throws if resolving the IRI results in an HTTP error", () => { diff --git a/src/gConsent/manage/getAccessGrant.ts b/src/gConsent/manage/getAccessGrant.ts index 881022f3a..0bb351a21 100644 --- a/src/gConsent/manage/getAccessGrant.ts +++ b/src/gConsent/manage/getAccessGrant.ts @@ -62,6 +62,7 @@ export async function getAccessGrant( normalizeAccessGrant(JSON.parse(responseErrorClone)), { baseIRI: accessGrantVcUrl.toString(), + includeVcProperties: true, }, ); } catch (e) { diff --git a/src/gConsent/manage/getAccessGrantAll.test.ts b/src/gConsent/manage/getAccessGrantAll.test.ts index 1b570ba2d..21a391f28 100644 --- a/src/gConsent/manage/getAccessGrantAll.test.ts +++ b/src/gConsent/manage/getAccessGrantAll.test.ts @@ -35,9 +35,10 @@ import { mockAccessGrantVc } from "../util/access.mock"; const otherFetch = jest.fn(global.fetch); jest.mock("@inrupt/solid-client-vc", () => { - const { verifiableCredentialToDataset, getCredentialSubject } = jest.requireActual( - "@inrupt/solid-client-vc", - ) as jest.Mocked; + const { verifiableCredentialToDataset, getCredentialSubject } = + jest.requireActual("@inrupt/solid-client-vc") as jest.Mocked< + typeof VcLibrary + >; return { verifiableCredentialToDataset, getCredentialSubject, diff --git a/src/gConsent/manage/getAccessRequestFromRedirectUrl.test.ts b/src/gConsent/manage/getAccessRequestFromRedirectUrl.test.ts index d44bef2e7..f8d1309e4 100644 --- a/src/gConsent/manage/getAccessRequestFromRedirectUrl.test.ts +++ b/src/gConsent/manage/getAccessRequestFromRedirectUrl.test.ts @@ -19,7 +19,10 @@ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import type { getVerifiableCredential } from "@inrupt/solid-client-vc"; +import type { + VerifiableCredential, + getVerifiableCredential, +} from "@inrupt/solid-client-vc"; import { fetch } from "@inrupt/universal-fetch"; import { describe, expect, it, jest, beforeEach } from "@jest/globals"; import type { getSessionFetch } from "../../common/util/getSessionFetch"; @@ -147,7 +150,7 @@ describe("getAccessRequestFromRedirectUrl", () => { mode: normalizedAccessRequest.credentialSubject.hasConsent.mode[0], }, }, - }); + } as VerifiableCredential); const { accessRequest } = await getAccessRequestFromRedirectUrl(redirectedToUrl); expect(accessRequest).toStrictEqual(accessRequestVc); diff --git a/src/gConsent/request/getAccessGrantFromRedirectUrl.test.ts b/src/gConsent/request/getAccessGrantFromRedirectUrl.test.ts index 1e890b9d9..4fde8cbef 100644 --- a/src/gConsent/request/getAccessGrantFromRedirectUrl.test.ts +++ b/src/gConsent/request/getAccessGrantFromRedirectUrl.test.ts @@ -20,7 +20,10 @@ // import { describe, it, jest, expect, beforeAll } from "@jest/globals"; -import type { getVerifiableCredential } from "@inrupt/solid-client-vc"; +import type { + VerifiableCredential, + getVerifiableCredential, +} from "@inrupt/solid-client-vc"; import { fetch } from "@inrupt/universal-fetch"; import { getAccessGrantFromRedirectUrl } from "./getAccessGrantFromRedirectUrl"; import type { getSessionFetch } from "../../common/util/getSessionFetch"; @@ -149,7 +152,7 @@ describe("getAccessGrantFromRedirectUrl", () => { mode: normalizedAccessGrant.credentialSubject.providedConsent.mode[0], }, }, - }); + } as VerifiableCredential); const redirectUrl = new URL("https://redirect.url"); redirectUrl.searchParams.set( diff --git a/src/gConsent/util/access.mock.ts b/src/gConsent/util/access.mock.ts index c4f5dbdb7..4b645354d 100644 --- a/src/gConsent/util/access.mock.ts +++ b/src/gConsent/util/access.mock.ts @@ -110,7 +110,10 @@ export const mockAccessRequestVc = async ( modify?.(asObject); return normalizeAccessRequest( - await verifiableCredentialToDataset(asObject, { baseIRI: asObject.id }), + await verifiableCredentialToDataset(asObject, { + baseIRI: asObject.id, + includeVcProperties: true, + }), ) as unknown as AccessRequest; }; @@ -165,6 +168,7 @@ export const mockAccessGrantVc = async ( return normalizeAccessGrant( await verifiableCredentialToDataset(asObject, { baseIRI: asObject.id, + includeVcProperties: true, }), ) as unknown as AccessGrant; };