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:
parent
d10b331e1a
commit
e6df8a2866
6 changed files with 69 additions and 16 deletions
|
@ -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]
|
||||
|
||||
|
|
11
docs/documentation/release_notes/topics/26_0_0.adoc
Normal file
11
docs/documentation/release_notes/topics/26_0_0.adoc
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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) => (
|
||||
<>
|
||||
<RedirectUrl id={id} />
|
||||
<ClientIdSecret create={create} />
|
||||
<DisplayOrder />
|
||||
</>
|
||||
);
|
||||
}: GeneralSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { control } = useFormContext();
|
||||
const alias = useWatch({ control, name: "alias" });
|
||||
const { tab } = useParams<IdentityProviderParams>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue