diff --git a/apps/admin-ui/public/resources/en/authentication.json b/apps/admin-ui/public/resources/en/authentication.json index 880e93b574..d33c954fa9 100644 --- a/apps/admin-ui/public/resources/en/authentication.json +++ b/apps/admin-ui/public/resources/en/authentication.json @@ -73,20 +73,22 @@ "buildIn": "Built-in", "appliedByProviders": "Applied by the following providers", "appliedByClients": "Applied by the following clients", - "specificProviders": "Specific providers", - "specificClients": "Specific clients", - "default": "Default", - "notInUse": "Not in use", + "used": { + "SPECIFIC_PROVIDERS": "Specific providers", + "SPECIFIC_CLIENTS": "Specific clients", + "DEFAULT": "Default", + "notInUse": "Not in use" + }, "duplicate": "Duplicate", "bindFlow": "Bind flow", "chooseBindingType": "Choose binding type", "flow": { - "browserFlow": "Browser flow", - "registrationFlow": "Registration flow", - "directGrantFlow": "Direct grant flow", - "resetCredentialsFlow": "Reset credentials flow", - "clientAuthenticationFlow": "Client authentication flow", - "dockerAuthenticationFlow": "Docker authentication flow" + "browser": "Browser flow", + "registration": "Registration flow", + "direct grant": "Direct grant flow", + "reset credentials": "Reset credentials flow", + "clients": "Client authentication flow", + "docker auth": "Docker authentication flow" }, "editInfo": "Edit info", "editFlow": "Edit flow", diff --git a/apps/admin-ui/src/authentication/AuthenticationSection.tsx b/apps/admin-ui/src/authentication/AuthenticationSection.tsx index 84f6442854..c0e3efd751 100644 --- a/apps/admin-ui/src/authentication/AuthenticationSection.tsx +++ b/apps/admin-ui/src/authentication/AuthenticationSection.tsx @@ -36,23 +36,25 @@ import { RoutableTabs, } from "../components/routable-tabs/RoutableTabs"; import { AuthenticationTab, toAuthentication } from "./routes/Authentication"; +import { addTrailingSlash } from "../util"; +import { getAuthorizationHeaders } from "../utils/getAuthorizationHeaders"; import "./authentication-section.css"; -type UsedBy = "specificClients" | "default" | "specificProviders"; +type UsedBy = "SPECIFIC_CLIENTS" | "SPECIFIC_PROVIDERS" | "DEFAULT"; export type AuthenticationType = AuthenticationFlowRepresentation & { - usedBy: { type?: UsedBy; values: string[] }; + usedBy?: { type?: UsedBy; values: string[] }; }; -export const REALM_FLOWS = [ - "browserFlow", - "registrationFlow", - "directGrantFlow", - "resetCredentialsFlow", - "clientAuthenticationFlow", - "dockerAuthenticationFlow", -]; +export const REALM_FLOWS = new Map([ + ["browserFlow", "browser"], + ["registrationFlow", "registration"], + ["directGrantFlow", "direct grant"], + ["resetCredentialsFlow", "reset credentials"], + ["clientAuthenticationFlow", "clients"], + ["dockerAuthenticationFlow", "docker auth"], +]); export default function AuthenticationSection() { const { t } = useTranslation("authentication"); @@ -68,54 +70,18 @@ export default function AuthenticationSection() { const [bindFlowOpen, toggleBindFlow] = useToggle(); const loader = async () => { - const [allClients, allIdps, realmRep, flows] = await Promise.all([ - adminClient.clients.find(), - adminClient.identityProviders.find(), - adminClient.realms.findOne({ realm }), - adminClient.authenticationManagement.getFlows(), - ]); - if (!realmRep) { - throw new Error(t("common:notFound")); - } - - const defaultFlows = Object.entries(realmRep).filter(([key]) => - REALM_FLOWS.includes(key) + const flowsRequest = await fetch( + `${addTrailingSlash( + adminClient.baseUrl + )}admin/realms/${realm}/admin-ui-authentication-management/flows`, + { + method: "GET", + headers: getAuthorizationHeaders(await adminClient.getAccessToken()), + } ); + const flows = await flowsRequest.json(); - for (const flow of flows as AuthenticationType[]) { - flow.usedBy = { values: [] }; - const clients = allClients.filter( - (client) => - client.authenticationFlowBindingOverrides && - (client.authenticationFlowBindingOverrides["direct_grant"] === - flow.id || - client.authenticationFlowBindingOverrides["browser"] === flow.id) - ); - if (clients.length > 0) { - flow.usedBy.type = "specificClients"; - flow.usedBy.values = clients.map(({ clientId }) => clientId!); - } - - const idps = allIdps.filter( - (idp) => - idp.firstBrokerLoginFlowAlias === flow.alias || - idp.postBrokerLoginFlowAlias === flow.alias - ); - if (idps.length > 0) { - flow.usedBy.type = "specificProviders"; - flow.usedBy.values = idps.map(({ alias }) => alias!); - } - - const defaultFlow = defaultFlows.find( - ([, alias]) => flow.alias === alias - ); - if (defaultFlow) { - flow.usedBy.type = "default"; - flow.usedBy.values.push(defaultFlow[0]); - } - } - - return sortBy(flows as AuthenticationType[], (flow) => flow.usedBy.type); + return sortBy(flows as AuthenticationType[], (flow) => flow.usedBy?.type); }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -156,7 +122,7 @@ export default function AuthenticationSection() { to={toFlow({ realm, id: id!, - usedBy: usedBy.type || "notInUse", + usedBy: usedBy?.type || "notInUse", builtIn: builtIn ? "builtIn" : undefined, })} key={`link-${id}`} @@ -236,7 +202,7 @@ export default function AuthenticationSection() { setSelectedFlow(data); }, }, - ...(data.usedBy.type !== "default" && + ...(data.usedBy?.type !== "DEFAULT" && data.providerId !== "client-flow" ? [ { @@ -248,7 +214,7 @@ export default function AuthenticationSection() { }, ] : []), - ...(!data.builtIn && data.usedBy.values.length === 0 + ...(!data.builtIn && !data.usedBy ? [ { title: t("common:delete"), diff --git a/apps/admin-ui/src/authentication/BindFlowDialog.tsx b/apps/admin-ui/src/authentication/BindFlowDialog.tsx index 7bfd42d054..e2debb63f9 100644 --- a/apps/admin-ui/src/authentication/BindFlowDialog.tsx +++ b/apps/admin-ui/src/authentication/BindFlowDialog.tsx @@ -84,7 +84,7 @@ export const BindFlowDialog = ({ flowAlias, onClose }: BindFlowDialogProps) => { ( )} /> diff --git a/apps/admin-ui/src/authentication/FlowDetails.tsx b/apps/admin-ui/src/authentication/FlowDetails.tsx index 6ea02b265a..6e4cc86ea0 100644 --- a/apps/admin-ui/src/authentication/FlowDetails.tsx +++ b/apps/admin-ui/src/authentication/FlowDetails.tsx @@ -303,7 +303,7 @@ export default function FlowDetails() { {t(usedBy)} }, + { text: }, builtIn ? { text: ( diff --git a/apps/admin-ui/src/authentication/components/UsedBy.tsx b/apps/admin-ui/src/authentication/components/UsedBy.tsx index 24a14d4e15..45e2ff62a8 100644 --- a/apps/admin-ui/src/authentication/components/UsedBy.tsx +++ b/apps/admin-ui/src/authentication/components/UsedBy.tsx @@ -10,9 +10,11 @@ import { } from "@patternfly/react-core"; import { CheckCircleIcon } from "@patternfly/react-icons"; -import type { AuthenticationType } from "../AuthenticationSection"; +import { AuthenticationType, REALM_FLOWS } from "../AuthenticationSection"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; import useToggle from "../../utils/useToggle"; +import { useAdminClient } from "../../context/auth/AdminClient"; +import { fetchUsedBy } from "../../components/role-mapping/resource"; import "./used-by.css"; @@ -28,17 +30,31 @@ const Label = ({ label }: { label: string }) => ( ); type UsedByModalProps = { - values: string[]; + id: string; onClose: () => void; isSpecificClient: boolean; }; -const UsedByModal = ({ - values, - isSpecificClient, - onClose, -}: UsedByModalProps) => { +const UsedByModal = ({ id, isSpecificClient, onClose }: UsedByModalProps) => { const { t } = useTranslation("authentication"); + const { adminClient } = useAdminClient(); + + const loader = async ( + first?: number, + max?: number, + search?: string + ): Promise<{ name: string }[]> => { + const result = await fetchUsedBy({ + adminClient, + id, + type: isSpecificClient ? "clients" : "idp", + first: first || 0, + max: max || 10, + search, + }); + return result.map((p) => ({ name: p })); + }; + return ( ({ name: value }))} + loader={loader} + isPaginated ariaLabelKey="authentication:usedBy" searchPlaceholderKey="common:search" columns={[ @@ -79,12 +96,7 @@ const UsedByModal = ({ ); }; -export const UsedBy = ({ - authType: { - id, - usedBy: { type, values }, - }, -}: UsedByProps) => { +export const UsedBy = ({ authType: { id, usedBy } }: UsedByProps) => { const { t } = useTranslation("authentication"); const [open, toggle] = useToggle(); @@ -92,26 +104,29 @@ export const UsedBy = ({ <> {open && ( )} - {(type === "specificProviders" || type === "specificClients") && - (values.length < 8 ? ( + {(usedBy?.type === "SPECIFIC_PROVIDERS" || + usedBy?.type === "SPECIFIC_CLIENTS") && + (usedBy.values.length <= 8 ? ( +
{t( "appliedBy" + - (type === "specificClients" ? "Clients" : "Providers") + (usedBy.type === "SPECIFIC_CLIENTS" + ? "Clients" + : "Providers") )}{" "} - {values.map((used, index) => ( + {usedBy.values.map((used, index) => ( <> {used} - {index < values.length - 1 ? ", " : ""} + {index < usedBy.values.length - 1 ? ", " : ""} ))}
@@ -121,7 +136,7 @@ export const UsedBy = ({ variant="link" className="keycloak__used-by__popover-button" > -
) : ( @@ -130,11 +145,19 @@ export const UsedBy = ({ className="keycloak__used-by__popover-button" onClick={toggle} > -