Просмотр исходного кода

Mask encrypted disks passphrase in transfer UI

When transferring VMs with encrypted drives, the
Encrypted Disks Passphrase field displays as a password input in the
configuration form but appears as plain text in the transfer details view.

Fix by adding `encrypted_disks_passphrase` to the `passwordFields` config so
it's treated as a sensitive field across all UI components.

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu 1 месяц назад
Родитель
Сommit
84649d5699

+ 1 - 0
config.ts

@@ -220,6 +220,7 @@ const conf: Config = {
     "private_key_passphrase",
     "secret_access_key",
     "client_secret",
+    "encrypted_disks_passphrase",
   ],
 
   // The number of items per page applicable to default lists:

+ 41 - 0
src/components/modules/TransferModule/MainDetails/MainDetails.spec.tsx

@@ -32,6 +32,19 @@ jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
   default: (props: any) => <div>{props.endpoint}</div>,
 }));
 jest.mock("react-router", () => ({ Link: "a" }));
+jest.mock("@src/utils/Config", () => ({
+  __esModule: true,
+  default: {
+    config: {
+      passwordFields: [
+        "private_key_passphrase",
+        "secret_access_key",
+        "client_secret",
+        "encrypted_disks_passphrase",
+      ],
+    },
+  },
+}));
 
 describe("MainDetails", () => {
   let defaultProps: MainDetails["props"];
@@ -104,4 +117,32 @@ describe("MainDetails", () => {
       getByText(TRANSFER_MOCK.destination_environment.password),
     ).toBeTruthy();
   });
+
+  it("masks encrypted disks passphrase", () => {
+    const { queryByText } = render(<MainDetails {...defaultProps} />);
+    const actualPassphrase =
+      TRANSFER_MOCK.destination_environment.encrypted_disks_passphrase;
+    expect(queryByText(actualPassphrase)).toBeFalsy();
+    const passwordEls = TestUtils.selectAll("PasswordValue__Wrapper");
+    expect(passwordEls.length).toBeGreaterThan(1);
+  });
+
+  it("shows encrypted disks passphrase when clicked", async () => {
+    const { getByText } = render(<MainDetails {...defaultProps} />);
+    const passwordEls = TestUtils.selectAll("PasswordValue__Wrapper");
+    expect(passwordEls.length).toBeGreaterThan(1);
+
+    const passphrasePasswordEl = passwordEls[passwordEls.length - 1];
+    expect(passphrasePasswordEl.textContent).toBe("•••••••••");
+
+    await act(async () => {
+      passphrasePasswordEl.click();
+    });
+
+    expect(
+      getByText(
+        TRANSFER_MOCK.destination_environment.encrypted_disks_passphrase,
+      ),
+    ).toBeTruthy();
+  });
 });

+ 8 - 3
src/components/modules/TransferModule/MainDetails/MainDetails.tsx

@@ -29,6 +29,7 @@ import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
 import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
 import DateUtils from "@src/utils/DateUtils";
 import LabelDictionary from "@src/utils/LabelDictionary";
+import configLoader from "@src/utils/Config";
 
 import arrowImage from "./images/arrow.svg";
 
@@ -249,7 +250,7 @@ class MainDetails extends React.Component<Props, State> {
       if (value && value.join) {
         value.forEach((v: any, i: number) => {
           const useLabel = i === 0 ? label : "";
-          properties.push({ label: useLabel, value: v });
+          properties.push({ label: useLabel, value: v, name: pn });
         });
       } else if (value && typeof value === "object") {
         properties = properties.concat(
@@ -263,11 +264,12 @@ class MainDetails extends React.Component<Props, State> {
             return {
               label: `${label} - ${LabelDictionary.get(p)}`,
               value: getValue(p, value[p]),
+              name: p,
             };
           }),
         );
       } else {
-        properties.push({ label, value: getValue(pn, value) });
+        properties.push({ label, value: getValue(pn, value), name: pn });
       }
     });
 
@@ -280,7 +282,10 @@ class MainDetails extends React.Component<Props, State> {
             <PropertyRow key={prop.label}>
               <PropertyName>{prop.label}</PropertyName>
               <PropertyValue>
-                {prop.label.toLowerCase().indexOf("password") > -1 &&
+                {(prop.label.toLowerCase().indexOf("password") > -1 ||
+                  configLoader.config.passwordFields.find(
+                    f => f === prop.name,
+                  )) &&
                 !this.state.showPassword.find(
                   f => f === `${prop.label}-${type}`,
                 ) ? (

+ 15 - 6
src/components/modules/TransferModule/TransferDetailsContent/ReplicaDetailsContent.spec.tsx

@@ -43,13 +43,22 @@ jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
 }));
 jest.mock("react-router", () => ({ Link: "a" }));
 jest.mock("@src/utils/Config", () => ({
-  config: {
-    providerSortPriority: {},
-    providerNames: {
-      openstack: "OpenStack",
-      vmware_vsphere: "VMware vSphere",
+  __esModule: true,
+  default: {
+    config: {
+      providerSortPriority: {},
+      providerNames: {
+        openstack: "OpenStack",
+        vmware_vsphere: "VMware vSphere",
+      },
+      providersDisabledExecuteOptions: ["metal"],
+      passwordFields: [
+        "private_key_passphrase",
+        "secret_access_key",
+        "client_secret",
+        "encrypted_disks_passphrase",
+      ],
     },
-    providersDisabledExecuteOptions: ["metal"],
   },
 }));
 jest.mock("@src/components/modules/TransferModule/Schedule", () => ({

+ 1 - 0
tests/mocks/TransferMock.ts

@@ -38,6 +38,7 @@ export const TRANSFER_MOCK: TransferItem = {
       disk_mappings: {},
     },
     password: "password-value",
+    encrypted_disks_passphrase: "password-encrypted-value",
   },
   source_environment: {},
   transfer_result: {