Add priorities to User Federation (#2005)

This commit is contained in:
mfrances17 2022-02-09 04:16:10 -05:00 committed by GitHub
parent d4b1230260
commit f9d3f9dfa7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 264 additions and 26 deletions

View file

@ -4,6 +4,7 @@ import ProviderPage from "../support/pages/admin_console/manage/providers/Provid
import Masthead from "../support/pages/admin_console/Masthead";
import ModalUtils from "../support/util/ModalUtils";
import { keycloakBefore } from "../support/util/keycloak_hooks";
import PriorityDialog from "../support/pages/admin_console/manage/providers/PriorityDialog";
const loginPage = new LoginPage();
const masthead = new Masthead();
@ -14,15 +15,20 @@ const modalUtils = new ModalUtils();
const provider = "kerberos";
const initCapProvider = provider.charAt(0).toUpperCase() + provider.slice(1);
const firstKerberosName = "my-kerberos";
const firstKerberosRealm = "my-realm";
const firstKerberosPrincipal = "my-principal";
const firstKerberosKeytab = "my-keytab";
const kerberosName = "my-kerberos";
const kerberosRealm = "my-realm";
const kerberosPrincipal = "my-principal";
const kerberosKeytab = "my-keytab";
const secondKerberosName = `${firstKerberosName}-2`;
const secondKerberosRealm = `${firstKerberosRealm}-2`;
const secondKerberosPrincipal = `${firstKerberosPrincipal}-2`;
const secondKerberosKeytab = `${firstKerberosKeytab}-2`;
const firstKerberosName = `${kerberosName}-1`;
const firstKerberosRealm = `${kerberosRealm}-1`;
const firstKerberosPrincipal = `${kerberosPrincipal}-1`;
const firstKerberosKeytab = `${kerberosKeytab}-1`;
const secondKerberosName = `${kerberosName}-2`;
const secondKerberosRealm = `${kerberosRealm}-2`;
const secondKerberosPrincipal = `${kerberosPrincipal}-2`;
const secondKerberosKeytab = `${kerberosKeytab}-2`;
const defaultPolicy = "DEFAULT";
const newPolicy = "EVICT_WEEKLY";
@ -39,6 +45,8 @@ const savedSuccessMessage = "User federation provider successfully saved";
const deletedSuccessMessage = "The user federation provider has been deleted.";
const deleteModalTitle = "Delete user federation provider?";
const disableModalTitle = "Disable user federation provider?";
const changeSuccessMsg =
"Successfully changed the priority order of user federation providers";
describe("User Fed Kerberos tests", () => {
beforeEach(() => {
@ -142,16 +150,27 @@ describe("User Fed Kerberos tests", () => {
sidebarPage.goToUserFederation();
});
it("Change the priority order of Kerberos providers", () => {
const priorityDialog = new PriorityDialog();
const providers = [firstKerberosName, secondKerberosName];
sidebarPage.goToUserFederation();
providersPage.clickMenuCommand(addProviderMenu, initCapProvider);
sidebarPage.goToUserFederation();
priorityDialog.openDialog().checkOrder(providers);
priorityDialog.clickSave();
masthead.checkNotificationMessage(changeSuccessMsg, true);
});
it("Delete a Kerberos provider from card view using the card's menu", () => {
providersPage.deleteCardFromCard(secondKerberosName);
modalUtils.checkModalTitle(deleteModalTitle).confirmModal();
masthead.checkNotificationMessage(deletedSuccessMessage);
});
it("Delete a Kerberos provider using the Settings view's Action menu", () => {
providersPage.deleteCardFromMenu(firstKerberosName);
modalUtils.checkModalTitle(deleteModalTitle).confirmModal();
masthead.checkNotificationMessage(deletedSuccessMessage);
});

View file

@ -0,0 +1,37 @@
const expect = chai.expect;
export default class PriorityDialog {
private managePriorityOrder = "viewHeader-lower-btn";
private list = "manageOrderDataList";
openDialog() {
cy.findByTestId(this.managePriorityOrder).click({ force: true });
return this;
}
moveRowTo(from: string, to: string) {
cy.findByTestId(from).trigger("dragstart").trigger("dragleave");
cy.findByTestId(to)
.trigger("dragenter")
.trigger("dragover")
.trigger("drop")
.trigger("dragend");
return this;
}
clickSave() {
cy.get("#modal-confirm").click();
return this;
}
checkOrder(providerNames: string[]) {
cy.get(`[data-testid=${this.list}] li`).should((providers) => {
expect(providers).to.have.length(providerNames.length);
for (let index = 0; index < providerNames.length; index++) {
expect(providers.eq(index)).to.contain(providerNames[index]);
}
});
}
}

View file

@ -1,5 +1,6 @@
import {
Badge,
Button,
Divider,
Dropdown,
DropdownPosition,
@ -38,6 +39,7 @@ export type ViewHeaderProps = {
dropdownItems?: ReactElement[];
lowerDropdownItems?: any;
lowerDropdownMenuTitle?: any;
lowerButton?: any;
isEnabled?: boolean;
onToggle?: (value: boolean) => void;
divider?: boolean;
@ -61,6 +63,7 @@ export const ViewHeader = ({
dropdownItems,
lowerDropdownMenuTitle,
lowerDropdownItems,
lowerButton,
isEnabled = true,
onToggle,
divider = true,
@ -191,6 +194,15 @@ export const ViewHeader = ({
dropdownItems={lowerDropdownItems}
/>
)}
{lowerButton && (
<Button
variant={lowerButton.variant}
onClick={lowerButton.onClick}
data-testid="viewHeader-lower-btn"
>
{lowerButton.lowerButtonTitle}
</Button>
)}
</PageSection>
{divider && <Divider component="div" />}
</>

View file

@ -34,7 +34,7 @@ import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { upperCaseFormatter } from "../util";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ProviderIconMapper } from "./ProviderIconMapper";
import { ManageOderDialog } from "./ManageOrderDialog";
import { ManageOrderDialog } from "./ManageOrderDialog";
import { toIdentityProvider } from "./routes/IdentityProvider";
import { toIdentityProviderCreate } from "./routes/IdentityProviderCreate";
import helpUrls from "../help-urls";
@ -161,7 +161,7 @@ export default function IdentityProvidersSection() {
<>
<DeleteConfirm />
{manageDisplayDialog && (
<ManageOderDialog
<ManageOrderDialog
onClose={() => setManageDisplayDialog(false)}
providers={providers.filter((p) => p.enabled)}
/>

View file

@ -21,15 +21,15 @@ import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
type ManageOderDialogProps = {
type ManageOrderDialogProps = {
providers: IdentityProviderRepresentation[];
onClose: () => void;
};
export const ManageOderDialog = ({
export const ManageOrderDialog = ({
providers,
onClose,
}: ManageOderDialogProps) => {
}: ManageOrderDialogProps) => {
const { t } = useTranslation("identity-providers");
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
@ -98,7 +98,7 @@ export const ManageOderDialog = ({
]}
>
<TextContent className="pf-u-pb-lg">
<Text>{t("oderDialogIntro")}</Text>
<Text>{t("orderDialogIntro")}</Text>
</TextContent>
<DataList

View file

@ -41,7 +41,7 @@ export default {
displayOrder: "Display order",
createSuccess: "Identity provider successfully created",
createError: "Could not create the identity provider: {{error}}",
oderDialogIntro:
orderDialogIntro:
"The order that the providers are listed in the login page or the account console. You can drag the row handles to change the order.",
manageOrderTableAria:
"List of identity providers in the order listed on the login page",

View file

@ -0,0 +1,147 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { sortBy } from "lodash-es";
import {
AlertVariant,
Button,
ButtonVariant,
DataList,
DataListCell,
DataListControl,
DataListDragButton,
DataListItem,
DataListItemCells,
DataListItemRow,
Modal,
ModalVariant,
TextContent,
Text,
} from "@patternfly/react-core";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
type ManagePriorityDialogProps = {
components: ComponentRepresentation[];
onClose: () => void;
};
export const ManagePriorityDialog = ({
components,
onClose,
}: ManagePriorityDialogProps) => {
const { t } = useTranslation("user-federation");
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
const [id, setId] = useState("");
const [liveText, setLiveText] = useState("");
const [order, setOrder] = useState(
components.map((component) => component.name!)
);
const onDragStart = (id: string) => {
setId(id);
setLiveText(t("common:onDragStart", { item: id }));
};
const onDragMove = () => {
setLiveText(t("common:onDragMove", { item: id }));
};
const onDragCancel = () => {
setLiveText(t("common:onDragCancel"));
};
const onDragFinish = (providerOrder: string[]) => {
setLiveText(t("common:onDragFinish", { list: providerOrder }));
setOrder(providerOrder);
};
return (
<Modal
variant={ModalVariant.small}
title={t("managePriorityOrder")}
isOpen={true}
onClose={onClose}
actions={[
<Button
id="modal-confirm"
key="confirm"
onClick={() => {
order.map(async (name, index) => {
const component = components!.find((c) => c.name === name)!;
component.config!.priority = [index.toString()];
try {
const id = component.id!;
await adminClient.components.update({ id }, component);
addAlert(t("orderChangeSuccess"), AlertVariant.success);
} catch (error) {
addError("orderChangeError", error);
}
});
onClose();
}}
>
{t("common:save")}
</Button>,
<Button
id="modal-cancel"
key="cancel"
variant={ButtonVariant.link}
onClick={onClose}
>
{t("common:cancel")}
</Button>,
]}
>
<TextContent className="pf-u-pb-lg">
<Text>{t("managePriorityInfo")}</Text>
</TextContent>
<DataList
aria-label={t("manageOrderTableAria")}
data-testid="manageOrderDataList"
isCompact
onDragFinish={onDragFinish}
onDragStart={onDragStart}
onDragMove={onDragMove}
onDragCancel={onDragCancel}
itemOrder={order}
>
{sortBy(components, "config.priority").map((component) => (
<DataListItem
aria-labelledby={component.name}
id={component.name}
key={component.name}
>
<DataListItemRow>
<DataListControl>
<DataListDragButton
aria-label="Reorder"
aria-labelledby={component.name}
aria-describedby={t("manageOrderItemAria")}
aria-pressed="false"
/>
</DataListControl>
<DataListItemCells
dataListCells={[
<DataListCell
key={`${component.name}-cell`}
data-testid={component.name}
>
<span id={component.name}>{component.name}</span>
</DataListCell>,
]}
/>
</DataListItemRow>
</DataListItem>
))}
</DataList>
<div className="pf-screen-reader" aria-live="assertive">
{liveText}
</div>
</Modal>
);
};

View file

@ -20,6 +20,7 @@ import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ManagePriorityDialog } from "./ManagePriorityDialog";
import { KeycloakCard } from "../components/keycloak-card/KeycloakCard";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
@ -43,6 +44,8 @@ export default function UserFederationSection() {
const history = useHistory();
const [manageDisplayDialog, setManageDisplayDialog] = useState(false);
const providers =
useServerInfo().componentTypes?.[
"org.keycloak.storage.UserStorageProvider"
@ -80,10 +83,11 @@ export default function UserFederationSection() {
[]
);
// const learnMoreLinkProps = {
// title: t("common:learnMore"),
// href: "https://www.keycloak.org/docs/latest/server_admin/index.html#_user-storage-federation",
// };
const lowerButtonProps = {
variant: "link",
onClick: () => setManageDisplayDialog(true),
lowerButtonTitle: t("managePriorities"),
};
let cards;
@ -109,8 +113,14 @@ export default function UserFederationSection() {
toggleDeleteDialog();
};
const cardSorter = (card1: any, card2: any) => {
const a = `${card1.name}`;
const b = `${card2.name}`;
return a < b ? -1 : 1;
};
if (userFederations) {
cards = userFederations.map((userFederation, index) => {
cards = userFederations.sort(cardSorter).map((userFederation, index) => {
const ufCardDropdownItems = [
<DropdownItem
key={`${index}-cardDelete`}
@ -149,6 +159,13 @@ export default function UserFederationSection() {
return (
<>
<DeleteConfirm />
{manageDisplayDialog && userFederations && (
<ManagePriorityDialog
onClose={() => setManageDisplayDialog(false)}
components={userFederations.filter((p) => p.config?.enabled)}
/>
)}
<ViewHeader
titleKey="userFederation"
subKey="user-federation:userFederationExplain"
@ -157,15 +174,13 @@ export default function UserFederationSection() {
? {
lowerDropdownItems: ufAddProviderDropdownItems,
lowerDropdownMenuTitle: "user-federation:addNewProvider",
lowerButton: lowerButtonProps,
}
: {})}
/>
<PageSection>
{userFederations && userFederations.length > 0 ? (
<>
<DeleteConfirm />
<Gallery hasGutter>{cards}</Gallery>
</>
<Gallery hasGutter>{cards}</Gallery>
) : (
<>
<TextContent>

View file

@ -95,6 +95,14 @@ export default {
"Error when trying to connect to LDAP. See server.log for details. {{error}}",
learnMore: "Learn more",
managePriorities: "Manage priorities",
managePriorityOrder: "Manage priority order",
managePriorityInfo:
"Priority is the order of providers when doing a user lookup. You can drag the row handlers to change the priorities.",
orderChangeSuccess:
"Successfully changed the priority order of user federation providers",
orderChangeError:
"Could not change the priority order of user federation providers {{error}}",
addNewProvider: "Add new provider",
addCustomProvider: "Add custom provider",
providerDetails: "Provider details",