Identity Providers(Mappers): Edit mappers (#1140)
* rebase dont fetch rolesbyID if mapperId save attributes fix cypress test cypress test updates fix cancel route route fix with Erik Apply suggestions from Jon's code review Co-authored-by: Jon Koops <jonkoops@gmail.com> pr feedback from jon remove unused import * usefindbytestid * PR feedback from Jon * fix tests * fix save bug and feedback from Jon * remove unnecessary type * fix cypress test
This commit is contained in:
parent
809247a686
commit
83d5624bf4
21 changed files with 395 additions and 171 deletions
|
@ -19,6 +19,8 @@ describe("Identity provider test", () => {
|
||||||
|
|
||||||
const createSuccessMsg = "Identity provider successfully created";
|
const createSuccessMsg = "Identity provider successfully created";
|
||||||
const createMapperSuccessMsg = "Mapper created successfully.";
|
const createMapperSuccessMsg = "Mapper created successfully.";
|
||||||
|
const saveMapperSuccessMsg = "Mapper saved successfully.";
|
||||||
|
|
||||||
const changeSuccessMsg =
|
const changeSuccessMsg =
|
||||||
"Successfully changed display order of identity providers";
|
"Successfully changed display order of identity providers";
|
||||||
const deletePrompt = "Delete provider?";
|
const deletePrompt = "Delete provider?";
|
||||||
|
@ -53,17 +55,10 @@ describe("Identity provider test", () => {
|
||||||
listingPage.itemExist(identityProviderName);
|
listingPage.itemExist(identityProviderName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should delete provider", () => {
|
|
||||||
const modalUtils = new ModalUtils();
|
|
||||||
listingPage.deleteItem(identityProviderName);
|
|
||||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
|
||||||
|
|
||||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create facebook provider", () => {
|
it("should create facebook provider", () => {
|
||||||
createProviderPage
|
createProviderPage
|
||||||
.clickCard("facebook")
|
.clickCreateDropdown()
|
||||||
|
.clickItem("facebook")
|
||||||
.fill("facebook", "123")
|
.fill("facebook", "123")
|
||||||
.clickAdd();
|
.clickAdd();
|
||||||
masthead.checkNotificationMessage(createSuccessMsg);
|
masthead.checkNotificationMessage(createSuccessMsg);
|
||||||
|
@ -71,18 +66,11 @@ describe("Identity provider test", () => {
|
||||||
|
|
||||||
it("should change order of providers", () => {
|
it("should change order of providers", () => {
|
||||||
const orderDialog = new OrderDialog();
|
const orderDialog = new OrderDialog();
|
||||||
const providers = ["facebook", identityProviderName, "bitbucket"];
|
const providers = [identityProviderName, "facebook", "bitbucket"];
|
||||||
|
|
||||||
sidebarPage.goToIdentityProviders();
|
sidebarPage.goToIdentityProviders();
|
||||||
listingPage.itemExist("facebook");
|
listingPage.itemExist("facebook");
|
||||||
|
|
||||||
createProviderPage
|
|
||||||
.clickCreateDropdown()
|
|
||||||
.clickItem(identityProviderName)
|
|
||||||
.fill(identityProviderName, "123")
|
|
||||||
.clickAdd();
|
|
||||||
|
|
||||||
cy.wait(2000);
|
|
||||||
sidebarPage.goToIdentityProviders();
|
sidebarPage.goToIdentityProviders();
|
||||||
listingPage.itemExist(identityProviderName);
|
listingPage.itemExist(identityProviderName);
|
||||||
|
|
||||||
|
@ -93,13 +81,14 @@ describe("Identity provider test", () => {
|
||||||
.clickAdd();
|
.clickAdd();
|
||||||
|
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
|
|
||||||
sidebarPage.goToIdentityProviders();
|
sidebarPage.goToIdentityProviders();
|
||||||
listingPage.itemExist(identityProviderName);
|
listingPage.itemExist(identityProviderName);
|
||||||
|
|
||||||
orderDialog.openDialog().checkOrder(providers);
|
orderDialog.openDialog().checkOrder(providers);
|
||||||
orderDialog.moveRowTo("facebook", identityProviderName);
|
orderDialog.moveRowTo("facebook", identityProviderName);
|
||||||
|
|
||||||
orderDialog.checkOrder(["facebook", "bitbucket", identityProviderName]);
|
orderDialog.checkOrder(["bitbucket", identityProviderName, "facebook"]);
|
||||||
|
|
||||||
orderDialog.clickSave();
|
orderDialog.clickSave();
|
||||||
masthead.checkNotificationMessage(changeSuccessMsg);
|
masthead.checkNotificationMessage(changeSuccessMsg);
|
||||||
|
@ -129,6 +118,14 @@ describe("Identity provider test", () => {
|
||||||
masthead.checkNotificationMessage(createSuccessMsg);
|
masthead.checkNotificationMessage(createSuccessMsg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should delete provider", () => {
|
||||||
|
const modalUtils = new ModalUtils();
|
||||||
|
listingPage.deleteItem(identityProviderName);
|
||||||
|
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||||
|
|
||||||
|
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||||
|
});
|
||||||
|
|
||||||
it("should add facebook social mapper", () => {
|
it("should add facebook social mapper", () => {
|
||||||
sidebarPage.goToIdentityProviders();
|
sidebarPage.goToIdentityProviders();
|
||||||
|
|
||||||
|
@ -159,6 +156,32 @@ describe("Identity provider test", () => {
|
||||||
masthead.checkNotificationMessage(createMapperSuccessMsg);
|
masthead.checkNotificationMessage(createMapperSuccessMsg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should edit facebook mapper", () => {
|
||||||
|
sidebarPage.goToIdentityProviders();
|
||||||
|
|
||||||
|
listingPage.goToItemDetails("facebook");
|
||||||
|
|
||||||
|
addMapperPage.goToMappersTab();
|
||||||
|
|
||||||
|
listingPage.goToItemDetails("facebook mapper");
|
||||||
|
|
||||||
|
addMapperPage.editSocialMapper();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should edit SAML mapper", () => {
|
||||||
|
sidebarPage.goToIdentityProviders();
|
||||||
|
|
||||||
|
listingPage.goToItemDetails("saml");
|
||||||
|
|
||||||
|
addMapperPage.goToMappersTab();
|
||||||
|
|
||||||
|
listingPage.goToItemDetails("SAML mapper");
|
||||||
|
|
||||||
|
addMapperPage.editSAMLorOIDCMapper();
|
||||||
|
|
||||||
|
masthead.checkNotificationMessage(saveMapperSuccessMsg);
|
||||||
|
});
|
||||||
|
|
||||||
it("clean up providers", () => {
|
it("clean up providers", () => {
|
||||||
const modalUtils = new ModalUtils();
|
const modalUtils = new ModalUtils();
|
||||||
|
|
||||||
|
@ -172,11 +195,6 @@ describe("Identity provider test", () => {
|
||||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||||
|
|
||||||
sidebarPage.goToIdentityProviders();
|
|
||||||
listingPage.itemExist("github").deleteItem("github");
|
|
||||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
|
||||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
|
||||||
|
|
||||||
sidebarPage.goToIdentityProviders();
|
sidebarPage.goToIdentityProviders();
|
||||||
listingPage.itemExist("oidc").deleteItem("oidc");
|
listingPage.itemExist("oidc").deleteItem("oidc");
|
||||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||||
|
|
|
@ -113,4 +113,42 @@ export default class AddMapperPage {
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editSocialMapper() {
|
||||||
|
cy.get(this.syncmodeSelectToggle).click();
|
||||||
|
|
||||||
|
cy.findByTestId("inherit").click();
|
||||||
|
|
||||||
|
cy.findByTestId(this.userSessionAttribute).clear();
|
||||||
|
cy.findByTestId(this.userSessionAttribute).type(
|
||||||
|
"user session attribute_edited"
|
||||||
|
);
|
||||||
|
cy.findByTestId(this.userSessionAttributeValue).clear();
|
||||||
|
|
||||||
|
cy.findByTestId(this.userSessionAttributeValue).type(
|
||||||
|
"user session attribute value_edited"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.saveNewMapper();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
editSAMLorOIDCMapper() {
|
||||||
|
cy.get(this.syncmodeSelectToggle).click();
|
||||||
|
|
||||||
|
cy.findByTestId("legacy").click();
|
||||||
|
|
||||||
|
cy.get(this.attributesKeyInput).clear();
|
||||||
|
cy.get(this.attributesKeyInput).type("key_edited");
|
||||||
|
|
||||||
|
cy.get(this.attributesValueInput).clear();
|
||||||
|
cy.get(this.attributesValueInput).type("value_edited");
|
||||||
|
|
||||||
|
this.toggleSwitch(this.regexAttributeValuesSwitch);
|
||||||
|
|
||||||
|
this.saveNewMapper();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,8 @@ export const AttributesForm = ({
|
||||||
|
|
||||||
const columns = ["Key", "Value"];
|
const columns = ["Key", "Value"];
|
||||||
|
|
||||||
|
const noSaveCancelButtons = !save && !reset;
|
||||||
|
|
||||||
const watchLast = inConfig
|
const watchLast = inConfig
|
||||||
? watch(`config.attributes[${fields.length - 1}].key`, "")
|
? watch(`config.attributes[${fields.length - 1}].key`, "")
|
||||||
: watch(`attributes[${fields.length - 1}].key`, "");
|
: watch(`attributes[${fields.length - 1}].key`, "");
|
||||||
|
@ -78,8 +80,6 @@ export const AttributesForm = ({
|
||||||
}
|
}
|
||||||
}, [fields]);
|
}, [fields]);
|
||||||
|
|
||||||
const noSaveCancelButtons = !save && !reset;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { Fragment, useState } from "react";
|
import React, { Fragment, useState } from "react";
|
||||||
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
import { Link, useHistory } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import {
|
||||||
|
@ -34,7 +34,8 @@ import { upperCaseFormatter } from "../util";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { ProviderIconMapper } from "./ProviderIconMapper";
|
import { ProviderIconMapper } from "./ProviderIconMapper";
|
||||||
import { ManageOderDialog } from "./ManageOrderDialog";
|
import { ManageOderDialog } from "./ManageOrderDialog";
|
||||||
import { toIdentityProviderTab } from "./routes/IdentityProviderTab";
|
import { toIdentityProvider } from "./routes/IdentityProvider";
|
||||||
|
import { toIdentityProviderCreate } from "./routes/IdentityProviderCreate";
|
||||||
|
|
||||||
export const IdentityProvidersSection = () => {
|
export const IdentityProvidersSection = () => {
|
||||||
const { t } = useTranslation("identity-providers");
|
const { t } = useTranslation("identity-providers");
|
||||||
|
@ -43,7 +44,6 @@ export const IdentityProvidersSection = () => {
|
||||||
"groupName"
|
"groupName"
|
||||||
);
|
);
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const { url } = useRouteMatch();
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const refresh = () => setKey(new Date().getTime());
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
@ -73,9 +73,9 @@ export const IdentityProvidersSection = () => {
|
||||||
const DetailLink = (identityProvider: IdentityProviderRepresentation) => (
|
const DetailLink = (identityProvider: IdentityProviderRepresentation) => (
|
||||||
<Link
|
<Link
|
||||||
key={identityProvider.providerId}
|
key={identityProvider.providerId}
|
||||||
to={toIdentityProviderTab({
|
to={toIdentityProvider({
|
||||||
realm,
|
realm,
|
||||||
providerId: identityProvider.providerId,
|
providerId: identityProvider.providerId!,
|
||||||
alias: identityProvider.alias!,
|
alias: identityProvider.alias!,
|
||||||
tab: "settings",
|
tab: "settings",
|
||||||
})}
|
})}
|
||||||
|
@ -96,7 +96,12 @@ export const IdentityProvidersSection = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigateToCreate = (providerId: string) =>
|
const navigateToCreate = (providerId: string) =>
|
||||||
history.push(`${url}/${providerId}`);
|
history.push(
|
||||||
|
toIdentityProviderCreate({
|
||||||
|
realm,
|
||||||
|
providerId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const identityProviderOptions = () =>
|
const identityProviderOptions = () =>
|
||||||
Object.keys(identityProviders).map((group) => (
|
Object.keys(identityProviders).map((group) => (
|
||||||
|
@ -105,11 +110,18 @@ export const IdentityProvidersSection = () => {
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
value={provider.id}
|
value={provider.id}
|
||||||
data-testid={provider.id}
|
component={
|
||||||
onClick={() => navigateToCreate(provider.id)}
|
<Link
|
||||||
>
|
to={toIdentityProviderCreate({
|
||||||
{provider.name}
|
realm,
|
||||||
</DropdownItem>
|
providerId: provider.id,
|
||||||
|
})}
|
||||||
|
data-testid={provider.id}
|
||||||
|
>
|
||||||
|
{provider.name}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</DropdownGroup>
|
</DropdownGroup>
|
||||||
));
|
));
|
||||||
|
@ -243,7 +255,7 @@ export const IdentityProvidersSection = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "providerId",
|
name: "providerId",
|
||||||
displayKey: "identity-providers:provider",
|
displayKey: "identity-providers:providerDetails",
|
||||||
cellFormatters: [upperCaseFormatter()],
|
cellFormatters: [upperCaseFormatter()],
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
PageSection,
|
PageSection,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
import type { BreadcrumbData } from "use-react-router-breadcrumbs";
|
|
||||||
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
|
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
|
||||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||||
import { toUpperCase } from "../../util";
|
import { toUpperCase } from "../../util";
|
||||||
|
@ -18,34 +17,12 @@ import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
import { GeneralSettings } from "./GeneralSettings";
|
import { GeneralSettings } from "./GeneralSettings";
|
||||||
import { toIdentityProviderTab } from "../routes/IdentityProviderTab";
|
import { toIdentityProvider } from "../routes/IdentityProvider";
|
||||||
|
import type { IdentityProviderCreateParams } from "../routes/IdentityProviderCreate";
|
||||||
export const IdentityProviderCrumb = ({ match, location }: BreadcrumbData) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const {
|
|
||||||
params: { id },
|
|
||||||
} = match as unknown as {
|
|
||||||
params: { [id: string]: string };
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{t(
|
|
||||||
`identity-providers:${
|
|
||||||
location.pathname.endsWith("settings")
|
|
||||||
? "provider"
|
|
||||||
: "addIdentityProvider"
|
|
||||||
}`,
|
|
||||||
{
|
|
||||||
provider: toUpperCase(id),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AddIdentityProvider = () => {
|
export const AddIdentityProvider = () => {
|
||||||
const { t } = useTranslation("identity-providers");
|
const { t } = useTranslation("identity-providers");
|
||||||
const { id } = useParams<{ id: string }>();
|
const { providerId } = useParams<IdentityProviderCreateParams>();
|
||||||
const form = useForm<IdentityProviderRepresentation>();
|
const form = useForm<IdentityProviderRepresentation>();
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
@ -61,11 +38,18 @@ export const AddIdentityProvider = () => {
|
||||||
try {
|
try {
|
||||||
await adminClient.identityProviders.create({
|
await adminClient.identityProviders.create({
|
||||||
...provider,
|
...provider,
|
||||||
providerId: id,
|
providerId,
|
||||||
alias: id,
|
alias: providerId,
|
||||||
});
|
});
|
||||||
addAlert(t("createSuccess"), AlertVariant.success);
|
addAlert(t("createSuccess"), AlertVariant.success);
|
||||||
history.push(toIdentityProviderTab({ realm, providerId: id, alias: id }));
|
history.push(
|
||||||
|
toIdentityProvider({
|
||||||
|
realm,
|
||||||
|
providerId: providerId!,
|
||||||
|
alias: providerId!,
|
||||||
|
tab: "settings",
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("identity-providers:createError", error);
|
addError("identity-providers:createError", error);
|
||||||
}
|
}
|
||||||
|
@ -74,7 +58,9 @@ export const AddIdentityProvider = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={t("addIdentityProvider", { provider: toUpperCase(id) })}
|
titleKey={t("addIdentityProvider", {
|
||||||
|
provider: toUpperCase(providerId!),
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<FormAccess
|
<FormAccess
|
||||||
|
@ -83,7 +69,7 @@ export const AddIdentityProvider = () => {
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
>
|
>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<GeneralSettings id={id} />
|
<GeneralSettings id={providerId!} />
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
|
@ -32,6 +32,9 @@ import _ from "lodash";
|
||||||
import { AssociatedRolesModal } from "../../realm-roles/AssociatedRolesModal";
|
import { AssociatedRolesModal } from "../../realm-roles/AssociatedRolesModal";
|
||||||
import type { RoleRepresentation } from "../../model/role-model";
|
import type { RoleRepresentation } from "../../model/role-model";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
import type { IdentityProviderEditMapperParams } from "../routes/EditMapper";
|
||||||
|
import { convertToFormValues } from "../../util";
|
||||||
|
import { toIdentityProvider } from "../routes/IdentityProvider";
|
||||||
|
|
||||||
type IdPMapperRepresentationWithAttributes =
|
type IdPMapperRepresentationWithAttributes =
|
||||||
IdentityProviderMapperRepresentation & {
|
IdentityProviderMapperRepresentation & {
|
||||||
|
@ -45,61 +48,119 @@ export const AddMapper = () => {
|
||||||
const { handleSubmit, control, register, errors } = form;
|
const { handleSubmit, control, register, errors } = form;
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
|
|
||||||
const history = useHistory();
|
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
|
||||||
const { providerId, alias } = useParams<IdentityProviderAddMapperParams>();
|
const { providerId, alias } = useParams<IdentityProviderAddMapperParams>();
|
||||||
|
const { id } = useParams<IdentityProviderEditMapperParams>();
|
||||||
|
|
||||||
const isSAMLorOIDC = providerId === "saml" || providerId === "oidc";
|
const isSAMLorOIDC = providerId === "saml" || providerId === "oidc";
|
||||||
|
|
||||||
const [mapperTypes, setMapperTypes] =
|
const [mapperTypes, setMapperTypes] =
|
||||||
useState<Record<string, IdentityProviderMapperRepresentation>>();
|
useState<Record<string, IdentityProviderMapperRepresentation>>();
|
||||||
|
const [currentMapper, setCurrentMapper] =
|
||||||
|
useState<IdentityProviderMapperRepresentation>();
|
||||||
const [roles, setRoles] = useState<RoleRepresentation[]>([]);
|
const [roles, setRoles] = useState<RoleRepresentation[]>([]);
|
||||||
|
|
||||||
const [rolesModalOpen, setRolesModalOpen] = useState(false);
|
const [rolesModalOpen, setRolesModalOpen] = useState(false);
|
||||||
|
|
||||||
const save = async (idpMapper: IdentityProviderMapperRepresentation) => {
|
const save = async (idpMapper: IdentityProviderMapperRepresentation) => {
|
||||||
try {
|
if (id) {
|
||||||
await adminClient.identityProviders.createMapper({
|
const updatedMapper = {
|
||||||
identityProviderMapper: {
|
...idpMapper,
|
||||||
...idpMapper,
|
identityProviderAlias: alias!,
|
||||||
identityProviderAlias: alias,
|
id: id,
|
||||||
config: {
|
name: currentMapper?.name!,
|
||||||
...idpMapper.config,
|
config: {
|
||||||
attributes: JSON.stringify(idpMapper.config.attributes),
|
...idpMapper.config,
|
||||||
},
|
attributes: JSON.stringify(idpMapper.config?.attributes!),
|
||||||
},
|
},
|
||||||
alias: alias!,
|
};
|
||||||
});
|
try {
|
||||||
addAlert(t("mapperCreateSuccess"), AlertVariant.success);
|
await adminClient.identityProviders.updateMapper(
|
||||||
} catch (error) {
|
{
|
||||||
addError(t("mapperCreateError"), error);
|
id: id!,
|
||||||
|
alias: alias!,
|
||||||
|
},
|
||||||
|
updatedMapper
|
||||||
|
);
|
||||||
|
addAlert(t("mapperSaveSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError(t("mapperSaveError"), error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await adminClient.identityProviders.createMapper({
|
||||||
|
identityProviderMapper: {
|
||||||
|
...idpMapper,
|
||||||
|
identityProviderAlias: alias,
|
||||||
|
config: {
|
||||||
|
...idpMapper.config,
|
||||||
|
attributes: JSON.stringify(idpMapper.config.attributes),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
alias: alias!,
|
||||||
|
});
|
||||||
|
addAlert(t("mapperCreateSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError(t("mapperCreateError"), error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
const { append, remove, fields } = useFieldArray({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
name: "attributes",
|
name: "config.attributes",
|
||||||
});
|
});
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
() =>
|
||||||
const allMapperTypes =
|
Promise.all([
|
||||||
await adminClient.identityProviders.findMapperTypes({
|
id ? adminClient.identityProviders.findOneMapper({ alias, id }) : null,
|
||||||
alias: alias!,
|
adminClient.identityProviders.findMapperTypes({ alias }),
|
||||||
});
|
!id ? adminClient.roles.find() : null,
|
||||||
|
]),
|
||||||
|
([mapper, mapperTypes, roles]) => {
|
||||||
|
if (mapper) {
|
||||||
|
setCurrentMapper(mapper);
|
||||||
|
setupForm(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
const allRoles = await adminClient.roles.find();
|
setMapperTypes(mapperTypes);
|
||||||
return { allMapperTypes, allRoles };
|
|
||||||
},
|
if (roles) {
|
||||||
({ allMapperTypes, allRoles }) => {
|
setRoles(roles);
|
||||||
setMapperTypes(allMapperTypes);
|
}
|
||||||
setRoles(allRoles);
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setupForm = (mapper: IdentityProviderMapperRepresentation) => {
|
||||||
|
form.reset();
|
||||||
|
Object.entries(mapper).map(([key, value]) => {
|
||||||
|
if (key === "config") {
|
||||||
|
if (mapper.config?.["are-attribute-values-regex"]) {
|
||||||
|
form.setValue(
|
||||||
|
"config.are-attribute-values-regex",
|
||||||
|
value["are-attribute-values-regex"][0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapper.config?.attributes) {
|
||||||
|
form.setValue("config.attributes", JSON.parse(value.attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapper.config?.role) {
|
||||||
|
form.setValue("config.role", value.role[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToFormValues(value, "config", form.setValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setValue(key, value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const syncModes = ["inherit", "import", "legacy", "force"];
|
const syncModes = ["inherit", "import", "legacy", "force"];
|
||||||
const [syncModeOpen, setSyncModeOpen] = useState(false);
|
const [syncModeOpen, setSyncModeOpen] = useState(false);
|
||||||
const [mapperTypeOpen, setMapperTypeOpen] = useState(false);
|
const [mapperTypeOpen, setMapperTypeOpen] = useState(false);
|
||||||
|
@ -113,7 +174,7 @@ export const AddMapper = () => {
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
className="kc-add-mapper-title"
|
className="kc-add-mapper-title"
|
||||||
titleKey={t("addIdPMapper")}
|
titleKey={id ? t("editIdPMapper") : t("addIdPMapper")}
|
||||||
divider
|
divider
|
||||||
/>
|
/>
|
||||||
<AssociatedRolesModal
|
<AssociatedRolesModal
|
||||||
|
@ -122,6 +183,7 @@ export const AddMapper = () => {
|
||||||
open={rolesModalOpen}
|
open={rolesModalOpen}
|
||||||
omitComposites
|
omitComposites
|
||||||
isRadio
|
isRadio
|
||||||
|
isMapperId
|
||||||
toggleDialog={toggleModal}
|
toggleDialog={toggleModal}
|
||||||
/>
|
/>
|
||||||
<FormAccess
|
<FormAccess
|
||||||
|
@ -130,6 +192,29 @@ export const AddMapper = () => {
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
className="pf-u-mt-lg"
|
className="pf-u-mt-lg"
|
||||||
>
|
>
|
||||||
|
{id && (
|
||||||
|
<FormGroup
|
||||||
|
label={t("common:id")}
|
||||||
|
fieldId="kc-mapper-id"
|
||||||
|
validated={
|
||||||
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
ref={register()}
|
||||||
|
type="text"
|
||||||
|
value={currentMapper?.id}
|
||||||
|
datatest-id="name-input"
|
||||||
|
id="kc-name"
|
||||||
|
name="name"
|
||||||
|
isDisabled={!!id}
|
||||||
|
validated={
|
||||||
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("common:name")}
|
label={t("common:name")}
|
||||||
labelIcon={
|
labelIcon={
|
||||||
|
@ -153,6 +238,7 @@ export const AddMapper = () => {
|
||||||
datatest-id="name-input"
|
datatest-id="name-input"
|
||||||
id="kc-name"
|
id="kc-name"
|
||||||
name="name"
|
name="name"
|
||||||
|
isDisabled={!!id}
|
||||||
validated={
|
validated={
|
||||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
}
|
}
|
||||||
|
@ -217,12 +303,17 @@ export const AddMapper = () => {
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="identityProviderMapper"
|
name="identityProviderMapper"
|
||||||
defaultValue={"saml-advanced-role-idp-mapper"}
|
defaultValue={
|
||||||
|
providerId === "saml"
|
||||||
|
? "saml-advanced-role-idp-mapper"
|
||||||
|
: "oidc-advanced-role-idp-mapper"
|
||||||
|
}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ onChange, value }) => (
|
render={({ onChange, value }) => (
|
||||||
<Select
|
<Select
|
||||||
toggleId="identityProviderMapper"
|
toggleId="identityProviderMapper"
|
||||||
data-testid="idp-mapper-select"
|
data-testid="idp-mapper-select"
|
||||||
|
isDisabled={!!id}
|
||||||
required
|
required
|
||||||
direction="down"
|
direction="down"
|
||||||
onToggle={() => setMapperTypeOpen(!mapperTypeOpen)}
|
onToggle={() => setMapperTypeOpen(!mapperTypeOpen)}
|
||||||
|
@ -342,7 +433,7 @@ export const AddMapper = () => {
|
||||||
helperTextInvalid={t("common:required")}
|
helperTextInvalid={t("common:required")}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={register({ required: true })}
|
ref={register()}
|
||||||
type="text"
|
type="text"
|
||||||
id="kc-role"
|
id="kc-role"
|
||||||
data-testid="mapper-role-input"
|
data-testid="mapper-role-input"
|
||||||
|
@ -440,7 +531,17 @@ export const AddMapper = () => {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={() => history.push(`/${realm}/client-scopes`)}
|
component={(props) => (
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
to={toIdentityProvider({
|
||||||
|
realm,
|
||||||
|
providerId,
|
||||||
|
alias: alias!,
|
||||||
|
tab: "settings",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{t("common:cancel")}
|
{t("common:cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Link, useHistory, useParams } from "react-router-dom";
|
import { Link, useHistory, useParams } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
Form,
|
Form,
|
||||||
PageSection,
|
PageSection,
|
||||||
|
Spinner,
|
||||||
Tab,
|
Tab,
|
||||||
TabTitleText,
|
TabTitleText,
|
||||||
ToolbarItem,
|
ToolbarItem,
|
||||||
|
@ -38,7 +39,10 @@ import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTa
|
||||||
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||||
import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation";
|
import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation";
|
||||||
import { toIdentityProviderAddMapper } from "../routes/AddMapper";
|
import { toIdentityProviderAddMapper } from "../routes/AddMapper";
|
||||||
|
import { toIdentityProviderEditMapper } from "../routes/EditMapper";
|
||||||
|
|
||||||
import { toUpperCase } from "../../util";
|
import { toUpperCase } from "../../util";
|
||||||
|
import type { IdentityProviderParams } from "../routes/IdentityProvider";
|
||||||
|
|
||||||
type HeaderProps = {
|
type HeaderProps = {
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
|
@ -52,6 +56,7 @@ type IdPWithMapperAttributes = IdentityProviderMapperRepresentation & {
|
||||||
category?: string;
|
category?: string;
|
||||||
helpText?: string;
|
helpText?: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
mapperId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => {
|
const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => {
|
||||||
|
@ -95,21 +100,35 @@ const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => {
|
||||||
|
|
||||||
export const DetailSettings = () => {
|
export const DetailSettings = () => {
|
||||||
const { t } = useTranslation("identity-providers");
|
const { t } = useTranslation("identity-providers");
|
||||||
const { providerId, alias } =
|
const { alias, providerId } = useParams<IdentityProviderParams>();
|
||||||
useParams<{ providerId: string; alias: string }>();
|
|
||||||
|
|
||||||
const form = useForm<IdentityProviderRepresentation>();
|
const form = useForm<IdentityProviderRepresentation>();
|
||||||
const { handleSubmit, getValues, reset } = form;
|
const { handleSubmit, getValues, reset } = form;
|
||||||
|
const [provider, setProvider] = useState<IdentityProviderRepresentation>();
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
|
||||||
|
const MapperLink = ({ name, mapperId }: IdPWithMapperAttributes) => (
|
||||||
|
<Link
|
||||||
|
to={toIdentityProviderEditMapper({
|
||||||
|
realm,
|
||||||
|
alias,
|
||||||
|
providerId: provider?.providerId!,
|
||||||
|
id: mapperId,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() => adminClient.identityProviders.findOne({ alias: alias }),
|
() => adminClient.identityProviders.findOne({ alias }),
|
||||||
(fetchedProvider) => {
|
(fetchedProvider) => {
|
||||||
reset(fetchedProvider);
|
reset(fetchedProvider);
|
||||||
|
setProvider(fetchedProvider);
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
@ -143,10 +162,14 @@ export const DetailSettings = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
const sections = [t("generalSettings"), t("advancedSettings")];
|
const sections = [t("generalSettings"), t("advancedSettings")];
|
||||||
|
|
||||||
const isOIDC = providerId.includes("oidc");
|
const isOIDC = provider.providerId!.includes("oidc");
|
||||||
const isSAML = providerId.includes("saml");
|
const isSAML = provider.providerId!.includes("saml");
|
||||||
|
|
||||||
const loader = async () => {
|
const loader = async () => {
|
||||||
const [loaderMappers, loaderMapperTypes] = await Promise.all([
|
const [loaderMappers, loaderMapperTypes] = await Promise.all([
|
||||||
|
@ -164,6 +187,7 @@ export const DetailSettings = () => {
|
||||||
...mapperType,
|
...mapperType,
|
||||||
name: loaderMapper.name!,
|
name: loaderMapper.name!,
|
||||||
type: mapperType?.name!,
|
type: mapperType?.name!,
|
||||||
|
mapperId: loaderMapper.id!,
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -243,7 +267,7 @@ export const DetailSettings = () => {
|
||||||
isHorizontal
|
isHorizontal
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
>
|
>
|
||||||
<AdvancedSettings isOIDC={isOIDC} isSAML={isSAML} />
|
<AdvancedSettings isOIDC={isOIDC!} isSAML={isSAML!} />
|
||||||
|
|
||||||
<ActionGroup className="keycloak__form_actions">
|
<ActionGroup className="keycloak__form_actions">
|
||||||
<Button data-testid={"save"} type="submit">
|
<Button data-testid={"save"} type="submit">
|
||||||
|
@ -279,7 +303,8 @@ export const DetailSettings = () => {
|
||||||
toIdentityProviderAddMapper({
|
toIdentityProviderAddMapper({
|
||||||
realm,
|
realm,
|
||||||
alias: alias!,
|
alias: alias!,
|
||||||
providerId: providerId,
|
providerId: provider.providerId!,
|
||||||
|
tab: "mappers",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -296,7 +321,8 @@ export const DetailSettings = () => {
|
||||||
to={toIdentityProviderAddMapper({
|
to={toIdentityProviderAddMapper({
|
||||||
realm,
|
realm,
|
||||||
alias: alias!,
|
alias: alias!,
|
||||||
providerId: providerId,
|
providerId: provider.providerId!,
|
||||||
|
tab: "mappers",
|
||||||
})}
|
})}
|
||||||
datatest-id="add-mapper-button"
|
datatest-id="add-mapper-button"
|
||||||
>
|
>
|
||||||
|
@ -308,6 +334,7 @@ export const DetailSettings = () => {
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
displayKey: "common:name",
|
displayKey: "common:name",
|
||||||
|
cellRenderer: MapperLink,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "category",
|
name: "category",
|
||||||
|
|
|
@ -7,13 +7,13 @@ import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
import { RedirectUrl } from "../component/RedirectUrl";
|
import { RedirectUrl } from "../component/RedirectUrl";
|
||||||
import { TextField } from "../component/TextField";
|
import { TextField } from "../component/TextField";
|
||||||
import { DisplayOrder } from "../component/DisplayOrder";
|
import { DisplayOrder } from "../component/DisplayOrder";
|
||||||
import type { IdentityProviderTabParams } from "../routes/IdentityProviderTab";
|
import type { IdentityProviderParams } from "../routes/IdentityProvider";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
export const OIDCGeneralSettings = ({ id }: { id: string }) => {
|
export const OIDCGeneralSettings = ({ id }: { id: string }) => {
|
||||||
const { t } = useTranslation("identity-providers");
|
const { t } = useTranslation("identity-providers");
|
||||||
const { t: th } = useTranslation("identity-providers-help");
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
const { tab } = useParams<IdentityProviderTabParams>();
|
const { tab } = useParams<IdentityProviderParams>();
|
||||||
|
|
||||||
const { register, errors } = useFormContext();
|
const { register, errors } = useFormContext();
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { RedirectUrl } from "../component/RedirectUrl";
|
||||||
import { TextField } from "../component/TextField";
|
import { TextField } from "../component/TextField";
|
||||||
import { DisplayOrder } from "../component/DisplayOrder";
|
import { DisplayOrder } from "../component/DisplayOrder";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import type { IdentityProviderTabParams } from "../routes/IdentityProviderTab";
|
import type { IdentityProviderParams } from "../routes/IdentityProvider";
|
||||||
|
|
||||||
export const SamlGeneralSettings = ({ id }: { id: string }) => {
|
export const SamlGeneralSettings = ({ id }: { id: string }) => {
|
||||||
const { t } = useTranslation("identity-providers");
|
const { t } = useTranslation("identity-providers");
|
||||||
const { t: th } = useTranslation("identity-providers-help");
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
const { tab } = useParams<IdentityProviderTabParams>();
|
const { tab } = useParams<IdentityProviderParams>();
|
||||||
|
|
||||||
const { register, errors } = useFormContext();
|
const { register, errors } = useFormContext();
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,11 @@ export default {
|
||||||
listExplain:
|
listExplain:
|
||||||
"Through Identity Brokering it's easy to allow users to authenticate to Keycloak using external Identity Provider or Social Networks.",
|
"Through Identity Brokering it's easy to allow users to authenticate to Keycloak using external Identity Provider or Social Networks.",
|
||||||
searchForProvider: "Search for provider",
|
searchForProvider: "Search for provider",
|
||||||
provider: "Provider details",
|
providerDetails: "Provider details",
|
||||||
addProvider: "Add provider",
|
addProvider: "Add provider",
|
||||||
addMapper: "Add mapper",
|
addMapper: "Add mapper",
|
||||||
addIdPMapper: "Add Identity Provider Mapper",
|
addIdPMapper: "Add Identity Provider Mapper",
|
||||||
|
editIdPMapper: "Edit Identity Provider Mapper",
|
||||||
mappersList: "Mappers list",
|
mappersList: "Mappers list",
|
||||||
noMappers: "No Mappers",
|
noMappers: "No Mappers",
|
||||||
noMappersInstructions:
|
noMappersInstructions:
|
||||||
|
@ -160,6 +161,8 @@ export default {
|
||||||
selectRole: "Select role",
|
selectRole: "Select role",
|
||||||
mapperCreateSuccess: "Mapper created successfully.",
|
mapperCreateSuccess: "Mapper created successfully.",
|
||||||
mapperCreateError: "Error creating mapper.",
|
mapperCreateError: "Error creating mapper.",
|
||||||
|
mapperSaveSuccess: "Mapper saved successfully.",
|
||||||
|
mapperSaveError: "Error saving mapper: {{error}}",
|
||||||
userSessionAttribute: "User Session Attribute",
|
userSessionAttribute: "User Session Attribute",
|
||||||
userSessionAttributeValue: "User Session Attribute Value",
|
userSessionAttributeValue: "User Session Attribute Value",
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,17 +4,19 @@ import { IdentityProviderKeycloakOidcRoute } from "./routes/IdentityProviderKeyc
|
||||||
import { IdentityProviderOidcRoute } from "./routes/IdentityProviderOidc";
|
import { IdentityProviderOidcRoute } from "./routes/IdentityProviderOidc";
|
||||||
import { IdentityProviderSamlRoute } from "./routes/IdentityProviderSaml";
|
import { IdentityProviderSamlRoute } from "./routes/IdentityProviderSaml";
|
||||||
import { IdentityProvidersRoute } from "./routes/IdentityProviders";
|
import { IdentityProvidersRoute } from "./routes/IdentityProviders";
|
||||||
import { IdentityProviderTabRoute } from "./routes/IdentityProviderTab";
|
|
||||||
import { IdentityProviderAddMapperRoute } from "./routes/AddMapper";
|
import { IdentityProviderAddMapperRoute } from "./routes/AddMapper";
|
||||||
|
import { IdentityProviderEditMapperRoute } from "./routes/EditMapper";
|
||||||
|
import { IdentityProviderCreateRoute } from "./routes/IdentityProviderCreate";
|
||||||
|
|
||||||
const routes: RouteDef[] = [
|
const routes: RouteDef[] = [
|
||||||
|
IdentityProviderAddMapperRoute,
|
||||||
|
IdentityProviderEditMapperRoute,
|
||||||
IdentityProvidersRoute,
|
IdentityProvidersRoute,
|
||||||
IdentityProviderOidcRoute,
|
IdentityProviderOidcRoute,
|
||||||
IdentityProviderSamlRoute,
|
IdentityProviderSamlRoute,
|
||||||
IdentityProviderKeycloakOidcRoute,
|
IdentityProviderKeycloakOidcRoute,
|
||||||
|
IdentityProviderCreateRoute,
|
||||||
IdentityProviderRoute,
|
IdentityProviderRoute,
|
||||||
IdentityProviderTabRoute,
|
|
||||||
IdentityProviderAddMapperRoute,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
|
@ -7,12 +7,14 @@ export type IdentityProviderAddMapperParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
providerId: string;
|
providerId: string;
|
||||||
alias: string;
|
alias: string;
|
||||||
|
tab: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IdentityProviderAddMapperRoute: RouteDef = {
|
export const IdentityProviderAddMapperRoute: RouteDef = {
|
||||||
path: "/:realm/identity-providers/:providerId/:alias/mappers/create",
|
path: "/:realm/identity-providers/:providerId/:alias/:tab/create",
|
||||||
component: AddMapper,
|
component: AddMapper,
|
||||||
access: "manage-identity-providers",
|
access: "manage-identity-providers",
|
||||||
|
breadcrumb: (t) => t("identity-providers:addIdPMapper"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toIdentityProviderAddMapper = (
|
export const toIdentityProviderAddMapper = (
|
||||||
|
|
24
src/identity-providers/routes/EditMapper.ts
Normal file
24
src/identity-providers/routes/EditMapper.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import type { LocationDescriptorObject } from "history";
|
||||||
|
import { generatePath } from "react-router-dom";
|
||||||
|
import type { RouteDef } from "../../route-config";
|
||||||
|
import { AddMapper } from "../add/AddMapper";
|
||||||
|
|
||||||
|
export type IdentityProviderEditMapperParams = {
|
||||||
|
realm: string;
|
||||||
|
providerId: string;
|
||||||
|
alias: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IdentityProviderEditMapperRoute: RouteDef = {
|
||||||
|
path: "/:realm/identity-providers/:providerId/:alias/mappers/:id",
|
||||||
|
component: AddMapper,
|
||||||
|
access: "manage-identity-providers",
|
||||||
|
breadcrumb: (t) => t("identity-providers:editIdPMapper"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toIdentityProviderEditMapper = (
|
||||||
|
params: IdentityProviderEditMapperParams
|
||||||
|
): LocationDescriptorObject => ({
|
||||||
|
pathname: generatePath(IdentityProviderEditMapperRoute.path, params),
|
||||||
|
});
|
|
@ -1,20 +1,21 @@
|
||||||
import type { LocationDescriptorObject } from "history";
|
import type { LocationDescriptorObject } from "history";
|
||||||
import { generatePath } from "react-router-dom";
|
import { generatePath } from "react-router-dom";
|
||||||
import type { RouteDef } from "../../route-config";
|
import type { RouteDef } from "../../route-config";
|
||||||
import {
|
import { DetailSettings } from "../add/DetailSettings";
|
||||||
AddIdentityProvider,
|
|
||||||
IdentityProviderCrumb,
|
type IdentityProviderTabs = "settings" | "mappers";
|
||||||
} from "../add/AddIdentityProvider";
|
|
||||||
|
|
||||||
export type IdentityProviderParams = {
|
export type IdentityProviderParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
id: string;
|
providerId: string;
|
||||||
|
alias: string;
|
||||||
|
tab: IdentityProviderTabs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IdentityProviderRoute: RouteDef = {
|
export const IdentityProviderRoute: RouteDef = {
|
||||||
path: "/:realm/identity-providers/:id",
|
path: "/:realm/identity-providers/:providerId/:alias/:tab",
|
||||||
component: AddIdentityProvider,
|
component: DetailSettings,
|
||||||
breadcrumb: () => IdentityProviderCrumb,
|
breadcrumb: (t) => t("identity-providers:providerDetails"),
|
||||||
access: "manage-identity-providers",
|
access: "manage-identity-providers",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
22
src/identity-providers/routes/IdentityProviderCreate.ts
Normal file
22
src/identity-providers/routes/IdentityProviderCreate.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import type { LocationDescriptorObject } from "history";
|
||||||
|
import { generatePath } from "react-router-dom";
|
||||||
|
import type { RouteDef } from "../../route-config";
|
||||||
|
import { AddIdentityProvider } from "../add/AddIdentityProvider";
|
||||||
|
|
||||||
|
export type IdentityProviderCreateParams = {
|
||||||
|
realm: string;
|
||||||
|
providerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IdentityProviderCreateRoute: RouteDef = {
|
||||||
|
path: "/:realm/identity-providers/:providerId/add",
|
||||||
|
component: AddIdentityProvider,
|
||||||
|
breadcrumb: (t) => t("identity-providers:addProvider"),
|
||||||
|
access: "manage-identity-providers",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toIdentityProviderCreate = (
|
||||||
|
params: IdentityProviderCreateParams
|
||||||
|
): LocationDescriptorObject => ({
|
||||||
|
pathname: generatePath(IdentityProviderCreateRoute.path, params),
|
||||||
|
});
|
|
@ -6,7 +6,7 @@ import { AddOpenIdConnect } from "../add/AddOpenIdConnect";
|
||||||
export type IdentityProviderKeycloakOidcParams = { realm: string };
|
export type IdentityProviderKeycloakOidcParams = { realm: string };
|
||||||
|
|
||||||
export const IdentityProviderKeycloakOidcRoute: RouteDef = {
|
export const IdentityProviderKeycloakOidcRoute: RouteDef = {
|
||||||
path: "/:realm/identity-providers/keycloak-oidc",
|
path: "/:realm/identity-providers/keycloak-oidc/add",
|
||||||
component: AddOpenIdConnect,
|
component: AddOpenIdConnect,
|
||||||
breadcrumb: (t) => t("identity-providers:addKeycloakOpenIdProvider"),
|
breadcrumb: (t) => t("identity-providers:addKeycloakOpenIdProvider"),
|
||||||
access: "manage-identity-providers",
|
access: "manage-identity-providers",
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { AddOpenIdConnect } from "../add/AddOpenIdConnect";
|
||||||
export type IdentityProviderOidcParams = { realm: string };
|
export type IdentityProviderOidcParams = { realm: string };
|
||||||
|
|
||||||
export const IdentityProviderOidcRoute: RouteDef = {
|
export const IdentityProviderOidcRoute: RouteDef = {
|
||||||
path: "/:realm/identity-providers/oidc",
|
path: "/:realm/identity-providers/oidc/add",
|
||||||
component: AddOpenIdConnect,
|
component: AddOpenIdConnect,
|
||||||
breadcrumb: (t) => t("identity-providers:addOpenIdProvider"),
|
breadcrumb: (t) => t("identity-providers:addOpenIdProvider"),
|
||||||
access: "manage-identity-providers",
|
access: "manage-identity-providers",
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { AddSamlConnect } from "../add/AddSamlConnect";
|
||||||
export type IdentityProviderSamlParams = { realm: string };
|
export type IdentityProviderSamlParams = { realm: string };
|
||||||
|
|
||||||
export const IdentityProviderSamlRoute: RouteDef = {
|
export const IdentityProviderSamlRoute: RouteDef = {
|
||||||
path: "/:realm/identity-providers/saml",
|
path: "/:realm/identity-providers/saml/add",
|
||||||
component: AddSamlConnect,
|
component: AddSamlConnect,
|
||||||
breadcrumb: (t) => t("identity-providers:addSamlProvider"),
|
breadcrumb: (t) => t("identity-providers:addSamlProvider"),
|
||||||
access: "manage-identity-providers",
|
access: "manage-identity-providers",
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import type { LocationDescriptorObject } from "history";
|
|
||||||
import { generatePath } from "react-router-dom";
|
|
||||||
import type { RouteDef } from "../../route-config";
|
|
||||||
import { DetailSettings } from "../add/DetailSettings";
|
|
||||||
|
|
||||||
export type IdentityProviderTab = "settings";
|
|
||||||
|
|
||||||
export type IdentityProviderTabParams = {
|
|
||||||
realm: string;
|
|
||||||
providerId?: string;
|
|
||||||
alias: string;
|
|
||||||
tab?: IdentityProviderTab;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IdentityProviderTabRoute: RouteDef = {
|
|
||||||
path: "/:realm/identity-providers/:providerId?/:alias/:tab?",
|
|
||||||
component: DetailSettings,
|
|
||||||
access: "manage-identity-providers",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toIdentityProviderTab = (
|
|
||||||
params: IdentityProviderTabParams
|
|
||||||
): LocationDescriptorObject => ({
|
|
||||||
pathname: generatePath(IdentityProviderTabRoute.path, params),
|
|
||||||
});
|
|
|
@ -16,6 +16,7 @@ import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable
|
||||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||||
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
|
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import type { RealmRoleParams } from "./routes/RealmRole";
|
||||||
|
|
||||||
type Role = RoleRepresentation & {
|
type Role = RoleRepresentation & {
|
||||||
clientId?: string;
|
clientId?: string;
|
||||||
|
@ -29,6 +30,7 @@ export type AssociatedRolesModalProps = {
|
||||||
allRoles?: RoleRepresentation[];
|
allRoles?: RoleRepresentation[];
|
||||||
omitComposites?: boolean;
|
omitComposites?: boolean;
|
||||||
isRadio?: boolean;
|
isRadio?: boolean;
|
||||||
|
isMapperId?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
||||||
|
@ -42,7 +44,7 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const refresh = () => setKey(new Date().getTime());
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<RealmRoleParams>();
|
||||||
|
|
||||||
const alphabetize = (rolesList: RoleRepresentation[]) => {
|
const alphabetize = (rolesList: RoleRepresentation[]) => {
|
||||||
return _.sortBy(rolesList, (role) => role.name?.toUpperCase());
|
return _.sortBy(rolesList, (role) => role.name?.toUpperCase());
|
||||||
|
@ -144,16 +146,11 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
||||||
}, [filterType]);
|
}, [filterType]);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
() =>
|
||||||
if (id) return await adminClient.roles.findOneById({ id });
|
!props.isMapperId
|
||||||
},
|
? adminClient.roles.findOneById({ id })
|
||||||
(fetchedRole) => {
|
: Promise.resolve(null),
|
||||||
if (fetchedRole) {
|
(role) => setName(role ? role.name! : t("createRole")),
|
||||||
setName(fetchedRole.name!);
|
|
||||||
} else {
|
|
||||||
setName(t("createRole"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -183,7 +180,7 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
||||||
key="add"
|
key="add"
|
||||||
data-testid="add-associated-roles-button"
|
data-testid="add-associated-roles-button"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
isDisabled={!selectedRows?.length}
|
isDisabled={!selectedRows.length}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.toggleDialog();
|
props.toggleDialog();
|
||||||
props.onConfirm(selectedRows);
|
props.onConfirm(selectedRows);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import _ from "lodash";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { UserIdpModal } from "./UserIdPModal";
|
import { UserIdpModal } from "./UserIdPModal";
|
||||||
import { toIdentityProviderTab } from "../identity-providers/routes/IdentityProviderTab";
|
import { toIdentityProvider } from "../identity-providers/routes/IdentityProvider";
|
||||||
|
|
||||||
export const UserIdentityProviderLinks = () => {
|
export const UserIdentityProviderLinks = () => {
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
@ -42,10 +42,25 @@ export const UserIdentityProviderLinks = () => {
|
||||||
setIsLinkIdPModalOpen(!isLinkIdPModalOpen);
|
setIsLinkIdPModalOpen(!isLinkIdPModalOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type withProviderId = FederatedIdentityRepresentation & {
|
||||||
|
providerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
const identityProviders = useServerInfo().identityProviders;
|
const identityProviders = useServerInfo().identityProviders;
|
||||||
|
|
||||||
const getFederatedIdentities = async () => {
|
const getFederatedIdentities = async () => {
|
||||||
return await adminClient.users.listFederatedIdentities({ id });
|
const allProviders = await adminClient.identityProviders.find();
|
||||||
|
|
||||||
|
const allFedIds = (await adminClient.users.listFederatedIdentities({
|
||||||
|
id,
|
||||||
|
})) as unknown as withProviderId[];
|
||||||
|
for (const element of allFedIds) {
|
||||||
|
element.providerId = allProviders.find(
|
||||||
|
(item) => item.alias === element.identityProvider
|
||||||
|
)?.providerId!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allFedIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAvailableIdPs = async () => {
|
const getAvailableIdPs = async () => {
|
||||||
|
@ -89,11 +104,12 @@ export const UserIdentityProviderLinks = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const idpLinkRenderer = (idp: FederatedIdentityRepresentation) => {
|
const idpLinkRenderer = (idp: withProviderId) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={toIdentityProviderTab({
|
to={toIdentityProvider({
|
||||||
realm,
|
realm,
|
||||||
|
providerId: idp.providerId,
|
||||||
alias: idp.identityProvider!,
|
alias: idp.identityProvider!,
|
||||||
tab: "settings",
|
tab: "settings",
|
||||||
})}
|
})}
|
||||||
|
|
Loading…
Reference in a new issue