diff --git a/rfirma-app/po/ca.po b/rfirma-app/po/ca.po index 00223e58..b51258b4 100644 --- a/rfirma-app/po/ca.po +++ b/rfirma-app/po/ca.po @@ -1052,6 +1052,9 @@ msgstr "Fitxer instal·lat" msgid "status.storeBrands.windows" msgstr "Magatzem de Windows" +msgid "status.storeBrands.macos" +msgstr "Magatzem de macOS" + msgid "status.notices.restartFirefox" msgstr "Reinicia el Firefox perquè tingui efecte." diff --git a/rfirma-app/po/en.po b/rfirma-app/po/en.po index 28a455aa..b045a3f8 100644 --- a/rfirma-app/po/en.po +++ b/rfirma-app/po/en.po @@ -1052,6 +1052,9 @@ msgstr "Installed file" msgid "status.storeBrands.windows" msgstr "Windows store" +msgid "status.storeBrands.macos" +msgstr "macOS store" + msgid "status.notices.restartFirefox" msgstr "Restart Firefox for this to take effect." diff --git a/rfirma-app/po/es.po b/rfirma-app/po/es.po index ebf068e7..887a98ea 100644 --- a/rfirma-app/po/es.po +++ b/rfirma-app/po/es.po @@ -1052,6 +1052,9 @@ msgstr "Fichero instalado" msgid "status.storeBrands.windows" msgstr "Almacén de Windows" +msgid "status.storeBrands.macos" +msgstr "Almacén de macOS" + msgid "status.notices.restartFirefox" msgstr "Reinicia Firefox para que surta efecto." diff --git a/rfirma-app/po/eu.po b/rfirma-app/po/eu.po index 87e6be35..00a4e188 100644 --- a/rfirma-app/po/eu.po +++ b/rfirma-app/po/eu.po @@ -1053,6 +1053,9 @@ msgstr "Instalatutako fitxategia" msgid "status.storeBrands.windows" msgstr "Windows biltegia" +msgid "status.storeBrands.macos" +msgstr "macOS biltegia" + msgid "status.notices.restartFirefox" msgstr "Berrabiarazi Firefox eragina izan dezan." diff --git a/rfirma-app/po/gl.po b/rfirma-app/po/gl.po index 9faa93a9..a080228c 100644 --- a/rfirma-app/po/gl.po +++ b/rfirma-app/po/gl.po @@ -1050,6 +1050,9 @@ msgstr "Ficheiro instalado" msgid "status.storeBrands.windows" msgstr "Almacén de Windows" +msgid "status.storeBrands.macos" +msgstr "Almacén de macOS" + msgid "status.notices.restartFirefox" msgstr "Reinicia Firefox para que xurda efecto." diff --git a/rfirma-app/po/messages.pot b/rfirma-app/po/messages.pot index a2eae7ab..934330c3 100644 --- a/rfirma-app/po/messages.pot +++ b/rfirma-app/po/messages.pot @@ -1037,6 +1037,9 @@ msgstr "" msgid "status.storeBrands.windows" msgstr "" +msgid "status.storeBrands.macos" +msgstr "" + msgid "status.notices.restartFirefox" msgstr "" diff --git a/rfirma-app/src/setup/SetupWizard.presentation.test.tsx b/rfirma-app/src/setup/SetupWizard.presentation.test.tsx index cf51d47f..33f328e6 100644 --- a/rfirma-app/src/setup/SetupWizard.presentation.test.tsx +++ b/rfirma-app/src/setup/SetupWizard.presentation.test.tsx @@ -1,5 +1,6 @@ import { composeStories } from "@storybook/react-vite"; import { screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { describe, expect, it } from "vitest"; import { renderWithCatalog } from "../testing/render"; import * as stories from "./SetupWizard.stories"; @@ -13,6 +14,7 @@ const { CertificateDoneHandlerPending, WithoutAutoFirma, ProtectionOff, + CertificateFailedOnMacos, } = composeStories(stories); const stepButtons = ["Instalar", "Usar rFirma", "Ahora no"]; @@ -99,3 +101,13 @@ describe("the protection against accidental signing", () => { expect(await screen.findByRole("switch")).not.toBeChecked(); }); }); + +describe("the certificate step on macOS", () => { + it("names the macOS store among the places where the install failed", async () => { + renderWithCatalog(); + + await userEvent.click(await screen.findByRole("button", { name: "Instalar" })); + + expect(await screen.findByText("Almacén de macOS")).toBeInTheDocument(); + }); +}); diff --git a/rfirma-app/src/setup/SetupWizard.stories.tsx b/rfirma-app/src/setup/SetupWizard.stories.tsx index 69b0a3f4..c490dba3 100644 --- a/rfirma-app/src/setup/SetupWizard.stories.tsx +++ b/rfirma-app/src/setup/SetupWizard.stories.tsx @@ -1,13 +1,14 @@ //! Las historias del asistente del primer arranque: la bienvenida y la configuración con cada estado de sus dos pasos. import type { Meta, StoryObj } from "@storybook/react-vite"; -import { fn } from "storybook/test"; +import { expect, fn, userEvent, within } from "storybook/test"; import { inMemoryPreferences } from "../preferences/preferences"; import { defaults } from "../preferences/testing/fixtures"; import { memoryStatus } from "../status/status"; import { SetupWizard } from "./SetupWizard"; import { aVersionRow, + certificateFailedOnMacos, certificateInstalled, certificateNotInstalled, handlerNotOurs, @@ -85,6 +86,22 @@ export const WithoutAutoFirma: Story = { }, }; +export const CertificateFailedOnMacos: Story = { + name: "2 · Configuración, certificado sin instalar en macOS", + args: { + initialStep: 2, + statusPort: { + ...memoryStatus([aVersionRow, certificateNotInstalled, handlerNotOurs]), + installLocalCaCertificate: async () => certificateFailedOnMacos, + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Instalar" })); + await expect(await canvas.findByText("Almacén de macOS")).toBeVisible(); + }, +}; + export const ProtectionOff: Story = { name: "2 · Configuración, sin la espera de la sede", args: { diff --git a/rfirma-app/src/setup/testing/fixtures.ts b/rfirma-app/src/setup/testing/fixtures.ts index 568c30f5..a24c2848 100644 --- a/rfirma-app/src/setup/testing/fixtures.ts +++ b/rfirma-app/src/setup/testing/fixtures.ts @@ -28,6 +28,12 @@ export const certificateInstalled: SignalRow = { action: null, }; +export const certificateFailedOnMacos: SignalRow = { + ...certificateNotInstalled, + verdict: "incorrect", + detail: { kind: "trust", stores: [{ brand: "macos", trusted: false }] }, +}; + export const handlerNotOurs: SignalRow = { signal: "siteSignature", value: "AutoFirma", diff --git a/rfirma-app/src/signing/CertificateCard.tsx b/rfirma-app/src/signing/CertificateCard.tsx index 4b201392..59c2d359 100644 --- a/rfirma-app/src/signing/CertificateCard.tsx +++ b/rfirma-app/src/signing/CertificateCard.tsx @@ -67,6 +67,8 @@ export function storeLabel(store: Store, t: TFunction): string { return t("panel.certificate.stores.installed"); case "windows": return t("status.storeBrands.windows"); + case "macos": + return t("status.storeBrands.macos"); } } diff --git a/rfirma-app/src/signing/CertificateSelect.stories.tsx b/rfirma-app/src/signing/CertificateSelect.stories.tsx index 9dfc2a79..86791228 100644 --- a/rfirma-app/src/signing/CertificateSelect.stories.tsx +++ b/rfirma-app/src/signing/CertificateSelect.stories.tsx @@ -5,6 +5,7 @@ import { fn } from "storybook/test"; import { CertificateSelect } from "./CertificateSelect"; import { ENTITY_CERTIFICATE, + MACOS_CERTIFICATE, PERSONAL_CERTIFICATE, STORY_CERTIFICATES, } from "./testing/storyReports"; @@ -53,4 +54,12 @@ export const Searching: Story = { args: { certificates: [], chosen: null, search export const Disabled: Story = { args: { disabled: true } }; +export const OpenWithMacosStore: Story = { + args: { + certificates: [MACOS_CERTIFICATE, PERSONAL_CERTIFICATE], + chosen: MACOS_CERTIFICATE, + defaultOpen: true, + }, +}; + export const Open: Story = { args: { defaultOpen: true } }; diff --git a/rfirma-app/src/signing/CertificateSelect.test.tsx b/rfirma-app/src/signing/CertificateSelect.test.tsx index eaa55da9..2478cbc4 100644 --- a/rfirma-app/src/signing/CertificateSelect.test.tsx +++ b/rfirma-app/src/signing/CertificateSelect.test.tsx @@ -247,6 +247,16 @@ describe("CertificateSelect", () => { expect(within(row(0)).getByText("Tarjeta")).toBeVisible(); }); + it("names the macOS personal store as the macOS store", async () => { + renderSelect({ + certificates: [aCertificate({ id: "macos", stores: ["macos"] })], + }); + + await userEvent.click(box()); + + expect(within(row(0)).getByText("Almacén de macOS")).toBeVisible(); + }); + it("tags a DNIe certificate as «DNIe» and a certificate on any other card as «Tarjeta»", async () => { renderSelect({ certificates: [ diff --git a/rfirma-app/src/signing/certificate.ts b/rfirma-app/src/signing/certificate.ts index 1ad27b8c..af911cd4 100644 --- a/rfirma-app/src/signing/certificate.ts +++ b/rfirma-app/src/signing/certificate.ts @@ -55,6 +55,7 @@ type CertificateStoreClass = | "nssdb" | "installed" | "windows" + | "macos" | BrowserStoreMark; /** Un certificado elegible, con lo justo para pintarlo y para firmar con él. */ diff --git a/rfirma-app/src/signing/testing/storyReports.ts b/rfirma-app/src/signing/testing/storyReports.ts index 99fbdb33..6afc7236 100644 --- a/rfirma-app/src/signing/testing/storyReports.ts +++ b/rfirma-app/src/signing/testing/storyReports.ts @@ -181,6 +181,15 @@ const NOT_YET_VALID_CERTIFICATE = aCertificate({ status: { kind: "notYetValid", notBefore: IN_2099 }, }); +export const MACOS_CERTIFICATE = aCertificate({ + id: "macos", + holderName: "BABBAGE CHARLES", + givenName: "Charles", + surname: "Babbage", + idNumber: "IDCES-00000002W", + stores: ["macos"], +}); + export const STORY_CERTIFICATES: readonly Certificate[] = [ PERSONAL_CERTIFICATE, ENTITY_CERTIFICATE, diff --git a/rfirma-app/src/status/StatusView.presentation.test.tsx b/rfirma-app/src/status/StatusView.presentation.test.tsx index 2c0ddd3e..9d6b8a6d 100644 --- a/rfirma-app/src/status/StatusView.presentation.test.tsx +++ b/rfirma-app/src/status/StatusView.presentation.test.tsx @@ -118,6 +118,11 @@ const table: Record = { buttons: [], absent: ["No aplica"], }, + UserCertificatesOnMacos: { + texts: ["Almacén de macOS", "Tarjeta"], + buttons: ["Ver dónde"], + absent: ["Almacén de Windows"], + }, UserCertificatesDetailExpanded: { texts: [ "Almacén de Windows", diff --git a/rfirma-app/src/status/StatusView.stories.tsx b/rfirma-app/src/status/StatusView.stories.tsx index 5a2d7d5d..a764bdda 100644 --- a/rfirma-app/src/status/StatusView.stories.tsx +++ b/rfirma-app/src/status/StatusView.stories.tsx @@ -21,6 +21,7 @@ import { sitesUnavailableOutsideFlatpak, sitesWithTwoCandidates, someUserCertificates, + someUserCertificatesOnMacos, versionChecking, versionOutdated, versionUpToDate, @@ -122,6 +123,10 @@ export const CardReaderMissing = panel([cardReaderMissing]); export const CardReaderNotSupported = panel([cardReaderNotSupported]); +export const UserCertificatesOnMacos = panel([someUserCertificatesOnMacos], { + initiallyExpanded: ["userCertificates"], +}); + export const UserCertificatesDetailExpanded = panel([someUserCertificates], { initiallyExpanded: ["userCertificates"], }); diff --git a/rfirma-app/src/status/status.ts b/rfirma-app/src/status/status.ts index 703770a9..bcc5aa34 100644 --- a/rfirma-app/src/status/status.ts +++ b/rfirma-app/src/status/status.ts @@ -21,7 +21,8 @@ export type StoreBrand = | "nssdb" | "card" | "installed" - | "windows"; + | "windows" + | "macos"; export interface StoreDetail { brand: StoreBrand; @@ -44,6 +45,8 @@ export function storeBrandLabel(t: TFunction, brand: StoreBrand): string { return t("status.storeBrands.installed"); case "windows": return t("status.storeBrands.windows"); + case "macos": + return t("status.storeBrands.macos"); } } diff --git a/rfirma-app/src/status/testing/fixtures.ts b/rfirma-app/src/status/testing/fixtures.ts index 917c7c4a..46e1e0f3 100644 --- a/rfirma-app/src/status/testing/fixtures.ts +++ b/rfirma-app/src/status/testing/fixtures.ts @@ -148,6 +148,17 @@ export const someUserCertificates: SignalRow = { }, }; +export const someUserCertificatesOnMacos: SignalRow = { + ...someUserCertificates, + detail: { + kind: "certificates", + stores: [ + { brand: "macos", certificates: 2 }, + { brand: "card", certificates: 1 }, + ], + }, +}; + export const cardReaderDetected = cardReaderRow({ kind: "noCard" }); export const cardReaderMissing = cardReaderRow({ kind: "noReader" });