Skip to content

Commit

Permalink
Refactor VC flow e2e tests
Browse files Browse the repository at this point in the history
This PR refactors the VC related e2e tests such that the test_app is no
longer misused to test alternative origins configurations for the issuer.

And because the issuer can now be configured as needed in tests, the call to
the provision script in `dfx.json` no longer needs to configure the
test app as the issuer derivation origin either.

Additionally a new test is added to verify that the VC flow indeed fails
on issuer alternative origins misconfiguration.
  • Loading branch information
frederikrothenberger committed Jul 5, 2024
1 parent 83cf3f8 commit b234492
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 106 deletions.
16 changes: 16 additions & 0 deletions demos/vc_issuer/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ const App = () => {
}
};

// The derivation origin to use for authentication
const [derivationOrigin, setDerivationOrigin] = useState<string | undefined>(
undefined
);

// The principal, set during auth
const [principal, setPrincipal] = useState<string | undefined>(undefined);

Expand All @@ -57,6 +62,7 @@ const App = () => {
await new Promise<void>((resolve, reject) => {
authClient.login({
identityProvider: iiUrl,
derivationOrigin,
onSuccess: () => resolve(),
onError: reject,
});
Expand Down Expand Up @@ -127,6 +133,16 @@ const App = () => {
/>
</label>
</section>
<section>
<label>
Derivation origin (for authentication):
<input
data-role="derivation-origin"
type="text"
onChange={(evt) => setDerivationOrigin(evt.target.value)}
/>
</label>
</section>
<section>
<button
data-action="authenticate"
Expand Down
2 changes: 1 addition & 1 deletion dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"candid": "demos/vc_issuer/vc_demo_issuer.did",
"wasm": "demos/vc_issuer/vc_demo_issuer.wasm.gz",
"build": "demos/vc_issuer/build.sh",
"post_install": "bash -c 'demos/vc_issuer/provision --issuer-derivation-origin https://$(dfx canister id test_app).icp0.io'",
"post_install": "bash -c 'demos/vc_issuer/provision'",
"dependencies": [ "internet_identity" ]
}
},
Expand Down
8 changes: 5 additions & 3 deletions src/frontend/src/test-e2e/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { KnownDapp } from "$src/flows/dappsExplorer/dapps";
import { readCanisterId } from "@dfinity/internet-identity-vite-plugins/utils";
import { getReplicaHost } from "../../../vite-plugins/dist/utils";

// XXX: this is not exactly a constant (since it might change on every node eval) but in
// practice is very stable, and is much easier to use as "constants" than as a lookup function.
const testAppCanisterId = readCanisterId({ canisterName: "test_app" });
const issuerAppCanisterId = readCanisterId({ canisterName: "issuer" });
export const ISSUER_CANISTER_ID = readCanisterId({ canisterName: "issuer" });

export const REPLICA_URL = getReplicaHost();
export const TEST_APP_CANONICAL_URL = `https://${testAppCanisterId}.icp0.io`;
export const TEST_APP_CANONICAL_URL_RAW = `https://${testAppCanisterId}.raw.icp0.io`;
export const TEST_APP_CANONICAL_URL_LEGACY = `https://${testAppCanisterId}.ic0.app`;
Expand All @@ -16,8 +18,8 @@ export const KNOWN_TEST_DAPP = new KnownDapp({
logo: "no-such-logo",
});

export const ISSUER_APP_URL = `https://${issuerAppCanisterId}.icp0.io`;
export const ISSUER_APP_URL_LEGACY = `https://${issuerAppCanisterId}.ic0.app`;
export const ISSUER_APP_URL = `https://${ISSUER_CANISTER_ID}.icp0.io`;
export const ISSUER_APP_URL_LEGACY = `https://${ISSUER_CANISTER_ID}.ic0.app`;

// Value needs to match how the canister was provisioned
export const ISSUER_CUSTOM_ORIGIN_NICE_URL = `https://nice-issuer-custom-orig.com`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,52 @@ import { DemoAppView } from "$src/test-e2e/views";
import {
II_URL,
ISSUER_APP_URL,
ISSUER_CANISTER_ID,
ISSUER_CUSTOM_ORIGIN_NICE_URL,
KNOWN_TEST_DAPP,
TEST_APP_CANONICAL_URL,
TEST_APP_NICE_URL,
} from "$src/test-e2e/constants";

import { beforeEach } from "vitest";
import {
authenticateToRelyingParty,
authenticateWithIssuer,
getVCPresentation,
getVCPresentation_,
register,
registerWithIssuer,
resetIssuerOriginsConfig,
setIssuerAlternativeOrigins,
setIssuerDerivationOrigin,
} from "./utils";

beforeEach(async () => {
await resetIssuerOriginsConfig({ issuerCanisterId: ISSUER_CANISTER_ID });
});

test("Can issue credential with alternative RP derivation origin", async () => {
await runInBrowser(async (browser: WebdriverIO.Browser) => {
await browser.url(II_URL);

const authConfig = await register["webauthn"](browser);

// XXX: The issuer always specifies a derivation origin, which we need
// to whitelist (we use the test app because it's the only canister that
// has an API for setting alternative origins)
const demoAppView = new DemoAppView(browser);
await demoAppView.open(TEST_APP_CANONICAL_URL, II_URL);
await demoAppView.updateAlternativeOrigins(
`{"alternativeOrigins":["${ISSUER_APP_URL}"]}`,
"certified"
);

// Do the flow without alt origins

let vcTestApp = await authenticateToRelyingParty({
browser,
issuer: ISSUER_APP_URL,
authConfig,
relyingParty: TEST_APP_CANONICAL_URL,
});
const principalRP = await vcTestApp.getPrincipal();

// Add employee and set up issuer
const { msg: _msg, principal: _principal } = await registerWithIssuer({
// Add user as employee on the issuer
await registerWithIssuer({
browser,
issuer: ISSUER_APP_URL,
authConfig,
principal: principalRP,
});

vcTestApp = await authenticateToRelyingParty({
// Authenticate to RP without alt origins
const vcTestApp = await authenticateToRelyingParty({
browser,
issuer: ISSUER_APP_URL,
authConfig,
relyingParty: TEST_APP_CANONICAL_URL,
});
const principalRP = await vcTestApp.getPrincipal();

// Do the VC flow without alt origins
const { alias: alias_ } = await getVCPresentation({
vcTestApp,
browser,
Expand All @@ -68,7 +59,14 @@ test("Can issue credential with alternative RP derivation origin", async () => {
});
const alias = JSON.parse(alias_);

// 3. Do the flow WITH alt origins
// Set up alternative origin for RP VC flow
const demoAppView = new DemoAppView(browser);
await demoAppView.updateAlternativeOrigins(
`{"alternativeOrigins":["${TEST_APP_NICE_URL}"]}`,
"certified"
);

// Authenticate to RP WITH alt origins
const vcTestAppAlt = await authenticateToRelyingParty({
browser,
issuer: ISSUER_APP_URL,
Expand All @@ -79,12 +77,7 @@ test("Can issue credential with alternative RP derivation origin", async () => {
const principalRPAlt = await vcTestAppAlt.getPrincipal();
expect(principalRPAlt).toBe(principalRP);

// XXX: More gymnastics for alternative origins to be set correctly during VC flow
await demoAppView.updateAlternativeOrigins(
`{"alternativeOrigins":["${ISSUER_APP_URL}", "${TEST_APP_NICE_URL}"]}`,
"certified"
);

// Do the VC flow WITH alt origins
const { alias: aliasAlt_ } = await getVCPresentation({
vcTestApp: vcTestAppAlt,
browser,
Expand All @@ -102,19 +95,17 @@ test("Can issue credential with alternative RP derivation origin", async () => {
test("Cannot issue credential with bad alternative RP derivation origin", async () => {
await runInBrowser(async (browser: WebdriverIO.Browser) => {
await browser.url(II_URL);

const authConfig = await register["webauthn"](browser);

// 1. Add employee

const { msg: _msg, principal: _principal } = await registerWithIssuer({
browser,
issuer: ISSUER_APP_URL,
authConfig,
});
// 1. Set up alternative origin for RP
const demoAppView = new DemoAppView(browser);
await demoAppView.open(TEST_APP_NICE_URL, II_URL);
await demoAppView.updateAlternativeOrigins(
`{"alternativeOrigins":["${TEST_APP_NICE_URL}"]}`,
"certified"
);

// 2. Do the flow WITH alt origins RESET

const vcTestAppAlt = await authenticateToRelyingParty({
browser,
issuer: ISSUER_APP_URL,
Expand All @@ -123,8 +114,10 @@ test("Cannot issue credential with bad alternative RP derivation origin", async
derivationOrigin: TEST_APP_CANONICAL_URL,
});

await new DemoAppView(browser).resetAlternativeOrigins();
// 3. Reset alternative origins
await demoAppView.resetAlternativeOrigins();

// 4. Do the flow WITH alt origins RESET
const result = await getVCPresentation_({
vcTestApp: vcTestAppAlt,
browser,
Expand All @@ -147,59 +140,122 @@ test("Cannot issue credential with bad alternative RP derivation origin", async
test("Can issue credential with alternative issuer derivation origin", async () => {
await runInBrowser(async (browser: WebdriverIO.Browser) => {
await browser.url(II_URL);

// This runs a VC flow where the issuer uses a nice domain and uses the test-app as its
// derivation origin. We use the test-app as derivation origin because it is easy
// to configure its alternativeOrigins.

const authConfig = await register["webauthn"](browser);
// Nice URL for the issuer
const issuer = ISSUER_CUSTOM_ORIGIN_NICE_URL;
// Derivation origin used on the nice issuer URL
const derivationOrigin = ISSUER_APP_URL;
const relyingParty = TEST_APP_CANONICAL_URL;

const authConfig = await register["webauthn"](browser);
// authenticate to the issuer without alternative origins so that we can check that the alternative origins configuration is taking effect
// we do not add an employee, making sure the flow will later only succeed if alternative origins is working correctly.
const { principal: principalIss } = await authenticateWithIssuer({
browser,
issuer,
authConfig,
});

// First authenticate & grab the principal (because of alternative origins, it's the same
// for the test app and the issuer)
let vcTestApp = await authenticateToRelyingParty({
// configure issuer
await setIssuerAlternativeOrigins({
issuerCanisterId: ISSUER_CANISTER_ID,
alternativeOrigins: `{"alternativeOrigins":["${issuer}"]}`,
});
await setIssuerDerivationOrigin({
issuerCanisterId: ISSUER_CANISTER_ID,
frontendHostname: issuer,
derivationOrigin,
});
const { principal: principalAlt } = await registerWithIssuer({
browser,
issuer,
derivationOrigin,
authConfig,
});
// make sure alternative origins works as expected
expect(principalAlt).not.toBe(principalIss);

// Go through the VC flow
const vcTestApp = await authenticateToRelyingParty({
browser,
issuer,
authConfig,
relyingParty,
});
const principalRP = await vcTestApp.getPrincipal();
const { alias: _alias } = await getVCPresentation({
vcTestApp,
browser,
authConfig,
relyingParty,
issuer,
knownDapps: [KNOWN_TEST_DAPP],
});
});
}, 300_000);

// Register with the issuer, with a custom principal (i.e. not one generated by the issuer's
// origin)
const { msg: _msg, principal: _principal } = await registerWithIssuer({
test("Cannot issue credential with bad alternative issuer derivation origin", async () => {
await runInBrowser(async (browser: WebdriverIO.Browser) => {
await browser.url(II_URL);
const authConfig = await register["webauthn"](browser);
// Nice URL for the issuer
const issuer = ISSUER_CUSTOM_ORIGIN_NICE_URL;
// Derivation origin used on the nice issuer URL
const derivationOrigin = ISSUER_APP_URL;
const relyingParty = TEST_APP_CANONICAL_URL;

// authenticate to the issuer without alternative origins so that we can check that the alternative origins configuration is taking effect
// we do not add an employee, making sure the flow will later only succeed if alternative origins is working correctly.
const { principal: principalIss } = await authenticateWithIssuer({
browser,
issuer,
principal: principalRP,
authConfig,
});

// Go back to the test app and start the VC flow
// configure issuer
await setIssuerAlternativeOrigins({
issuerCanisterId: ISSUER_CANISTER_ID,
alternativeOrigins: `{"alternativeOrigins":["${issuer}"]}`,
});
await setIssuerDerivationOrigin({
issuerCanisterId: ISSUER_CANISTER_ID,
frontendHostname: issuer,
derivationOrigin,
});
const { principal: principalAlt } = await registerWithIssuer({
browser,
issuer,
derivationOrigin,
authConfig,
});
// make sure alternative origins works as expected
expect(principalAlt).not.toBe(principalIss);

// XXX: we use the test app as the derivation origin because it has an
// API for updating alternative origins
const demoAppView = new DemoAppView(browser);
await demoAppView.open(relyingParty, II_URL);
await demoAppView.updateAlternativeOrigins(
`{"alternativeOrigins":["${issuer}"]}`,
"certified"
);
// remove the alternative origin again
await setIssuerAlternativeOrigins({
issuerCanisterId: ISSUER_CANISTER_ID,
alternativeOrigins: '{"alternativeOrigins":[]}',
});

vcTestApp = await authenticateToRelyingParty({
// Go through the VC flow
const vcTestApp = await authenticateToRelyingParty({
browser,
issuer,
authConfig,
relyingParty,
});
const { alias: _alias } = await getVCPresentation({
const result = await getVCPresentation_({
vcTestApp,
browser,
authConfig,
relyingParty,
issuer,
knownDapps: [KNOWN_TEST_DAPP],
});
expect(result.result).toBe("aborted");
if (!("reason" in result)) {
throw new Error(
"Expected VC result to be aborted, got: " + JSON.stringify(result)
);
}
expect(result.reason).toBe("invalid_derivation_origin_issuer");
});
}, 300_000);
Loading

0 comments on commit b234492

Please sign in to comment.