Skip to content

Commit

Permalink
Use AccessGrantError and UmaError
Browse files Browse the repository at this point in the history
  • Loading branch information
NSeydoux committed Dec 13, 2024
1 parent adec7b8 commit 064172c
Show file tree
Hide file tree
Showing 18 changed files with 53 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/common/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export function getRequestor(vc: DatasetWithId): string {
);
}

throw new Error(`No requestor found.`);
throw new AccessGrantError(`No requestor found.`);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/common/verify/isValidAccessGrant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { getBaseAccess } from "../../gConsent/util/getBaseAccessVerifiableCredential";
import { getSessionFetch } from "../util/getSessionFetch";
import { toVcDataset } from "../util/toVcDataset";
import { AccessGrantError } from "../errors/AccessGrantError";

/**
* Makes a request to the access server to verify the validity of a given Verifiable Credential.
Expand All @@ -52,7 +53,7 @@ async function isValidAccessGrant(
const validVc = await toVcDataset(vc, options);

if (validVc === undefined) {
throw new Error(
throw new AccessGrantError(
`Invalid argument: expected either a VC URL or a RDFJS DatasetCore, received ${vc}`,
);
}
Expand All @@ -66,7 +67,7 @@ async function isValidAccessGrant(
.verifierService;

if (verifierEndpoint === undefined) {
throw new Error(
throw new AccessGrantError(
`The VC service provider ${getIssuer(
vcObject,
)} does not advertize for a verifier service in its .well-known/vc-configuration document`,
Expand Down
11 changes: 6 additions & 5 deletions src/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
CONTEXT_ESS_DEFAULT,
PRESENTATION_TYPE_BASE,
} from "../gConsent/constants";
import { UmaError } from "../common/errors/UmaError";

const WWW_AUTH_HEADER = "www-authenticate";
const VC_CLAIM_TOKEN_TYPE = "https://www.w3.org/TR/vc-data-model/#json-ld";
Expand Down Expand Up @@ -75,7 +76,7 @@ export async function getUmaConfiguration(
const configurationUrl = new URL(UMA_CONFIG_PATH, authIri).href;
const response = await fetch(configurationUrl);
return response.json().catch((e) => {
throw new Error(
throw new UmaError(
`Parsing the UMA configuration found at ${configurationUrl} failed with the following error: ${e.toString()}`,
);
});
Expand Down Expand Up @@ -178,18 +179,18 @@ export async function fetchWithVc(
const wwwAuthentication = headers.get(WWW_AUTH_HEADER);

if (!wwwAuthentication) {
throw new Error(NO_WWW_AUTH_HEADER_ERROR);
throw new UmaError(NO_WWW_AUTH_HEADER_ERROR);
}

const authTicket = parseUMAAuthTicket(wwwAuthentication);
const authIri = parseUMAAuthIri(wwwAuthentication);

if (!authTicket) {
throw new Error(NO_WWW_AUTH_HEADER_UMA_TICKET_ERROR);
throw new UmaError(NO_WWW_AUTH_HEADER_UMA_TICKET_ERROR);
}

if (!authIri) {
throw new Error(NO_WWW_AUTH_HEADER_UMA_IRI_ERROR);
throw new UmaError(NO_WWW_AUTH_HEADER_UMA_IRI_ERROR);
}

const umaConfiguration = await getUmaConfiguration(authIri);
Expand All @@ -203,7 +204,7 @@ export async function fetchWithVc(
);

if (!accessToken) {
throw new Error(NO_ACCESS_TOKEN_RETURNED);
throw new UmaError(NO_ACCESS_TOKEN_RETURNED);
}

return boundFetch(accessToken);
Expand Down
10 changes: 6 additions & 4 deletions src/gConsent/discover/getAccessApiEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import type { UrlString } from "@inrupt/solid-client";
import { parse } from "auth-header";
import type { AccessBaseOptions } from "../type/AccessBaseOptions";
import { AccessGrantError } from "../../common/errors/AccessGrantError";
import { UmaError } from "../../common/errors/UmaError";

Check failure on line 26 in src/gConsent/discover/getAccessApiEndpoint.ts

View workflow job for this annotation

GitHub Actions / lint / lint

'UmaError' is defined but never used

async function getAccessEndpointForResource(
resource: UrlString,
Expand All @@ -30,14 +32,14 @@ async function getAccessEndpointForResource(
// authorization server.
const response = await fetch(resource);
if (!response.headers.has("WWW-Authenticate")) {
throw new Error(
throw new AccessGrantError(
`Expected a 401 error with a WWW-Authenticate header, got a [${response.status}: ${response.statusText}] response lacking the WWW-Authenticate header.`,
);
}
const authHeader = response.headers.get("WWW-Authenticate") as string;
const authHeaderToken = parse(authHeader);
if (authHeaderToken.scheme !== "UMA") {
throw new Error(
throw new AccessGrantError(
`Unsupported authorization scheme: [${authHeaderToken.scheme}]`,
);
}
Expand All @@ -49,7 +51,7 @@ async function getAccessEndpointForResource(
const rawDiscoveryDocument = await fetch(wellKnownIri.href);
const discoveryDocument = await rawDiscoveryDocument.json();
if (typeof discoveryDocument.verifiable_credential_issuer !== "string") {
throw new Error(
throw new AccessGrantError(
`No access issuer listed for property [verifiable_credential_issuer] in [${JSON.stringify(
discoveryDocument,
)}]`,
Expand All @@ -76,7 +78,7 @@ async function getAccessApiEndpoint(
try {
return await getAccessEndpointForResource(resource.toString());
} catch (e: unknown) {
throw new Error(
throw new AccessGrantError(
`Couldn't figure out the Access Grant issuer from the resources: ${e}`,
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/gConsent/discover/redirectToAccessManagementUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type { FetchOptions } from "../../type/FetchOptions";
import type { RedirectOptions } from "../../type/RedirectOptions";
import { getResources } from "../../common";
import { toVcDataset } from "../../common/util/toVcDataset";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

export const REQUEST_VC_URL_PARAM_NAME = "requestVcUrl";
export const REDIRECT_URL_PARAM_NAME = "redirectUrl";
Expand Down Expand Up @@ -104,7 +105,7 @@ export async function redirectToAccessManagementUi(
const validVc = await toVcDataset(accessRequestVc, options);

if (validVc === undefined) {
throw new Error(
throw new AccessGrantError(
`Invalid argument: expected either a VC URL or a RDFJS DatasetCore, received ${accessRequestVc}`,
);
}
Expand All @@ -123,7 +124,7 @@ export async function redirectToAccessManagementUi(
});

if (accessManagementUi === undefined) {
throw new Error(
throw new AccessGrantError(
`Cannot discover access management UI URL for [${resourceUrl}]${
options.resourceOwner ? `, neither from [${options.resourceOwner}]` : ""
}`,
Expand Down
3 changes: 2 additions & 1 deletion src/gConsent/guard/isGConsentAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { RESOURCE_ACCESS_MODE } from "../../type/ResourceAccessMode";
import { isUnknownObject } from "./isUnknownObject";
import type { GConsentStatus } from "../type/GConsentStatus";
import { acl, gc } from "../../common/constants";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

const { defaultGraph } = DataFactory;

Expand Down Expand Up @@ -104,7 +105,7 @@ export function isRdfjsGConsentAttributes(
);

if (forPersonalData.size === 0) {
throw new Error("No Personal Data specified for Access Grant");
throw new AccessGrantError("No Personal Data specified for Access Grant");
}

for (const { object } of forPersonalData) {
Expand Down
7 changes: 4 additions & 3 deletions src/gConsent/manage/approveAccessRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { initializeGrantParameters } from "../util/initializeGrantParameters";
import { getGrantBody, issueAccessVc } from "../util/issueAccessVc";
import { toVcDataset } from "../../common/util/toVcDataset";
import type { CustomField } from "../../type/CustomField";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

export type ApproveAccessRequestOverrides = Omit<
Omit<AccessGrantParameters, "status">,
Expand All @@ -75,7 +76,7 @@ export function normalizeAccessGrant<T extends VerifiableCredentialBase>(
// Proper type checking is performed after normalization, so casting here is fine.
const normalized = { ...accessGrant } as unknown as AccessGrant;
if (normalized.credentialSubject.providedConsent === undefined) {
throw new Error(
throw new AccessGrantError(
`[${normalized.id}] is not an Access Grant: missing field "credentialSubject.providedConsent".`,
);
}
Expand Down Expand Up @@ -128,7 +129,7 @@ async function addVcMatcher(
);
// eslint-disable-next-line camelcase
if (!acp_ess_2.hasAccessibleAcr(resourceInfo)) {
throw new Error(
throw new AccessGrantError(
"The current user does not have access to the resource's Access Control Resource. Either they have insufficiant credentials, or the resource is not controlled using ACP. In either case, an Access Grant cannot be issued.",
);
}
Expand Down Expand Up @@ -340,7 +341,7 @@ export async function approveAccessRequest(
!isAccessGrant(accessGrant)
: !isRdfjsBaseAccessGrantVerifiableCredential(accessGrant)
) {
throw new Error(
throw new AccessGrantError(
`Unexpected response when approving Access Request, the result is not an Access Grant: ${JSON.stringify(
accessGrant,
)}`,
Expand Down
3 changes: 2 additions & 1 deletion src/gConsent/manage/denyAccessRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { getGrantBody, issueAccessVc } from "../util/issueAccessVc";
import { normalizeAccessGrant } from "./approveAccessRequest";
import { getBaseAccess } from "../util/getBaseAccessVerifiableCredential";
import { toVcDataset } from "../../common/util/toVcDataset";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

/**
* Deny an access request. The content of the denied access request is provided
Expand Down Expand Up @@ -95,7 +96,7 @@ async function denyAccessRequest(
const validVc = await toVcDataset(vc, options);

if (validVc === undefined) {
throw new Error(
throw new AccessGrantError(
`Invalid argument: expected either a VC URL or a RDFJS DatasetCore, received ${vc}`,
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/gConsent/manage/getAccessGrant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import type { AccessBaseOptions } from "../type/AccessBaseOptions";
import type { AccessGrant } from "../type/AccessGrant";
import { normalizeAccessGrant } from "./approveAccessRequest";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

/**
* Retrieve the Access Grant associated to the given URL.
Expand Down Expand Up @@ -105,7 +106,7 @@ export async function getAccessGrant(
!isRdfjsBaseAccessGrantVerifiableCredential(data) ||
!isGConsentAccessGrant(data)
) {
throw new Error(
throw new AccessGrantError(
`Unexpected response when resolving [${vcUrl}], the result is not an Access Grant: ${JSON.stringify(
data,
null,
Expand All @@ -121,7 +122,7 @@ export async function getAccessGrant(
normalize: normalizeAccessGrant,
});
if (!isBaseAccessGrantVerifiableCredential(data) || !isAccessGrant(data)) {
throw new Error(
throw new AccessGrantError(
`Unexpected response when resolving [${vcUrl}], the result is not an Access Grant: ${JSON.stringify(
data,
)}`,
Expand Down
5 changes: 4 additions & 1 deletion src/gConsent/manage/getAccessGrantAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
import { getInherit, getResources } from "../../common/getters";
import { normalizeAccessGrant } from "./approveAccessRequest";
import { gc } from "../../common/constants";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

export type AccessParameters = Partial<
Pick<IssueAccessRequestParameters, "access" | "purpose"> & {
Expand Down Expand Up @@ -157,7 +158,9 @@ async function getAccessGrantAll(
} = {},
): Promise<Array<DatasetWithId>> {
if (!params.resource && !options.accessEndpoint) {
throw new Error("resource and accessEndpoint cannot both be undefined");
throw new AccessGrantError(
"resource and accessEndpoint cannot both be undefined",
);
}
const sessionFetch = await getSessionFetch(options);
// TODO: Fix access API endpoint retrieval (should include all the different API endpoints)
Expand Down
5 changes: 3 additions & 2 deletions src/gConsent/manage/getAccessRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import type { AccessRequest } from "../type/AccessRequest";
import { getSessionFetch } from "../../common/util/getSessionFetch";
import { normalizeAccessRequest } from "../request/issueAccessRequest";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

/**
* Fetch the Access Request from the given URL.
Expand Down Expand Up @@ -94,7 +95,7 @@ export async function getAccessRequest(
});

if (!isRdfjsAccessRequest(accessRequest)) {
throw new Error(
throw new AccessGrantError(
`${JSON.stringify(accessRequest)} is not an Access Request`,
);
}
Expand All @@ -107,7 +108,7 @@ export async function getAccessRequest(
});

if (!isAccessRequest(accessRequest)) {
throw new Error(
throw new AccessGrantError(
`${JSON.stringify(accessRequest, null, 2)} is not an Access Request`,
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/gConsent/manage/getAccessRequestFromRedirectUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ import {
} from "../discover/redirectToAccessManagementUi";
import type { AccessRequest } from "../type/AccessRequest";
import getAccessRequest from "./getAccessRequest";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

function getSearchParam(url: URL, param: string) {
const value = url.searchParams.get(param);
if (value === null) {
throw new Error(
throw new AccessGrantError(
`The provided redirect URL [${url.toString()}] is missing the expected [${param}] query parameter`,
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/gConsent/manage/revokeAccessGrant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { getSessionFetch } from "../../common/util/getSessionFetch";
import type { AccessBaseOptions } from "../type/AccessBaseOptions";
import { getBaseAccess } from "../util/getBaseAccessVerifiableCredential";
import { toVcDataset } from "../../common/util/toVcDataset";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

const { quad, namedNode } = DataFactory;

Expand All @@ -57,7 +58,7 @@ async function revokeAccessCredential(
const validVc = await toVcDataset(vc, options);

if (validVc === undefined) {
throw new Error(
throw new AccessGrantError(
`Invalid argument: expected either a VC URL or a RDFJS DatasetCore, received ${vc}`,
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/gConsent/request/getAccessGrantFromRedirectUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { DatasetWithId } from "@inrupt/solid-client-vc";
import { getAccessGrant } from "../manage";
import { GRANT_VC_URL_PARAM_NAME } from "../manage/redirectToRequestor";
import type { AccessGrant } from "../type/AccessGrant";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

/**
* Get the Access Grant out of the incoming redirect from the Access Management app.
Expand Down Expand Up @@ -83,7 +84,7 @@ export async function getAccessGrantFromRedirectUrl(
GRANT_VC_URL_PARAM_NAME,
);
if (accessGrantIri === null) {
throw new Error(
throw new AccessGrantError(
`The provided redirect URL [${redirectUrl}] is missing the expected [${GRANT_VC_URL_PARAM_NAME}] query parameter`,
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/gConsent/request/issueAccessRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
isRdfjsAccessRequest,
} from "../guard/isAccessRequest";
import { gc } from "../../common/constants";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

/**
* Internal function. This is a stopgap until we have proper JSON-LD parsing.
Expand Down Expand Up @@ -152,7 +153,7 @@ async function issueAccessRequest(
returnLegacyJsonld: false,
});
if (!isRdfjsAccessRequest(accessRequest)) {
throw new Error(
throw new AccessGrantError(
`${JSON.stringify(accessRequest)} is not an Access Request`,
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/gConsent/util/getBaseAccessVerifiableCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { DataFactory } from "n3";
import { TYPE, gc } from "../../common/constants";
import type { AccessBaseOptions } from "../type/AccessBaseOptions";
import { getConsent } from "../../common/getters";
import { AccessGrantError } from "../../common/errors/AccessGrantError";

const { quad, namedNode } = DataFactory;

Expand All @@ -36,12 +37,12 @@ export async function getBaseAccess(
hasStatus?: NamedNode,
) {
if (type && !vc.has(quad(namedNode(getId(vc)), TYPE, type))) {
throw new Error(
throw new AccessGrantError(
`An error occurred when type checking the VC: Not of type [${type.value}].`,
);
}
if (hasStatus && !vc.has(quad(getConsent(vc), gc.hasStatus, hasStatus))) {
throw new Error(
throw new AccessGrantError(
`An error occurred when type checking the VC: status not [${hasStatus.value}].`,
);
}
Expand Down
Loading

0 comments on commit 064172c

Please sign in to comment.