diff --git a/docs/documentation/release_notes/topics/26_0_0.adoc b/docs/documentation/release_notes/topics/26_0_0.adoc index 757727da0e4..74fc26603ac 100644 --- a/docs/documentation/release_notes/topics/26_0_0.adoc +++ b/docs/documentation/release_notes/topics/26_0_0.adoc @@ -95,3 +95,11 @@ In the past, regaining access to a {project_name} instance when all admin users It is now possible to run the `start` or `start-dev` commands with specific options to create a temporary admin account. Additionally, a new dedicated command has been introduced, which allows users to regain admin access without hassle. For detailed instructions and more information on this topic, refer to the link:{bootstrapadminrecovery_link}[{bootstrapadminrecovery_name}] guide. + += Identity Providers no longer available from the realm representation + +As part of the improvements around the scalability of realms and organizations when they have many identity providers, the realm representation +no longer holds the list of identity providers. However, they are still available from the realm representation +when exporting a realm. + +For information on how to migrate, see the link:{upgradingguide_link}[{upgradingguide_name}]. diff --git a/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc index c8c7b6a7ac5..8bc3e439edd 100644 --- a/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc @@ -124,3 +124,12 @@ an existing Route53 configurations to avoid prolonged failover times due to clie It used to be difficult to regain access to a {project_name} instance when all admin users were locked out. The process required multiple advanced steps, including direct database access and manual changes. In an effort to improve the user experience, {project_name} now provides multiple ways to bootstrap a new admin account, which can be used to recover from such situations. Consequently, the environment variables `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` have been deprecated. You should use `KC_BOOTSTRAP_ADMIN_USERNAME` and `KC_BOOTSTRAP_ADMIN_PASSWORD` instead. These are also general options, so they may be specified via the cli or other config sources, for example `--bootstrap-admin-username=admin`. For more information, see the new https://www.keycloak.org/server/bootstrap-admin-recovery[Bootstrap admin and recovery] guide. + += Identity Providers no longer available from the realm representation + +As part of the improvements around the scalability of realms and organizations when they have many identity providers, the realm representation +no longer holds the list of identity providers. However, they are still available from the realm representation +when exporting a realm. + +To obtain the query the identity providers in a realm, prefer using the `/realms/{realm}/identity-provider/instances` endpoint. +This endpoint supports filters and pagination. diff --git a/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx b/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx index ae0f3865901..75aa0a7031d 100644 --- a/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx +++ b/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx @@ -39,7 +39,7 @@ export const UserIdentityProviderLinks = ({ const [federatedId, setFederatedId] = useState(""); const [isLinkIdPModalOpen, setIsLinkIdPModalOpen] = useState(false); - const { realm, realmRepresentation } = useRealm(); + const { realm } = useRealm(); const { addAlert, addError } = useAlerts(); const { t } = useTranslation(); const { hasAccess, hasSomeAccess } = useAccess(); @@ -74,8 +74,8 @@ export const UserIdentityProviderLinks = ({ return allFedIds; }; - const getAvailableIdPs = () => { - return realmRepresentation?.identityProviders; + const getAvailableIdPs = async () => { + return adminClient.identityProviders.find(); }; const linkedIdPsLoader = async () => { @@ -87,7 +87,7 @@ export const UserIdentityProviderLinks = ({ (x) => x.identityProvider, ); - return getAvailableIdPs()?.filter( + return (await getAvailableIdPs())?.filter( (item) => !linkedNames.includes(item.alias), )!; }; diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index b8a0d3c2b02..b612e2c148f 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -503,13 +503,14 @@ public class ModelToRepresentation { rep.setRequiredCredentials(reqCredentials); } - List identityProviders = realm.getIdentityProvidersStream() - .map(provider -> toRepresentation(realm, provider, export)).collect(Collectors.toList()); - rep.setIdentityProviders(identityProviders); - - List identityProviderMappers = realm.getIdentityProviderMappersStream() - .map(ModelToRepresentation::toRepresentation).collect(Collectors.toList()); - rep.setIdentityProviderMappers(identityProviderMappers); + if (export) { + List identityProviders = realm.getIdentityProvidersStream() + .map(provider -> toRepresentation(realm, provider, export)).collect(Collectors.toList()); + rep.setIdentityProviders(identityProviders); + List identityProviderMappers = realm.getIdentityProviderMappersStream() + .map(ModelToRepresentation::toRepresentation).collect(Collectors.toList()); + rep.setIdentityProviderMappers(identityProviderMappers); + } rep.setInternationalizationEnabled(realm.isInternationalizationEnabled()); rep.setSupportedLocales(realm.getSupportedLocalesStream().collect(Collectors.toSet())); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java index 10aba83afa5..5b58cbd31a9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -58,6 +58,7 @@ import jakarta.ws.rs.core.Response; import java.io.IOException; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; @@ -186,7 +187,7 @@ public class IdentityProvidersResource { maxResults = 100; // always set a maximum of 100 by default } - Function toRepresentation = briefRepresentation != null && briefRepresentation + Function toRepresentation = Optional.ofNullable(briefRepresentation).orElse(false) ? m -> ModelToRepresentation.toBriefRepresentation(realm, m) : m -> StripSecretsUtils.stripSecrets(session, ModelToRepresentation.toRepresentation(realm, m)); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 36e617c7a47..933401c6d4b 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -389,12 +389,6 @@ public class RealmAdminResource { rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername()); } - if (auth.realm().canViewIdentityProviders()) { - RealmRepresentation r = ModelToRepresentation.toRepresentation(session, realm, false); - rep.setIdentityProviders(r.getIdentityProviders()); - rep.setIdentityProviderMappers(r.getIdentityProviderMappers()); - } - return rep; } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java index b38371ab04c..f7879efbcb3 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -84,6 +84,7 @@ import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.rotation.HardcodedKeyLocator; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.exceptions.ConfigurationException; @@ -566,6 +567,17 @@ public class IdentityProviderTest extends AbstractAdminTest { } } + @Test + public void testNotAvailableFromRealRepresentation() { + IdentityProviderRepresentation newIdentityProvider = createRep("remove-identity-provider", "saml"); + + create(newIdentityProvider); + + RealmRepresentation rep = this.realm.toRepresentation(); + assertNull(rep.getIdentityProviders()); + assertNull(rep.getIdentityProviderMappers()); + } + private void create(IdentityProviderRepresentation idpRep) { Response response = realm.identityProviders().create(idpRep); Assert.assertNotNull(ApiUtil.getCreatedId(response)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java index d21644a5e88..1613456b6f2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java @@ -288,7 +288,7 @@ public class ExportImportUtil { Assert.assertEquals("3025", smtpConfig.get("port")); // Test identity providers - List identityProviders = realm.getIdentityProviders(); + List identityProviders = realmRsc.identityProviders().findAll(); Assert.assertEquals(4, identityProviders.size()); IdentityProviderRepresentation google = null; for (IdentityProviderRepresentation idpRep : identityProviders) {