Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions web/package/cockpit-agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Mon Jun 12 11:13:41 UTC 2023 - David Diaz <dgonzalez@suse.com>

- UI: Makes more evident when there is none authentication method
defined for the root user (gh#openSUSE/agama#615).

-------------------------------------------------------------------
Fri Jun 9 09:38:05 UTC 2023 - David Diaz <dgonzalez@suse.com>

Expand Down
39 changes: 31 additions & 8 deletions web/src/components/users/RootAuthMethods.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,26 @@
*/

import React, { useState, useEffect } from "react";
import { Skeleton, Truncate } from "@patternfly/react-core";
import { Button, Skeleton, Truncate } from "@patternfly/react-core";
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import { Em, RowActions } from '~/components/core';
import { RootPasswordPopup, RootSSHKeyPopup } from '~/components/users';

import { useCancellablePromise } from "~/utils";
import { useInstallerClient } from "~/context/installer";

const MethodsNotDefined = ({ setPassword, setSSHKey }) => {
return (
<div className="stack">
<div className="bold">No root auth method defined yet</div>
Comment thread
dgdavid marked this conversation as resolved.
Outdated
<div>Please, define at least one root authentication method for being able to log into the system as administrative user.</div>
Comment thread
dgdavid marked this conversation as resolved.
Outdated
<div className="split">
<Button variant="primary" onClick={setPassword}>Set a password</Button>
<Button variant="secondary" onClick={setSSHKey}>Upload a SSH Public Key</Button>
Comment thread
dgdavid marked this conversation as resolved.
</div>
</div>
);
};
export default function RootAuthMethods() {
const { users: client } = useInstallerClient();
const { cancellablePromise } = useCancellablePromise();
Expand Down Expand Up @@ -65,10 +77,15 @@ export default function RootAuthMethods() {

const isSSHKeyDefined = sshKey !== "";

const openPasswordForm = () => setIsPasswordFormOpen(true);
const openSSHKeyForm = () => setIsSSHKeyFormOpen(true);
const closePasswordForm = () => setIsPasswordFormOpen(false);
const closeSSHKeyForm = () => setIsSSHKeyFormOpen(false);

const passwordActions = [
{
title: isPasswordDefined ? "Change" : "Set",
onClick: () => setIsPasswordFormOpen(true),
onClick: openPasswordForm

},
isPasswordDefined && {
Expand All @@ -81,7 +98,7 @@ export default function RootAuthMethods() {
const sshKeyActions = [
{
title: isSSHKeyDefined ? "Change" : "Set",
onClick: () => setIsSSHKeyFormOpen(true),
onClick: openSSHKeyForm
},
sshKey && {
title: "Discard",
Expand All @@ -100,9 +117,6 @@ export default function RootAuthMethods() {
);
}

const closePasswordForm = () => setIsPasswordFormOpen(false);
const closeSSHKeyForm = () => setIsSSHKeyFormOpen(false);

const PasswordLabel = () => {
return isPasswordDefined
? "Already set"
Expand All @@ -121,8 +135,12 @@ export default function RootAuthMethods() {
);
};

return (
<>
const Content = () => {
if (!isPasswordDefined && !isSSHKeyDefined) {
return <MethodsNotDefined setPassword={openPasswordForm} setSSHKey={openSSHKeyForm} />;
}

return (
<TableComposable variant="compact" gridBreakPoint="grid-md">
<Thead>
<Tr>
Expand All @@ -148,7 +166,12 @@ export default function RootAuthMethods() {
</Tr>
</Tbody>
</TableComposable>
);
};

return (
<>
<Content />
{ isPasswordFormOpen &&
<RootPasswordPopup
isOpen
Expand Down
57 changes: 52 additions & 5 deletions web/src/components/users/RootAuthMethods.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,50 @@ describe("when loading initial data", () => {
});

describe("when ready", () => {
it("renders a table holding available methods", async () => {
installerRender(<RootAuthMethods />);
describe("and none method is defined", () => {
Comment thread
dgdavid marked this conversation as resolved.
Outdated
it("renders a text inviting the user to define at least one", async () => {
installerRender(<RootAuthMethods />);

await screen.findByText("No root auth method defined yet");
screen.getByText(/at least one/);
});

it("renders buttons for setting either, a password or a SSH Public Key", async () => {
installerRender(<RootAuthMethods />);

await screen.findByRole("button", { name: "Set a password" });
screen.getByRole("button", { name: "Upload a SSH Public Key" });
});

it("allows setting the password", async () => {
const { user } = installerRender(<RootAuthMethods />);

const button = await screen.findByRole("button", { name: "Set a password" });
await user.click(button);

screen.getByRole("dialog", { name: "Set a root password" });
});

it("allows setting the SSH Public Key", async () => {
const { user } = installerRender(<RootAuthMethods />);

const table = await screen.findByRole("grid");
within(table).getByText("Password");
within(table).getByText("SSH Key");
const button = await screen.findByRole("button", { name: "Upload a SSH Public Key" });
await user.click(button);

screen.getByRole("dialog", { name: "Add a SSH Public Key for root" });
});
});

describe("and at least one method is already defined", () => {
beforeEach(() => isRootPasswordSetFn.mockResolvedValue(true));

it("renders a table with available methods", async () => {
installerRender(<RootAuthMethods />);

const table = await screen.findByRole("grid");
within(table).getByText("Password");
within(table).getByText("SSH Key");
});
});

describe("and the password has been set", () => {
Expand Down Expand Up @@ -132,6 +170,9 @@ describe("when ready", () => {
});

describe("but the password is not set yet", () => {
// Mock another auth method for reaching the table
beforeEach(() => getRootSSHKeyFn.mockResolvedValue("Fake"));

it("renders the 'Not set' status", async () => {
installerRender(<RootAuthMethods />);

Expand Down Expand Up @@ -226,6 +267,9 @@ describe("when ready", () => {
});

describe("but the SSH Key is not set yet", () => {
// Mock another auth method for reaching the table
beforeEach(() => isRootPasswordSetFn.mockResolvedValue(true));

it("renders the 'Not set' status", async () => {
installerRender(<RootAuthMethods />);

Expand Down Expand Up @@ -266,6 +310,9 @@ describe("when ready", () => {
});

describe("and user settings changes", () => {
// Mock an auth method for reaching the table
beforeEach(() => isRootPasswordSetFn.mockResolvedValue(true));

it("updates the UI accordingly", async () => {
const [mockFunction, callbacks] = createCallbackMock();
onUsersChangeFn = mockFunction;
Expand Down