From e6df8a2866f17a9cedbba44713b791e19908a0aa Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 6 Jun 2024 12:02:22 -0300 Subject: [PATCH] Allow multiple instances of the same social broker in a realm Closes #30088 Signed-off-by: Pedro Igor --- docs/documentation/release_notes/index.adoc | 3 ++ .../release_notes/topics/26_0_0.adoc | 11 +++++ .../add/AddIdentityProvider.tsx | 10 ++++- .../identity-providers/add/DetailSettings.tsx | 3 +- .../add/GeneralSettings.tsx | 40 +++++++++++++++---- .../model/IdentityProviderBean.java | 18 ++++++--- 6 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 docs/documentation/release_notes/topics/26_0_0.adoc diff --git a/docs/documentation/release_notes/index.adoc b/docs/documentation/release_notes/index.adoc index 754fde73f7..5e25af0a67 100644 --- a/docs/documentation/release_notes/index.adoc +++ b/docs/documentation/release_notes/index.adoc @@ -13,6 +13,9 @@ include::topics/templates/document-attributes.adoc[] :release_header_latest_link: {releasenotes_link_latest} include::topics/templates/release-header.adoc[] +== {project_name_full} 26.0.0 +include::topics/26_0_0.adoc[leveloffset=2] + == {project_name_full} 25.0.0 include::topics/25_0_0.adoc[leveloffset=2] diff --git a/docs/documentation/release_notes/topics/26_0_0.adoc b/docs/documentation/release_notes/topics/26_0_0.adoc new file mode 100644 index 0000000000..bc17cc2984 --- /dev/null +++ b/docs/documentation/release_notes/topics/26_0_0.adoc @@ -0,0 +1,11 @@ += Support for multiple instances of a social broker in a realm + +It is now possible to have multiple instances of the same social broker in a realm. + +Most of the time a realm does not need multiple instances of the same social broker. But due to the introduction +of the `organization` feature, it should be possible to link different instances of the same social broker +to different organizations. + +When creating a social broker, you should now provide an `Alias` and optionally a `Display name` just like any other +broker. + diff --git a/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx b/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx index 59bc8241e2..2762ee66df 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx @@ -62,14 +62,14 @@ export default function AddIdentityProvider() { await adminClient.identityProviders.create({ ...provider, providerId, - alias: providerId, + alias: provider.alias!, }); addAlert(t("createIdentityProviderSuccess"), AlertVariant.success); navigate( toIdentityProvider({ realm, providerId, - alias: providerId, + alias: provider.alias!, tab: "settings", }), ); @@ -78,6 +78,12 @@ export default function AddIdentityProvider() { } }; + const alias = form.getValues("alias"); + + if (!alias) { + form.setValue("alias", providerId); + } + return ( <> { const [loaderMappers, loaderMapperTypes] = await Promise.all([ @@ -440,7 +441,7 @@ export default function DetailSettings() { isHorizontal onSubmit={handleSubmit(save)} > - {!isOIDC && !isSAML && } + {isSocial && } {isOIDC && } {isSAML && } {providerInfo && ( diff --git a/js/apps/admin-ui/src/identity-providers/add/GeneralSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/GeneralSettings.tsx index a0bed143e3..15a030a549 100644 --- a/js/apps/admin-ui/src/identity-providers/add/GeneralSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/GeneralSettings.tsx @@ -1,6 +1,11 @@ -import { RedirectUrl } from "../component/RedirectUrl"; import { ClientIdSecret } from "../component/ClientIdSecret"; import { DisplayOrder } from "../component/DisplayOrder"; +import { RedirectUrl } from "../component/RedirectUrl"; +import { TextControl } from "@keycloak/keycloak-ui-shared"; +import { useTranslation } from "react-i18next"; +import { useFormContext, useWatch } from "react-hook-form"; +import { useParams } from "react-router-dom"; +import type { IdentityProviderParams } from "../routes/IdentityProvider"; type GeneralSettingsProps = { id: string; @@ -10,10 +15,29 @@ type GeneralSettingsProps = { export const GeneralSettings = ({ create = true, id, -}: GeneralSettingsProps) => ( - <> - - - - -); +}: GeneralSettingsProps) => { + const { t } = useTranslation(); + const { control } = useFormContext(); + const alias = useWatch({ control, name: "alias" }); + const { tab } = useParams(); + + return ( + <> + + + + + + + + + ); +}; diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java index d049e18bc6..e01b3ed616 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java @@ -26,11 +26,11 @@ import org.keycloak.theme.Theme; import java.io.IOException; import java.net.URI; -import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Properties; /** * @author Stian Thorgersen @@ -39,6 +39,7 @@ import java.util.Optional; public class IdentityProviderBean { public static OrderedModel.OrderedModelComparator IDP_COMPARATOR_INSTANCE = new OrderedModel.OrderedModelComparator<>(); + private static final String ICON_THEME_PREFIX = "kcLogoIdP-"; private boolean displaySocial; private List providers; @@ -85,11 +86,9 @@ public class IdentityProviderBean { // OR from IdentityProviderModel.getDisplayIconClasses if not defined in theme (for third-party IDPs like Sign-In-With-Apple) // f.e. kcLogoIdP-github = fa fa-github private String getLoginIconClasses(IdentityProviderModel identityProvider) { - final String ICON_THEME_PREFIX = "kcLogoIdP-"; - try { Theme theme = session.theme().getTheme(Theme.Type.LOGIN); - Optional classesFromTheme = Optional.ofNullable(theme.getProperties().getProperty(ICON_THEME_PREFIX + identityProvider.getAlias())); + Optional classesFromTheme = Optional.ofNullable(getLogoIconClass(identityProvider, theme.getProperties())); Optional classesFromModel = Optional.ofNullable(identityProvider.getDisplayIconClasses()); return classesFromTheme.orElse(classesFromModel.orElse("")); } catch (IOException e) { @@ -103,7 +102,7 @@ public class IdentityProviderBean { } public boolean isDisplayInfo() { - return realm.isRegistrationAllowed() || displaySocial; + return realm.isRegistrationAllowed() || displaySocial; } public static class IdentityProvider implements OrderedModel { @@ -154,4 +153,13 @@ public class IdentityProviderBean { } } + private String getLogoIconClass(IdentityProviderModel identityProvider, Properties themeProperties) throws IOException { + String iconClass = themeProperties.getProperty(ICON_THEME_PREFIX + identityProvider.getAlias()); + + if (iconClass == null) { + return themeProperties.getProperty(ICON_THEME_PREFIX + identityProvider.getProviderId()); + } + + return iconClass; + } }