286 lines
8.7 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
};
|