Allow multiple instances of the same social broker in a realm

Closes #30088

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-06-06 12:02:22 -03:00
parent d10b331e1a
commit e6df8a2866
6 changed files with 69 additions and 16 deletions

View file

@ -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]

View file

@ -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.

View file

@ -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 (
<>
<ViewHeader

View file

@ -405,6 +405,7 @@ export default function DetailSettings() {
const isOIDC = provider.providerId!.includes("oidc");
const isSAML = provider.providerId!.includes("saml");
const isSocial = !isOIDC && !isSAML;
const loader = async () => {
const [loaderMappers, loaderMapperTypes] = await Promise.all([
@ -440,7 +441,7 @@ export default function DetailSettings() {
isHorizontal
onSubmit={handleSubmit(save)}
>
{!isOIDC && !isSAML && <GeneralSettings create={false} id={alias} />}
{isSocial && <GeneralSettings create={false} id={providerId} />}
{isOIDC && <OIDCGeneralSettings />}
{isSAML && <SamlGeneralSettings isAliasReadonly />}
{providerInfo && (

View file

@ -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<IdentityProviderParams>();
return (
<>
<RedirectUrl id={id} />
<RedirectUrl id={alias ? alias : id} />
<TextControl
name="alias"
label={t("alias")}
labelIcon={t("aliasHelp")}
readOnly={tab === "settings"}
rules={{
required: t("required"),
}}
/>
<TextControl name="displayName" label={t("displayName")} />
<ClientIdSecret create={create} />
<DisplayOrder />
</>
);
);
};

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -39,6 +39,7 @@ import java.util.Optional;
public class IdentityProviderBean {
public static OrderedModel.OrderedModelComparator<IdentityProvider> IDP_COMPARATOR_INSTANCE = new OrderedModel.OrderedModelComparator<>();
private static final String ICON_THEME_PREFIX = "kcLogoIdP-";
private boolean displaySocial;
private List<IdentityProvider> 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<String> classesFromTheme = Optional.ofNullable(theme.getProperties().getProperty(ICON_THEME_PREFIX + identityProvider.getAlias()));
Optional<String> classesFromTheme = Optional.ofNullable(getLogoIconClass(identityProvider, theme.getProperties()));
Optional<String> classesFromModel = Optional.ofNullable(identityProvider.getDisplayIconClasses());
return classesFromTheme.orElse(classesFromModel.orElse(""));
} catch (IOException e) {
@ -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;
}
}