keycloak-scim/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx

286 lines
8.7 KiB
TypeScript

import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import {
AlertVariant,
Button,
ButtonVariant,
Label,
PageSection,
Text,
TextContent,
} from "@patternfly/react-core";
import { cellWidth } from "@patternfly/react-table";
import { capitalize } from "lodash-es";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { toIdentityProvider } from "../identity-providers/routes/IdentityProvider";
import { emptyFormatter, upperCaseFormatter } from "../util";
import { UserIdpModal } from "./UserIdPModal";
type UserIdentityProviderLinksProps = {
userId: string;
};
export const UserIdentityProviderLinks = ({
userId,
}: UserIdentityProviderLinksProps) => {
const [key, setKey] = useState(0);
const [federatedId, setFederatedId] = useState("");
const [isLinkIdPModalOpen, setIsLinkIdPModalOpen] = useState(false);
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const { t } = useTranslation("users");
const refresh = () => setKey(new Date().getTime());
type WithProviderId = FederatedIdentityRepresentation & {
providerId: string;
};
const identityProviders = useServerInfo().identityProviders;
const getFederatedIdentities = async () => {
const allProviders = await adminClient.identityProviders.find();
const allFedIds = (await adminClient.users.listFederatedIdentities({
id: userId,
})) as WithProviderId[];
for (const element of allFedIds) {
element.providerId = allProviders.find(
(item) => item.alias === element.identityProvider,
)?.providerId!;
}
return allFedIds;
};
const getAvailableIdPs = async () => {
return (await adminClient.realms.findOne({ realm }))!.identityProviders;
};
const linkedIdPsLoader = async () => {
return getFederatedIdentities();
};
const availableIdPsLoader = async () => {
const linkedNames = (await getFederatedIdentities()).map(
(x) => x.identityProvider,
);
return (await getAvailableIdPs())?.filter(
(item) => !linkedNames.includes(item.alias),
)!;
};
const [toggleUnlinkDialog, UnlinkConfirm] = useConfirmDialog({
titleKey: t("users:unlinkAccountTitle", {
provider: capitalize(federatedId),
}),
messageKey: t("users:unlinkAccountConfirm", {
provider: capitalize(federatedId),
}),
continueButtonLabel: "users:unlink",
continueButtonVariant: ButtonVariant.primary,
onConfirm: async () => {
try {
await adminClient.users.delFromFederatedIdentity({
id: userId,
federatedIdentityId: federatedId,
});
addAlert(t("users:idpUnlinkSuccess"), AlertVariant.success);
refresh();
} catch (error) {
addError("common:mappingDeletedError", error);
}
},
});
const idpLinkRenderer = (idp: WithProviderId) => {
return (
<Link
to={toIdentityProvider({
realm,
providerId: idp.providerId,
alias: idp.identityProvider!,
tab: "settings",
})}
>
{capitalize(idp.identityProvider)}
</Link>
);
};
const badgeRenderer1 = (idp: FederatedIdentityRepresentation) => {
const groupName = identityProviders?.find(
(provider) => provider["id"] === idp.identityProvider,
)?.groupName!;
return (
<Label color={groupName === "Social" ? "blue" : "orange"}>
{groupName === "Social"
? t("users:idpType.social")
: t("users:idpType.custom")}
</Label>
);
};
const badgeRenderer2 = (idp: IdentityProviderRepresentation) => {
const groupName = identityProviders?.find(
(provider) => provider["id"] === idp.providerId,
)?.groupName!;
return (
<Label color={groupName === "User-defined" ? "orange" : "blue"}>
{groupName === "User-defined"
? "Custom"
: groupName! === "Social"
? t("users:idpType.social")
: groupName!}
</Label>
);
};
const unlinkRenderer = (fedIdentity: FederatedIdentityRepresentation) => {
return (
<Button
variant="link"
onClick={() => {
setFederatedId(fedIdentity.identityProvider!);
toggleUnlinkDialog();
}}
>
{t("unlinkAccount")}
</Button>
);
};
const linkRenderer = (idp: IdentityProviderRepresentation) => {
return (
<Button
variant="link"
onClick={() => {
setFederatedId(idp.alias!);
setIsLinkIdPModalOpen(true);
}}
>
{t("linkAccount")}
</Button>
);
};
return (
<>
{isLinkIdPModalOpen && (
<UserIdpModal
userId={userId}
federatedId={federatedId}
onClose={() => setIsLinkIdPModalOpen(false)}
onRefresh={refresh}
/>
)}
<UnlinkConfirm />
<PageSection variant="light" className="pf-u-p-0">
<FormPanel title={t("linkedIdPs")} className="kc-linked-idps">
<TextContent>
<Text className="kc-available-idps-text">
{t("linkedIdPsText")}
</Text>
</TextContent>
<KeycloakDataTable
loader={linkedIdPsLoader}
key={key}
isPaginated={false}
ariaLabelKey="users:LinkedIdPs"
className="kc-linked-IdPs-table"
columns={[
{
name: "identityProvider",
displayKey: "common:name",
cellFormatters: [emptyFormatter()],
cellRenderer: idpLinkRenderer,
transforms: [cellWidth(20)],
},
{
name: "type",
displayKey: "common:type",
cellFormatters: [emptyFormatter()],
cellRenderer: badgeRenderer1,
transforms: [cellWidth(10)],
},
{
name: "userId",
displayKey: "users:userID",
cellFormatters: [emptyFormatter()],
transforms: [cellWidth(30)],
},
{
name: "userName",
displayKey: "users:username",
cellFormatters: [emptyFormatter()],
transforms: [cellWidth(20)],
},
{
name: "",
cellFormatters: [emptyFormatter()],
cellRenderer: unlinkRenderer,
transforms: [cellWidth(20)],
},
]}
emptyState={
<TextContent className="kc-no-providers-text">
<Text>{t("users:noProvidersLinked")}</Text>
</TextContent>
}
/>
</FormPanel>
<FormPanel className="kc-available-idps" title={t("availableIdPs")}>
<TextContent>
<Text className="kc-available-idps-text">
{t("availableIdPsText")}
</Text>
</TextContent>
<KeycloakDataTable
loader={availableIdPsLoader}
key={key}
isPaginated={false}
ariaLabelKey="users:LinkedIdPs"
className="kc-linked-IdPs-table"
columns={[
{
name: "alias",
displayKey: "common:name",
cellFormatters: [emptyFormatter(), upperCaseFormatter()],
transforms: [cellWidth(20)],
},
{
name: "type",
displayKey: "common:type",
cellFormatters: [emptyFormatter()],
cellRenderer: badgeRenderer2,
transforms: [cellWidth(60)],
},
{
name: "",
cellFormatters: [emptyFormatter()],
cellRenderer: linkRenderer,
},
]}
emptyState={
<TextContent className="kc-no-providers-text">
<Text>{t("users:noAvailableIdentityProviders")}</Text>
</TextContent>
}
/>
</FormPanel>
</PageSection>
</>
);
};