Added default group tab to user registration (#1965)
This commit is contained in:
parent
f9d3f9dfa7
commit
90199db346
11 changed files with 386 additions and 14 deletions
54
cypress/integration/realm_user_registration.spec.ts
Normal file
54
cypress/integration/realm_user_registration.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
232
src/realm-settings/DefaultGroupsTab.tsx
Normal file
232
src/realm-settings/DefaultGroupsTab.tsx
Normal 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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>.",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue