From 588eb537e1590f394790b1ed5219e0c540bc9251 Mon Sep 17 00:00:00 2001 From: Mason Hu Date: Sun, 26 May 2024 10:26:20 +0200 Subject: [PATCH] chore: add oidc login tests Signed-off-by: Mason Hu --- .github/workflows/coverage.yaml | 18 ++++++++- .github/workflows/pr.yaml | 21 +++++++++- .gitignore | 1 + CONTRIBUTING.md | 12 ++++++ HACKING.md | 2 +- package.json | 6 ++- playwright.config.ts | 30 ++++++++++++++- tests/fixtures/lxd-test.ts | 2 + tests/helpers/permission-identities.ts | 2 +- tests/images.spec.ts | 4 ++ tests/scripts/create_oidc_identities | 32 ---------------- tests/scripts/delete_oidc_identities | 8 ---- tests/scripts/setup_test | 53 ++++++++++++++++++++++++++ tests/scripts/teardown_test | 13 +++++++ tests/setup/auth.setup.ts | 25 ++++++++++++ yarn.lock | 12 ++++++ 16 files changed, 192 insertions(+), 49 deletions(-) delete mode 100755 tests/scripts/create_oidc_identities delete mode 100755 tests/scripts/delete_oidc_identities create mode 100755 tests/scripts/setup_test create mode 100755 tests/scripts/teardown_test create mode 100644 tests/setup/auth.setup.ts diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 09b5a92890..7727fd5fb2 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -78,9 +78,23 @@ jobs: - name: Install Playwright Browser run: npx playwright install --with-deps chromium - - name: Create OIDC users + - name: Setup for tests shell: bash - run: ./tests/scripts/create_oidc_identities + env: + LXD_OIDC_CLIENT_ID: ${{ secrets.LXD_OIDC_CLIENT_ID }} + LXD_OIDC_ISSUER: ${{ secrets.LXD_OIDC_ISSUER }} + LXD_OIDC_AUDIENCE: ${{ secrets.LXD_OIDC_AUDIENCE }} + LXD_OIDC_USER: ${{ secrets.LXD_OIDC_USER }} + LXD_OIDC_PASSWORD: ${{ secrets.LXD_OIDC_PASSWORD }} + LXD_OIDC_GROUPS_CLAIM: ${{ secrets.LXD_OIDC_GROUPS_CLAIM }} + run: | + echo "LXD_OIDC_CLIENT_ID=$LXD_OIDC_CLIENT_ID" >> .env.local + echo "LXD_OIDC_ISSUER=$LXD_OIDC_ISSUER" >> .env.local + echo "LXD_OIDC_AUDIENCE=$LXD_OIDC_AUDIENCE" >> .env.local + echo "LXD_OIDC_USER=$LXD_OIDC_USER" >> .env.local + echo "LXD_OIDC_PASSWORD=$LXD_OIDC_PASSWORD" >> .env.local + echo "LXD_OIDC_GROUPS_CLAIM=$LXD_OIDC_GROUPS_CLAIM" >> .env.local + ./tests/scripts/setup_test - name: Run tests with coverage shell: bash diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index d1a8fd2b0f..b75fc71588 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -149,10 +149,27 @@ jobs: LXD_CHANNEL=$(echo '${{ matrix.lxd_channel }}' | sed 's#/#-#g') echo "LXD_CHANNEL=$LXD_CHANNEL" >> $GITHUB_OUTPUT - - name: Create OIDC users + - name: Setup for tests if: ${{ matrix.lxd_channel != '5.0/edge' }} shell: bash - run: ./tests/scripts/create_oidc_identities + env: + LXD_OIDC_CLIENT_ID: ${{ secrets.LXD_OIDC_CLIENT_ID }} + LXD_OIDC_ISSUER: ${{ secrets.LXD_OIDC_ISSUER }} + LXD_OIDC_AUDIENCE: ${{ secrets.LXD_OIDC_AUDIENCE }} + LXD_OIDC_USER: ${{ secrets.LXD_OIDC_USER }} + LXD_OIDC_PASSWORD: ${{ secrets.LXD_OIDC_PASSWORD }} + LXD_OIDC_GROUPS_CLAIM: ${{ secrets.LXD_OIDC_GROUPS_CLAIM }} + run: | + echo "LXD_OIDC_CLIENT_ID=$LXD_OIDC_CLIENT_ID" >> .env.local + echo "LXD_OIDC_ISSUER=$LXD_OIDC_ISSUER" >> .env.local + echo "LXD_OIDC_AUDIENCE=$LXD_OIDC_AUDIENCE" >> .env.local + echo "LXD_OIDC_USER=$LXD_OIDC_USER" >> .env.local + echo "LXD_OIDC_PASSWORD=$LXD_OIDC_PASSWORD" >> .env.local + echo "LXD_OIDC_GROUPS_CLAIM=$LXD_OIDC_GROUPS_CLAIM" >> .env.local + ./tests/scripts/setup_test + + - name: show lxd configs + run: sudo lxc config show - name: Run Playwright tests run: npx playwright test --project ${{ matrix.browser }}:lxd-${{ steps.lxd-env.outputs.LXD_CHANNEL }} diff --git a/.gitignore b/.gitignore index 4b834d7f1d..e24020eedc 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,4 @@ dist/ tests/screenshots haproxy-local.cfg +tests/.auth diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9940391cdb..88b721ac2a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -137,6 +137,18 @@ The e2e tests can be run against LXD 5.0, or the edge version of LXD. If you wan snap refresh lxd --channel latest/edge +The tests are carried out for oidc authenticated identities and therefore will require oidc setup for your lxd server. You may refer to the [Setup oidc login wiki page](https://github.com/canonical/lxd-ui/wiki/Setup-oidc-login) for setup instructions. Once you have completed the oidc setup, create a `.env.local` file at the root level of the project and ensure the environment variables shown below are set against the relevant lxd server oidc config values: + # Configs that enables OIDC authentication for the lxd server + LXD_OIDC_CLIENT_ID=[oidc.client.id] + LXD_OIDC_ISSUER=[oidc.issuer] + LXD_OIDC_AUDIENCE=[oidc.audience] + # Config required for provisioning the OIDC identity with admin permission + LXD_OIDC_GROUPS_CLAIM=[oidc.groups.claim] + + # user email and password from your identity provider for the identity that you intend to authenticate with + LXD_OIDC_USER=[login-user-email] + LXD_OIDC_PASSWORD=[login-user-password] + The tests expect the environment on localhost to be accessible. Execute `dotrun` first then run the tests against the latest LXD version with yarn test-e2e-edge diff --git a/HACKING.md b/HACKING.md index 57d098dea2..281cb1f1fc 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1 +1 @@ -moved to [contribution guidelines](CONTRIBUTING.md) \ No newline at end of file +moved to [contribution guidelines](CONTRIBUTING.md) diff --git a/package.json b/package.json index 557c0cf72e..b9de58f398 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "start": "concurrently --kill-others --raw 'vite --host | grep -v 3000' 'yarn serve'", "serve": "./entrypoint", "test-js": "vitest --run", - "test-e2e-edge": "tests/scripts/create_oidc_identities && npx playwright test --project chromium:lxd-latest-edge firefox:lxd-latest-edge && tests/scripts/delete_oidc_identities", - "test-e2e-5.21-edge": "tests/scripts/create_oidc_identities && npx playwright test --project chromium:lxd-5.21-edge firefox:lxd-5.21-edge && tests/scripts/delete_oidc_identities", + "test-e2e-edge": "tests/scripts/setup_test && npx playwright test --project chromium:lxd-latest-edge firefox:lxd-latest-edge && tests/scripts/teardown_test", + "test-e2e-5.21-edge": "tests/scripts/setup_test && npx playwright test --project chromium:lxd-5.21-edge firefox:lxd-5.21-edge && tests/scripts/teardown_test", "test-e2e-5.0-edge": "npx playwright test --project chromium:lxd-5.0-edge firefox:lxd-5.0-edge", "test-coverage": "yarn test-js-coverage && yarn test-e2e-coverage && yarn test-report-coverage", "test-js-coverage": "vitest --run --coverage", @@ -62,6 +62,7 @@ "@playwright/test": "1.41.0", "@types/convert-source-map": "2.0.3", "@types/cytoscape-popper": "2.0.4", + "@types/dotenv": "8.2.0", "@types/lodash.isequal": "^4.5.8", "@types/node-forge": "1.3.11", "@types/react": "18.2.48", @@ -77,6 +78,7 @@ "babel-plugin-istanbul": "6.1.1", "concurrently": "8.2.2", "convert-source-map": "2.0.0", + "dotenv": "16.4.5", "eslint": "8.56.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", diff --git a/playwright.config.ts b/playwright.config.ts index 737998d276..9e250b3416 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,13 @@ import type { PlaywrightTestConfig } from "@playwright/test"; import { devices } from "@playwright/test"; -import { TestOptions } from "./tests/fixtures/lxd-test"; +import { TestOptions, authFile } from "./tests/fixtures/lxd-test"; +import dotenv from "dotenv"; +import path from "path"; +import { fileURLToPath } from "url"; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +// provide environment variables from .env.local to all tests +dotenv.config({ path: path.resolve(__dirname, ".env.local") }); /** * See https://playwright.dev/docs/test-configuration. @@ -42,6 +49,17 @@ const config: PlaywrightTestConfig = { /* Configure projects for major browsers */ projects: [ + // Setup project + { + name: "setup-chromium", + use: { ...devices["Desktop Chrome"] }, + testMatch: /.*\.setup\.ts/, + }, + { + name: "setup-firefox", + use: { ...devices["Desktop Firefox"] }, + testMatch: /.*\.setup\.ts/, + }, { name: "chromium:lxd-5.0-edge", use: { @@ -61,36 +79,46 @@ const config: PlaywrightTestConfig = { use: { ...devices["Desktop Chrome"], lxdVersion: "5.21-edge", + storageState: authFile, }, + dependencies: ["setup-chromium"], }, { name: "firefox:lxd-5.21-edge", use: { ...devices["Desktop Firefox"], lxdVersion: "5.21-edge", + storageState: authFile, }, + dependencies: ["setup-firefox"], }, { name: "chromium:lxd-latest-edge", use: { ...devices["Desktop Chrome"], lxdVersion: "latest-edge", + storageState: authFile, }, + dependencies: ["setup-chromium"], }, { name: "firefox:lxd-latest-edge", use: { ...devices["Desktop Firefox"], lxdVersion: "latest-edge", + storageState: authFile, }, + dependencies: ["setup-firefox"], }, { name: "coverage", use: { ...devices["Desktop Chrome"], lxdVersion: "latest-edge", + storageState: authFile, hasCoverage: true, }, + dependencies: ["setup-chromium"], }, ], }; diff --git a/tests/fixtures/lxd-test.ts b/tests/fixtures/lxd-test.ts index 3e41bda180..35cf262364 100644 --- a/tests/fixtures/lxd-test.ts +++ b/tests/fixtures/lxd-test.ts @@ -26,3 +26,5 @@ export const test = base.extend({ }); export const expect = test.expect; + +export const authFile = "tests/.auth/user.json"; diff --git a/tests/helpers/permission-identities.ts b/tests/helpers/permission-identities.ts index 6b3b511fd3..a685c635f1 100644 --- a/tests/helpers/permission-identities.ts +++ b/tests/helpers/permission-identities.ts @@ -1,7 +1,7 @@ import { Page } from "@playwright/test"; import { expect } from "../fixtures/lxd-test"; -// These identities are created by the create_oidc_identities script in tests/scripts +// These identities are created by the setup_test script in tests/scripts export const identityBar = "bar@bar.com"; export const identityFoo = "foo@foo.com"; diff --git a/tests/images.spec.ts b/tests/images.spec.ts index 03ebf207a8..952a47d2b5 100644 --- a/tests/images.spec.ts +++ b/tests/images.spec.ts @@ -4,6 +4,10 @@ import { deleteInstance, randomInstanceName } from "./helpers/instances"; test("search for custom image and create an instance from it", async ({ page, }) => { + test.skip( + Boolean(!process.env.CI), + "Skipping test locally for custom image creation", + ); const customInstance = randomInstanceName(); const image = "my-custom-image"; // this is created in pr.yaml and coverage.yaml diff --git a/tests/scripts/create_oidc_identities b/tests/scripts/create_oidc_identities deleted file mode 100755 index 8e4df8098e..0000000000 --- a/tests/scripts/create_oidc_identities +++ /dev/null @@ -1,32 +0,0 @@ -#! /usr/bin/env bash -set -e - -# create oidc user foo -lxd sql global " - INSERT OR REPLACE INTO identities - (id, auth_method, type, identifier, name, metadata) - VALUES - ( - (SELECT id from identities WHERE name='foo'), - 2, - 5, - 'foo@foo.com', - 'foo', - '{}' - ); -" - -# create oidc user bar -lxd sql global " - INSERT OR REPLACE INTO identities - (id, auth_method, type, identifier, name, metadata) - VALUES - ( - (SELECT id from identities WHERE name='bar'), - 2, - 5, - 'bar@bar.com', - 'bar', - '{}' - ); -" diff --git a/tests/scripts/delete_oidc_identities b/tests/scripts/delete_oidc_identities deleted file mode 100755 index 82335eab29..0000000000 --- a/tests/scripts/delete_oidc_identities +++ /dev/null @@ -1,8 +0,0 @@ -#! /usr/bin/env bash -set -e - -lxd sql global " - DELETE - FROM identities - WHERE name IN ('foo', 'bar'); -" diff --git a/tests/scripts/setup_test b/tests/scripts/setup_test new file mode 100755 index 0000000000..afdca386d2 --- /dev/null +++ b/tests/scripts/setup_test @@ -0,0 +1,53 @@ +#! /usr/bin/env bash +set -e + +# remove tls cert for oidc login +FINGERPRINT=$(lxc config trust list | grep lxd-ui.crt | awk '{print $8}') +lxc config trust remove ${FINGERPRINT} + +# setup oidc configs +if [ -f .env.local ] +then + set -o allexport; source .env.local; set +o allexport +fi +lxc config set oidc.issuer=${LXD_OIDC_ISSUER} +lxc config set oidc.client.id=${LXD_OIDC_CLIENT_ID} +lxc config set oidc.audience=${LXD_OIDC_AUDIENCE} +lxc config set oidc.groups.claim=${LXD_OIDC_GROUPS_CLAIM} + +# create identity provider group mapping +lxc auth group create login-admin +lxc auth group permission add login-admin server admin +# The name of the identity provider group should be the same as the role name assigned to the user +lxc auth identity-provider-group create admin +lxc auth identity-provider-group group add admin login-admin + +# create oidc user foo +lxd sql global " + INSERT OR REPLACE INTO identities + (id, auth_method, type, identifier, name, metadata) + VALUES + ( + (SELECT id from identities WHERE name='foo'), + 2, + 5, + 'foo@foo.com', + 'foo', + '{}' + ); +" + +# create oidc user bar +lxd sql global " + INSERT OR REPLACE INTO identities + (id, auth_method, type, identifier, name, metadata) + VALUES + ( + (SELECT id from identities WHERE name='bar'), + 2, + 5, + 'bar@bar.com', + 'bar', + '{}' + ); +" diff --git a/tests/scripts/teardown_test b/tests/scripts/teardown_test new file mode 100755 index 0000000000..9947189143 --- /dev/null +++ b/tests/scripts/teardown_test @@ -0,0 +1,13 @@ +#! /usr/bin/env bash +set -e + +lxd sql global " + DELETE + FROM identities + WHERE name IN ('foo', 'bar', 'admin'); +" +lxc auth identity-provider-group delete admin +lxc auth group delete login-admin + +# add tls cert back +lxc config trust add keys/lxd-ui.crt diff --git a/tests/setup/auth.setup.ts b/tests/setup/auth.setup.ts new file mode 100644 index 0000000000..043cc79143 --- /dev/null +++ b/tests/setup/auth.setup.ts @@ -0,0 +1,25 @@ +import { Page } from "@playwright/test"; +import { test as setup, expect, authFile } from "../fixtures/lxd-test"; + +const loginUser = async (page: Page) => { + await page.getByRole("link", { name: "Login with SSO" }).click(); + await page.getByLabel("Email address*").click(); + await page.getByLabel("Email address*").fill(process.env.LXD_OIDC_USER || ""); + await page.getByLabel("Password*").click(); + await page.getByLabel("Password*").fill(process.env.LXD_OIDC_PASSWORD || ""); + await page.getByRole("button", { name: "Continue", exact: true }).click(); + await expect(page.getByText("Log out")).toBeVisible(); +}; + +setup("authenticate", async ({ page }) => { + await page.goto("/ui/"); + await loginUser(page); + // Check logout functionality + await page.getByText("Log out").click(); + await expect( + page.getByRole("link", { name: "Login with SSO" }), + ).toBeVisible(); + await loginUser(page); + + await page.context().storageState({ path: authFile }); +}); diff --git a/yarn.lock b/yarn.lock index 6a4d977094..184cb88b98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1623,6 +1623,13 @@ resolved "https://registry.yarnpkg.com/@types/cytoscape/-/cytoscape-3.21.0.tgz#30f5634774862b9a507b89a1b10616efabf1bf88" integrity sha512-RN5SPiyVDpUP+LoOlxxlOYAMzkE7iuv3gA1jt3Hx2qTwArpZVPPdO+SI0hUj49OAn4QABR7JK9Gi0hibzGE0Aw== +"@types/dotenv@8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-8.2.0.tgz#5cd64710c3c98e82d9d15844375a33bf1b45d053" + integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw== + dependencies: + dotenv "*" + "@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" @@ -2905,6 +2912,11 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +dotenv@*, dotenv@16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"