CEC-3301, CEC-3317 Magna security dll and remote commands (#249)

* CEC-3301, CEC-3317 Magna security dll and remote commands

* Fix test
This commit is contained in:
John Wu
2022-12-12 10:59:30 -08:00
committed by GitHub
parent 8e1b01b651
commit 2ec340efc5
28 changed files with 644 additions and 71 deletions

View File

@@ -8,17 +8,14 @@ jest.mock("../../services/vehiclesAPI");
jest.mock("../../services/superset")
import {
act,
render,
screen,
cleanup,
waitFor,
waitForElementToBeRemoved,
act, cleanup, render,
screen, waitFor,
waitForElementToBeRemoved
} from "@testing-library/react";
import { setToken } from "../Contexts/UserContext";
import {TEST_AUTH_OBJECT, TEST_AUTH_OBJECT_FISKER} from "../../utils/testing";
import App from ".";
import addSnapshotSerializer from "../../utils/snapshot";
import { TEST_AUTH_OBJECT_FISKER, TEST_AUTH_OBJECT_MAGNA } from "../../utils/testing";
import { setToken } from "../Contexts/UserContext";
const LOADING_STATUS = "Loading...";
@@ -113,9 +110,14 @@ describe("App", () => {
await check("/tools/sms/send", "span.MuiButton-label", "Sign In");
});
it("Route /tools/security-dll unauthenticated", async () => {
await check("/tools/security-dll", "span.MuiButton-label", "Sign In");
});
it("Route /page-not-found unauthenticated", async () => {
await check("/page-not-found", "h1", "Page Not Found");
});
it("Route / authenticated", async () => {
setToken(TEST_AUTH_OBJECT_FISKER);
await sleepAndCheck("/", "h6", "Home");
@@ -170,4 +172,9 @@ describe("App", () => {
setToken(TEST_AUTH_OBJECT_FISKER);
await sleepAndCheck("/tools/sms/send", "h6", "Send SMS");
});
it("Route /tools/security-dll authenticated", async () => {
setToken(TEST_AUTH_OBJECT_MAGNA);
await sleepAndCheck("/tools/security-dll", "h6", "Security.dll Download");
});
});

View File

@@ -5233,6 +5233,362 @@ exports[`App Route /tools/certificates/add unauthenticated 1`] = `
</div>
`;
exports[`App Route /tools/security-dll authenticated 1`] = `
<div>
<div
data-testid="mocked-userprovider"
>
<div
class="makeStyles-root-0"
>
<header
class="MuiPaper-root MuiAppBar-root MuiAppBar-positionFixed MuiAppBar-colorPrimary makeStyles-appBar-0 makeStyles-appBarShift-0 mui-fixed MuiPaper-elevation4"
>
<div
class="MuiToolbar-root MuiToolbar-regular MuiToolbar-gutters"
>
<div>
<h6
class="MuiTypography-root MuiTypography-h6 MuiTypography-noWrap"
>
Security.dll Download
</h6>
<nav
aria-label="breadcrumb"
class="MuiTypography-root MuiBreadcrumbs-root MuiTypography-body1 MuiTypography-colorInherit"
style="font-size: 10px;"
>
<ol
class="MuiBreadcrumbs-ol"
>
<li
class="MuiBreadcrumbs-li"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorInherit"
href="/tools/security-dll"
>
Tools
</a>
</li>
<li
aria-hidden="true"
class="MuiBreadcrumbs-separator"
>
/
</li>
<li
class="MuiBreadcrumbs-li"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorInherit"
href="/tools/security-dll"
>
Security.dll Download
</a>
</li>
</ol>
</nav>
</div>
<div
class="makeStyles-rightToolbar-0"
color="inherit"
>
<button
aria-controls="fade-menu"
aria-haspopup="true"
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
tabindex="0"
type="button"
>
<span
class="MuiButton-label"
>
Human
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</header>
<div
class="MuiDrawer-root MuiDrawer-docked makeStyles-drawer-0"
>
<div
class="MuiPaper-root MuiDrawer-paper makeStyles-drawerPaper-0 MuiDrawer-paperAnchorLeft MuiDrawer-paperAnchorDockedLeft MuiPaper-elevation0"
>
<div
class="makeStyles-drawerHeader-0 makeStyles-drawerHeaderLogo-0"
>
<img
alt="Fisker Admin Portal"
class="makeStyles-logo-0"
src="fisker-badge.svg"
/>
</div>
<hr
class="MuiDivider-root"
/>
<ul
class="MuiList-root MuiList-padding"
>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/home"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Home
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/packages"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Deployments
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/vehicles"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Vehicles
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/certificates/add"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Tools
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</span>
<ul
style="margin-left: 50px;"
>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/certificates/add"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Certificate
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
</div>
<main
class="makeStyles-content-0 makeStyles-contentShift-0"
>
<div
class="makeStyles-drawerHeader-0"
/>
<main
class="MuiContainer-root MuiContainer-maxWidthLg"
>
<h3>
Generating certificates...
</h3>
</main>
</main>
</div>
</div>
</div>
`;
exports[`App Route /tools/security-dll unauthenticated 1`] = `
<div>
<div
data-testid="mocked-userprovider"
>
<div
class="makeStyles-root-0"
>
<header
class="MuiPaper-root MuiAppBar-root MuiAppBar-positionFixed MuiAppBar-colorPrimary makeStyles-appBar-0 mui-fixed MuiPaper-elevation4"
>
<div
class="MuiToolbar-root MuiToolbar-regular MuiToolbar-gutters"
>
<div>
<h6
class="MuiTypography-root MuiTypography-h6 MuiTypography-noWrap"
/>
</div>
</div>
</header>
<main
class="makeStyles-content-0"
>
<div
class="makeStyles-drawerHeader-0"
/>
<main
class="MuiContainer-root MuiContainer-maxWidthLg"
>
<div
class="makeStyles-paper-0 makeStyles-textJustifyAlign-0"
>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary"
href="https://cognito.com/authorize?redirect=https://example.com/callback"
tabindex="0"
>
<span
class="MuiButton-label"
>
Sign In
</span>
<span
class="MuiTouchRipple-root"
/>
</a>
<p>
<strong>
Note: Your email address will be used as the user id
</strong>
</p>
</div>
</main>
</main>
</div>
</div>
</div>
`;
exports[`App Route /tools/sms/send authenticated 1`] = `
<div>
<div

View File

@@ -44,7 +44,6 @@ const TabViews = [
{
label: "Remote Commands",
component: RemoteCommandsTab,
rolesPerProvider: Permissions.FiskerRead,
},
{
label: "Fleets",

View File

@@ -0,0 +1,38 @@
import React, { useContext, useState } from "react";
import api from "../../services/suppliersAPI";
const KeygenContext = React.createContext();
export const KeygenProvider = ({ children }) => {
const [busy, setBusy] = useState(false);
const [securityCerts, setSecurityCerts] = useState(null);
const generateSecurityCerts = async (token) => {
setBusy(true);
try {
const data = await api.getManufactureCert(token);
if (data.error) throw new Error(data.message);
setSecurityCerts(data);
} finally {
setBusy(false);
}
};
return (
<KeygenContext.Provider
value={{
busy,
securityCerts,
generateSecurityCerts,
}}
>
{children}
</KeygenContext.Provider>
);
};
export const useKeygenContext = () => useContext(KeygenContext);

View File

@@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
const DownloadFileLink = ({ data, filename, mimetype }) => {
const [link, setLink] = useState("");
const releaseLink = () => {
if (link === "") return;
URL.revokeObjectURL(link);

View File

@@ -62,6 +62,11 @@ const menuData = [
to: "/tools/certificates/add",
rolesPerProvider: Permissions.FiskerCertificate,
},
{
label: "Security.dll",
to: "/tools/security-dll",
rolesPerProvider: Permissions.Magna,
},
{
label: "SMS",
to: "/tools/sms/send",

View File

@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Magna Security DLL page Security DLL Result page 1`] = `
<div>
<form>
<p>
Click to download your certificates and security.dll.
<br />
<strong>
DLL will not work unless certificate.pem and key.pem are placed next to the DLL
</strong>
</p>
<ul>
<li>
<a
download="certificate.pem"
>
certificate.pem
</a>
</li>
<li>
<a
download="key.pem"
>
key.pem
</a>
</li>
<li>
<a
href="https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll"
>
security.dll 32-bit
</a>
</li>
<li>
<a
href="https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll"
>
security.dll 64-bit
</a>
</li>
</ul>
</form>
</div>
`;

View File

@@ -0,0 +1,47 @@
import React, { useEffect } from "react";
import { KeygenProvider, useKeygenContext } from "../../Contexts/KeygenContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import Result from "./result";
const MainForm = () => {
const { token: { idToken: { jwtToken: token } } } = useUserContext();
const { generateSecurityCerts, securityCerts } = useKeygenContext();
const { setTitle, setSitePath, setMessage } = useStatusContext();
const getCert = async () => {
try {
await generateSecurityCerts(token);
} catch (e) {
setMessage(e.message);
}
};
useEffect(() => {
setTitle("Security.dll Download");
setSitePath([
{
label: `Tools`,
},
{
label: "Security.dll Download",
},
])
getCert();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
if (securityCerts) {
return <Result {...securityCerts} />
}
return <h3>Generating certificates...</h3>
};
const SecurityDLL = () => (
<KeygenProvider>
<MainForm/>
</KeygenProvider>
);
export default SecurityDLL;

View File

@@ -0,0 +1,21 @@
import { render } from "@testing-library/react";
import React from "react";
import addSnapshotSerializer from "../../../utils/snapshot";
import Result from "./result";
describe("Magna Security DLL page", () => {
beforeAll(() => {
global.URL.createObjectURL = jest.fn();
global.URL.revokeObjectURL = jest.fn();
addSnapshotSerializer(expect);
});
it("Security DLL Result page", () => {
const result = {public_key:"-----BEGIN CERTIFICATE-----\nPUBLIC_KEY\n-----END CERTIFICATE-----",private_key:"-----BEGIN RSA PRIVATE KEY-----\nPRIVATE_KEY\n-----END RSA PRIVATE KEY-----",serial_number:"77:49:34:9d:be:f6:59:03:b4:1c:63:84:07:5b:2a:8a:21:0a:c5:9e",type:"rsa"}
const { container } = render(<Result {...result} />);
expect(container).toMatchSnapshot();
})
})

View File

@@ -0,0 +1,36 @@
import React from "react";
import { SECURITY_DLL_64_URL, SECURITY_DLL_URL } from "../../../services/securityDLL";
import DownloadFileLink from "../../Controls/DownloadFileLink";
const CertMimeType = "application/x-pem-file";
const Result = ({ public_key, private_key }) => (
<>
<form>
<p>Click to download your certificates and security.dll.<br /><strong>DLL will not work unless certificate.pem and key.pem are placed next to the DLL</strong></p>
<ul>
<li>
<DownloadFileLink
data={public_key}
filename="certificate.pem"
mimetype={CertMimeType} />
</li>
<li>
<DownloadFileLink
data={private_key}
filename="key.pem"
mimetype={CertMimeType} />
</li>
<li>
<a href={SECURITY_DLL_URL}>security.dll 32-bit</a>
</li>
<li>
<a href={SECURITY_DLL_64_URL}>security.dll 64-bit</a>
</li>
</ul>
</form>
</>
);
export default Result;

View File

@@ -34,6 +34,7 @@ const SSOForm = React.lazy(() => import("../SSOForm"));
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
const VehicleUpdateForm = React.lazy(() => import("../Cars/Update"));
const CertificateCreate = React.lazy(() => import("../Certificates/Add"));
const SecurityDLL = React.lazy(() => import("../Magna/SecurityDLL"));
const SMSSend = React.lazy(() => import("../SMS/Send"));
const SuppliersList = React.lazy(() => import("../Suppliers/List"));
const SupplierDetails = React.lazy(() => import("../Suppliers/Details"));
@@ -228,6 +229,15 @@ const SiteRoutes = () => {
rolesPerGroup={Permissions.FiskerMagnaCertificate}
providers={providers}
/>
<AuthRoute
path="/tools/security-dll"
render={() => <SecurityDLL />}
type={TYPES.PROTECTED}
token={token}
groups={groups}
rolesPerGroup={Permissions.Magna}
providers={providers}
/>
<AuthRoute
path="/tools/sms/send"
render={() => <SMSSend />}