Add advanced extension list editors
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
// XCA-style editors for advanced extension list fields.
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
function unique(prefix) {
|
||||
return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
async function field(page, label) {
|
||||
return page.locator("label").filter({ hasText: label }).first().locator("..").locator("input, select, textarea").first();
|
||||
}
|
||||
|
||||
async function login(page, username) {
|
||||
await page.goto("/login");
|
||||
await page.getByRole("link", { name: "Sign in with default" }).click();
|
||||
await page.getByPlaceholder("Enter any user/subject").fill(username);
|
||||
await page.getByRole("button", { name: "Sign-in" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Vaults" })).toBeVisible();
|
||||
}
|
||||
|
||||
async function openEditor(page, buttonId, title) {
|
||||
await page.locator(buttonId).click();
|
||||
const dialog = page.getByRole("dialog", { name: title, exact: true });
|
||||
await expect(dialog).toBeVisible();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
async function addRow(dialog, type, value) {
|
||||
const index = await dialog.locator(".list-entry").count();
|
||||
await dialog.getByRole("button", { name: "Add", exact: true }).click();
|
||||
const row = dialog.locator(".list-entry").nth(index);
|
||||
await row.locator("select").selectOption(type);
|
||||
await row.locator(".list-value").fill(value);
|
||||
return row;
|
||||
}
|
||||
|
||||
test("advanced extension list editors", async ({ page }) => {
|
||||
const user = unique("list-editor");
|
||||
const vaultName = unique("List Editors");
|
||||
const certName = unique("constraints.example");
|
||||
const crlUrls = ["https://ca.example.com/root.crl", "https://backup.example.com/root.crl"];
|
||||
const cpsUrls = ["https://example.com/cps", "https://example.com/policy"];
|
||||
|
||||
await login(page, user);
|
||||
await page.getByRole("button", { name: "New vault" }).click();
|
||||
await page.getByPlaceholder("Production PKI").fill(vaultName);
|
||||
await page.getByRole("button", { name: "Create vault" }).click();
|
||||
await page.getByRole("link", { name: vaultName }).click();
|
||||
const vaultUrl = page.url();
|
||||
|
||||
const settingsResponse = page.waitForResponse(
|
||||
(response) => response.url().includes("/api/vaults/") && response.url().endsWith("/settings") && response.ok()
|
||||
);
|
||||
await page.goto(`${vaultUrl}/certificates/new`);
|
||||
await settingsResponse;
|
||||
await (await field(page, "Internal name")).fill(certName);
|
||||
await (await field(page, "Common name (CN)")).fill(certName);
|
||||
await (await field(page, "Basic constraints")).selectOption("ca");
|
||||
await page.locator("#new-key").check();
|
||||
|
||||
// URI-list edits can be cancelled without changing the original textarea.
|
||||
let dialog = await openEditor(page, "#edit-crl-urls", "CRL distribution point URLs");
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).not.toBeChecked();
|
||||
await addRow(dialog, "URI", "https://discarded.example.com/root.crl");
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(page.locator("#crl-urls")).toHaveValue("");
|
||||
|
||||
dialog = await openEditor(page, "#edit-crl-urls", "CRL distribution point URLs");
|
||||
await expect(dialog.locator(".list-entry")).toHaveCount(0);
|
||||
await addRow(dialog, "URI", crlUrls[0]);
|
||||
await addRow(dialog, "URI", crlUrls[1]);
|
||||
await dialog.getByLabel("Critical", { exact: true }).check();
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#crl-urls")).toHaveValue(crlUrls.join("\n"));
|
||||
|
||||
dialog = await openEditor(page, "#edit-crl-urls", "CRL distribution point URLs");
|
||||
await expect(dialog.locator(".list-entry")).toHaveCount(2);
|
||||
await expect(dialog.locator(".list-entry").nth(1).locator(".list-value")).toHaveValue(crlUrls[1]);
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked();
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
|
||||
// Name constraints use typed rows and share one extension-level critical flag.
|
||||
dialog = await openEditor(page, "#edit-nc-permitted", "Name constraints · permitted");
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked();
|
||||
await addRow(dialog, "DNS", ".example.com");
|
||||
await addRow(dialog, "DNS", "example.net");
|
||||
await dialog.getByLabel("Critical", { exact: true }).uncheck();
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#nc-permitted")).toHaveValue("DNS:.example.com, DNS:example.net");
|
||||
|
||||
dialog = await openEditor(page, "#edit-nc-excluded", "Name constraints · excluded");
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).not.toBeChecked();
|
||||
await dialog.getByLabel("Critical", { exact: true }).check();
|
||||
await addRow(dialog, "DNS", ".bad.example");
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#nc-excluded")).toHaveValue("DNS:.bad.example");
|
||||
|
||||
dialog = await openEditor(page, "#edit-nc-permitted", "Name constraints · permitted");
|
||||
await expect(dialog.locator(".list-entry")).toHaveCount(2);
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked();
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
|
||||
// CPS URIs use comma-separated plain URLs and support row deletion.
|
||||
await (await field(page, "Certificate policy OIDs")).fill("1.3.6.1.4.1.1");
|
||||
dialog = await openEditor(page, "#edit-policy-cps", "Policy CPS URIs");
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).not.toBeChecked();
|
||||
await dialog.getByLabel("Critical", { exact: true }).check();
|
||||
await addRow(dialog, "URI", cpsUrls[0]);
|
||||
await addRow(dialog, "URI", cpsUrls[1]);
|
||||
const discarded = await addRow(dialog, "URI", "https://discarded.example.com/cps");
|
||||
await discarded.getByRole("button", { name: "Delete", exact: true }).click();
|
||||
await expect(dialog.locator(".list-entry")).toHaveCount(2);
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#policy-cps")).toHaveValue(cpsUrls.join(", "));
|
||||
|
||||
await expect(await field(page, "Common name (CN)")).toHaveValue(certName);
|
||||
await expect(page.locator("#nc-excluded")).toHaveValue("DNS:.bad.example");
|
||||
await page.getByRole("button", { name: "Validate extensions" }).click();
|
||||
await expect(page.getByText("Extension preview")).toBeVisible();
|
||||
await expect(page.locator("pre.pem")).toContainText("Name Constraints");
|
||||
await expect(page.locator("pre.pem")).toContainText("Certificate Policies");
|
||||
|
||||
await page.getByRole("button", { name: "Create certificate" }).click();
|
||||
await expect(page.getByRole("heading", { name: certName })).toBeVisible();
|
||||
const certApiUrl = page.url().replace(/\/vaults\//, "/api/vaults/");
|
||||
const detail = await page.evaluate(async (url) => {
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
}, certApiUrl);
|
||||
const extensions = detail.extension_spec;
|
||||
expect(extensions.crl_distribution_points).toEqual({ critical: true, urls: crlUrls });
|
||||
expect(extensions.name_constraints).toEqual({
|
||||
critical: true,
|
||||
permitted: [
|
||||
{ type: "dns", value: ".example.com" },
|
||||
{ type: "dns", value: "example.net" },
|
||||
],
|
||||
excluded: [{ type: "dns", value: ".bad.example" }],
|
||||
});
|
||||
expect(extensions.certificate_policies).toEqual({
|
||||
critical: true,
|
||||
policies: [{ oid: "1.3.6.1.4.1.1", cps: cpsUrls }],
|
||||
});
|
||||
|
||||
// The shared advanced-extension component also works in template forms.
|
||||
await page.goto(`${vaultUrl}/templates/new`);
|
||||
await expect(page.getByRole("heading", { name: "New template" })).toBeVisible();
|
||||
dialog = await openEditor(page, "#edit-crl-urls", "CRL distribution point URLs");
|
||||
await addRow(dialog, "URI", "https://template.example.com/root.crl");
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#crl-urls")).toHaveValue("https://template.example.com/root.crl");
|
||||
});
|
||||
Reference in New Issue
Block a user