Added default group tab to user registration (#1965)

This commit is contained in:
Erik Jan de Wit 2022-02-09 12:46:58 +01:00 committed by GitHub
parent f9d3f9dfa7
commit 90199db346
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 386 additions and 14 deletions

View file

@ -0,0 +1,54 @@
import ListingPage from "../support/pages/admin_console/ListingPage";
import UserRegistration, {
GroupPickerDialog,
} from "../support/pages/admin_console/manage/realm_settings/UserRegistration";
import Masthead from "../support/pages/admin_console/Masthead";
import SidebarPage from "../support/pages/admin_console/SidebarPage";
import LoginPage from "../support/pages/LoginPage";
import AdminClient from "../support/util/AdminClient";
import {
keycloakBefore,
keycloakBeforeEach,
} from "../support/util/keycloak_hooks";
describe("Realm settings - User registration tab", () => {
const loginPage = new LoginPage();
const sidebarPage = new SidebarPage();
const masthead = new Masthead();
const adminClient = new AdminClient();
const listingPage = new ListingPage();
const groupPicker = new GroupPickerDialog();
const userRegistration = new UserRegistration();
const groupName = "The default group";
before(() => {
adminClient.createGroup(groupName);
keycloakBefore();
loginPage.logIn();
});
beforeEach(() => {
keycloakBeforeEach();
sidebarPage.goToRealmSettings();
userRegistration.goToTab();
});
after(() => adminClient.deleteGroups());
it("add default role", () => {
const role = "admin";
userRegistration.addRoleButtonClick();
userRegistration.selectRow(role).assign();
masthead.checkNotificationMessage("Associated roles have been added");
listingPage.searchItem(role, false).itemExist(role);
});
it("add default role", () => {
userRegistration.goToDefaultGroupTab().addDefaultGroupClick();
groupPicker.checkTitle("Add default groups").clickRow(groupName).clickAdd();
masthead.checkNotificationMessage("New group added to the default groups");
listingPage.itemExist(groupName);
});
});

View file

@ -0,0 +1,68 @@
export default class UserRegistration {
private userRegistrationTab = "rs-userRegistration-tab";
private defaultGroupTab = "#pf-tab-20-groups";
private addRoleButton = "add-role-button";
private addDefaultGroup = "no-default-groups-empty-action";
private namesColumn = 'td[data-label="Role name"]:visible';
private addBtn = "add-associated-roles-button";
goToTab() {
cy.findByTestId(this.userRegistrationTab).click();
return this;
}
goToDefaultGroupTab() {
cy.get(this.defaultGroupTab).click();
return this;
}
addRoleButtonClick() {
cy.findByTestId(this.addRoleButton).click();
return this;
}
addDefaultGroupClick() {
cy.findByTestId(this.addDefaultGroup).click();
return this;
}
selectRow(name: string) {
cy.get(this.namesColumn)
.contains(name)
.parent()
.within(() => {
cy.get("input").click();
});
return this;
}
assign() {
cy.findByTestId(this.addBtn).click();
return this;
}
}
export class GroupPickerDialog {
private addButton = "common:add-button";
private title = ".pf-c-modal-box__title";
clickRow(groupName: string) {
cy.findByTestId(groupName).within(() => cy.get("input").click());
return this;
}
clickRoot() {
cy.get(".pf-c-breadcrumb__item > button").click();
return this;
}
checkTitle(title: string) {
cy.get(this.title).should("have.text", title);
return this;
}
clickAdd() {
cy.findByTestId(this.addButton).click();
return this;
}
}

View file

@ -200,7 +200,7 @@ export const WebauthnPolicy = ({
<PageSection variant="light">
{enabled && (
<Popover bodyContent={t(`authentication-help:${namePrefix}FormHelp`)}>
<TextContent className="keycloak__webauthn_policies__intro">
<TextContent className="keycloak__section_intro__help">
<Text>
<QuestionCircleIcon /> {t("authentication-help:webauthnIntro")}
</Text>

View file

@ -1,9 +1,3 @@
.keycloak__webauthn_policies__intro {
padding: var(--pf-global--spacer--md) 0 var(--pf-global--spacer--lg);
color: var(--pf-global--primary-color--100);
width: fit-content;
}
@media (min-width: 768px) {
.keycloak__webauthn_policies_authentication__form .pf-c-form__group {
--pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 10rem;

View file

@ -36,6 +36,7 @@ import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { prettyPrintJSON } from "../../util";
import "./evaluate.css";
export type EvaluateScopesProps = {
@ -242,7 +243,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
<>
<PageSection variant="light">
{enabled && (
<TextContent className="keycloak__scopes_evaluate__intro">
<TextContent className="keycloak__section_intro__help">
<Text>
<QuestionCircleIcon /> {t("clients-help:evaluateExplain")}
</Text>

View file

@ -1,7 +1,3 @@
.keycloak__scopes_evaluate__intro {
padding: var(--pf-global--spacer--md) 0 var(--pf-global--spacer--lg);
color: var(--pf-global--primary-color--100);
}
.keycloak__scopes_evaluate__clipboard-copy input {
display: none;
}

View file

@ -59,3 +59,9 @@ td.pf-c-table__check > input[type="checkbox"] {
.kc-time-select-dropdown {
min-width: 170px;
}
.keycloak__section_intro__help {
padding: var(--pf-global--spacer--md) 0 var(--pf-global--spacer--lg);
color: var(--pf-global--primary-color--100);
width: fit-content;
}

View file

@ -0,0 +1,232 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import {
AlertVariant,
Button,
ButtonVariant,
Dropdown,
DropdownItem,
KebabToggle,
Popover,
Text,
TextContent,
ToolbarItem,
} from "@patternfly/react-core";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import useToggle from "../utils/useToggle";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { useAlerts } from "../components/alert/Alerts";
import { toUserFederation } from "../user-federation/routes/UserFederation";
import { useRealm } from "../context/realm-context/RealmContext";
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useHelp } from "../components/help-enabler/HelpHeader";
export const DefaultsGroupsTab = () => {
const { t } = useTranslation("realm-settings");
const [isKebabOpen, toggleKebab] = useToggle();
const [isGroupPickerOpen, toggleGroupPicker] = useToggle();
const [defaultGroups, setDefaultGroups] = useState<GroupRepresentation[]>();
const [selectedRows, setSelectedRows] = useState<GroupRepresentation[]>([]);
const [key, setKey] = useState(0);
const [load, setLoad] = useState(0);
const reload = () => setLoad(load + 1);
const adminClient = useAdminClient();
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const { enabled } = useHelp();
useFetch(
() => adminClient.realms.getDefaultGroups({ realm }),
(groups) => {
setDefaultGroups(groups);
setKey(key + 1);
},
[load]
);
const loader = () => Promise.resolve(defaultGroups!);
const removeGroup = async () => {
try {
await Promise.all(
selectedRows.map((group) =>
adminClient.realms.removeDefaultGroup({
realm,
id: group.id!,
})
)
);
addAlert(
t("groupRemove", { count: selectedRows.length }),
AlertVariant.success
);
setSelectedRows([]);
} catch (error) {
addError("realm-settings:groupRemoveError", error);
}
reload();
};
const addGroups = async (groups: GroupRepresentation[]) => {
try {
await Promise.all(
groups.map((group) =>
adminClient.realms.addDefaultGroup({
realm,
id: group.id!,
})
)
);
addAlert(
t("defaultGroupAdded", { count: groups.length }),
AlertVariant.success
);
} catch (error) {
addError("realm-settings:defaultGroupAddedError", error);
}
reload();
};
const [toggleRemoveDialog, RemoveDialog] = useConfirmDialog({
titleKey: t("removeConfirmTitle", { count: selectedRows.length }),
messageKey: t("removeConfirm", { count: selectedRows.length }),
continueButtonLabel: "common:delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: removeGroup,
});
if (!defaultGroups) {
return <KeycloakSpinner />;
}
return (
<>
<RemoveDialog />
{isGroupPickerOpen && (
<GroupPickerDialog
type="selectMany"
text={{
title: "realm-settings:addDefaultGroups",
ok: "common:add",
}}
onConfirm={(groups) => {
addGroups(groups);
toggleGroupPicker();
}}
onClose={toggleGroupPicker}
/>
)}
{enabled && (
<Popover
bodyContent={
<Trans i18nKey="realm-settings-help:defaultGroups">
{" "}
<Link to={toUserFederation({ realm })} />.
</Trans>
}
>
<TextContent
className="keycloak__section_intro__help"
style={{
paddingLeft: "var(--pf-c-page__main-section--PaddingLeft)",
}}
>
<Text>
<QuestionCircleIcon /> {t("whatIsDefaultGroups")}
</Text>
</TextContent>
</Popover>
)}
<KeycloakDataTable
key={key}
canSelectAll
onSelect={(rows) => setSelectedRows([...rows])}
loader={loader}
ariaLabelKey="realm-settings:defaultGroups"
searchPlaceholderKey="realm-settings:searchForGroups"
toolbarItem={
<>
<ToolbarItem>
<Button
data-testid="openCreateGroupModal"
variant="primary"
onClick={toggleGroupPicker}
>
{t("addGroups")}
</Button>
</ToolbarItem>
<ToolbarItem>
<Dropdown
toggle={
<KebabToggle
onToggle={toggleKebab}
isDisabled={selectedRows!.length === 0}
/>
}
isOpen={isKebabOpen}
isPlain
dropdownItems={[
<DropdownItem
key="action"
component="button"
onClick={() => {
toggleRemoveDialog();
toggleKebab();
}}
>
{t("common:remove")}
</DropdownItem>,
]}
/>
</ToolbarItem>
</>
}
actions={[
{
title: t("common:remove"),
onRowClick: (group: GroupRepresentation) => {
setSelectedRows([group]);
toggleRemoveDialog();
return Promise.resolve(false);
},
},
]}
columns={[
{
name: "name",
displayKey: "groups:groupName",
},
{
name: "path",
displayKey: "groups:path",
},
]}
emptyState={
<ListEmptyState
hasIcon
message={t("noDefaultGroups")}
instructions={
<Trans i18nKey="realm-settings:noDefaultGroupsInstructions">
{" "}
<Link to={toUserFederation({ realm })} />
Add groups...
</Trans>
}
primaryActionText={t("addGroups")}
onPrimaryAction={toggleGroupPicker}
/>
}
/>
</>
);
};

View file

@ -7,6 +7,7 @@ import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { AssociatedRolesTab } from "../realm-roles/AssociatedRolesTab";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { DefaultsGroupsTab } from "./DefaultGroupsTab";
export const UserRegistration = () => {
const { t } = useTranslation("realm-settings");
@ -29,11 +30,11 @@ export const UserRegistration = () => {
return (
<Tabs
key={key}
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
>
<Tab
key={key}
id="roles"
eventKey={10}
title={<TabTitleText>{t("defaultRoles")}</TabTitleText>}
@ -48,7 +49,7 @@ export const UserRegistration = () => {
eventKey={20}
title={<TabTitleText>{t("defaultGroups")}</TabTitleText>}
>
<h1>Work in progress</h1>
<DefaultsGroupsTab />
</Tab>
</Tabs>
);

View file

@ -143,5 +143,7 @@ export default {
"The condition checks the role of the entity who tries to create/update the client to determine whether the policy is applied.",
clientUpdaterSourceRolesTooltip:
"The condition is checked during client registration/update requests and it evaluates to true if the entity (usually user), who is creating/updating client is member of the specified role. For reference the realm role, you can use the realm role name like 'my_realm_role' . For reference client role, you can use the client_id.role_name for example 'my_client.my_client_role' will refer to client role 'my_client_role' of client 'my_client'. ",
defaultGroups:
"Default groups allow you to automatically assign groups membership whenever any new user is created or imported through <1>identity brokering</1>.",
},
};

View file

@ -726,6 +726,24 @@ export default {
"You can edit the supported locales. If you haven't selected supported locales yet, you can only edit the English locale.",
defaultRoles: "Default roles",
defaultGroups: "Default groups",
whatIsDefaultGroups: "What is the function of default groups?",
searchForGroups: "Search group",
addDefaultGroups: "Add default groups",
removeConfirmTitle_one: "Remove group?",
removeConfirmTitle_other: "Remove groups?",
removeConfirm_one: "Are you sure you want to remove this group",
removeConfirm_other: "Are you sure you want to remove these groups.",
groupRemove_one: "Group removed",
groupRemove_other: "Groups removed",
groupRemoveError: "Error removing group {error}",
defaultGroupAdded_one: "New group added to the default groups",
defaultGroupAdded_other: "Added {{count}} groups to the default groups",
defaultGroupAddedError:
"Error adding group(s) to the default group {error}",
noDefaultGroups: "No default groups",
noDefaultGroupsInstructions:
"Default groups allow you to automatically assign group membership whenever any new user is created or imported throughout <1>identity brokering</1>. Add default groups to get started",
addGroups: "Add groups",
securityDefences: "Security defenses",
headers: "Headers",
bruteForceDetection: "Brute force detection",