import React, { useState } from "react"; import { Link } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; import { AlertVariant, Button, ButtonVariant, Label, PageSection, Popover, Tab, TabTitleText, ToolbarItem, } from "@patternfly/react-core"; import { CheckCircleIcon } from "@patternfly/react-icons"; import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation"; import { useAdminClient } from "../context/auth/AdminClient"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useAlerts } from "../components/alert/Alerts"; import { toUpperCase } from "../util"; import { DuplicateFlowModal } from "./DuplicateFlowModal"; import { toCreateFlow } from "./routes/CreateFlow"; import { toFlow } from "./routes/Flow"; import { RequiredActions } from "./RequiredActions"; import "./authentication-section.css"; type UsedBy = "specificClients" | "default" | "specificProviders"; type AuthenticationType = AuthenticationFlowRepresentation & { usedBy: { type?: UsedBy; values: string[] }; }; const realmFlows = [ "browserFlow", "registrationFlow", "directGrantFlow", "resetCredentialsFlow", "clientAuthenticationFlow", "dockerAuthenticationFlow", ]; export const AuthenticationSection = () => { const { t } = useTranslation("authentication"); const adminClient = useAdminClient(); const { realm } = useRealm(); const [key, setKey] = useState(0); const refresh = () => setKey(new Date().getTime()); const { addAlert, addError } = useAlerts(); const [selectedFlow, setSelectedFlow] = useState(); const [open, setOpen] = useState(false); const loader = async () => { const clients = await adminClient.clients.find(); const idps = await adminClient.identityProviders.find(); const realmRep = await adminClient.realms.findOne({ realm }); if (!realmRep) { throw new Error(t("common:notFound")); } const defaultFlows = Object.entries(realmRep) .filter((entry) => realmFlows.includes(entry[0])) .map((entry) => entry[1]); const flows = (await adminClient.authenticationManagement.getFlows()) as AuthenticationType[]; for (const flow of flows) { flow.usedBy = { values: [] }; const client = clients.find( (client) => client.authenticationFlowBindingOverrides && (client.authenticationFlowBindingOverrides["direct_grant"] === flow.id || client.authenticationFlowBindingOverrides["browser"] === flow.id) ); if (client) { flow.usedBy.type = "specificClients"; flow.usedBy.values.push(client.clientId!); } const idp = idps.find( (idp) => idp.firstBrokerLoginFlowAlias === flow.alias || idp.postBrokerLoginFlowAlias === flow.alias ); if (idp) { flow.usedBy.type = "specificProviders"; flow.usedBy.values.push(idp.alias!); } const isDefault = defaultFlows.includes(flow.alias); if (isDefault) { flow.usedBy.type = "default"; } } return flows; }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "authentication:deleteConfirmFlow", children: ( {" "} {{ flow: selectedFlow ? selectedFlow.alias : "" }}. ), continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { await adminClient.authenticationManagement.deleteFlow({ flowId: selectedFlow!.id!, }); refresh(); addAlert(t("deleteFlowSuccess"), AlertVariant.success); } catch (error) { addError("authentication:deleteFlowError", error); } }, }); const UsedBy = ({ id, usedBy: { type, values } }: AuthenticationType) => ( <> {(type === "specificProviders" || type === "specificClients") && ( {t( "appliedBy" + (type === "specificClients" ? "Clients" : "Providers") )}{" "} {values.map((used, index) => ( <> {used} {index < values.length - 1 ? ", " : ""} ))} } > )} {type === "default" && ( )} {!type && ( )} ); const AliasRenderer = ({ id, alias, usedBy, builtIn, }: AuthenticationType) => ( <> {toUpperCase(alias!)} {" "} {builtIn && } ); return ( <> {open && ( setOpen(!open)} onComplete={() => { refresh(); setOpen(false); }} /> )} {t("flows")}} > } actionResolver={({ data }) => { const defaultActions = [ { title: t("duplicate"), onClick: () => { setOpen(true); setSelectedFlow(data); }, }, ]; // remove delete when it's in use or default flow if (data.builtIn || data.usedBy.values.length > 0) { return defaultActions; } else { return [ { title: t("common:delete"), onClick: () => { setSelectedFlow(data); toggleDeleteDialog(); }, }, ...defaultActions, ]; } }} columns={[ { name: "alias", displayKey: "authentication:flowName", cellRenderer: AliasRenderer, }, { name: "usedBy", displayKey: "authentication:usedBy", cellRenderer: UsedBy, }, { name: "description", displayKey: "common:description", }, ]} emptyState={ } /> {t("requiredActions")}} > ); };