From 0b6333f27a49f1c2d7e4fad6d99db1a756d94d53 Mon Sep 17 00:00:00 2001 From: Konstantinos Georgilakis Date: Wed, 23 Mar 2022 12:59:56 +0200 Subject: [PATCH] Support for configuring claims supported in Keycloak OP metadata closes #9047 --- .../public/locales/en/translation.json | 10 +- .../OpenIdEndpointConfigurationTab.tsx | 140 ++++++++++++++++++ .../src/realm-settings/RealmSettingsTabs.tsx | 13 ++ .../realm-settings/routes/RealmSettings.tsx | 3 +- .../models/cache/infinispan/RealmAdapter.java | 12 ++ .../infinispan/entities/CachedRealm.java | 7 + .../org/keycloak/models/jpa/RealmAdapter.java | 15 ++ .../models/jpa/entities/RealmAttributes.java | 2 + .../migration/migrators/MigrateTo23_0_0.java | 33 +++++ .../models/map/realm/MapRealmAdapter.java | 17 +++ .../util/IdentityBrokerStateTestHelpers.java | 11 ++ .../java/org/keycloak/models/RealmModel.java | 5 + .../protocol/oidc/OIDCWellKnownProvider.java | 2 +- .../services/managers/RealmManager.java | 2 + .../testsuite/admin/realm/RealmTest.java | 6 +- .../migration/AbstractMigrationTest.java | 13 ++ .../oidc/OIDCWellKnownProviderTest.java | 24 +++ 17 files changed, 310 insertions(+), 5 deletions(-) create mode 100644 js/apps/admin-ui/src/realm-settings/OpenIdEndpointConfigurationTab.tsx create mode 100644 model/legacy-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index e8438a545c99..735da71cc691 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -3291,5 +3291,13 @@ "scopeTypeHelp": "Client scopes, which will be added as default scopes to each created client", "clientDescriptionHelp": "Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example: ${my_client_description}", "clientsClientTypeHelp": "'OpenID Connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.", - "clientsClientScopesHelp": "The scopes associated with this resource." + "clientsClientScopesHelp": "The scopes associated with this resource.", + "openIdEndpointConfiguration": "OpenID Endpoint Configuration", + "claimsSupportedHelp": "Claims supported by this realm. Values will be shown in claims_supported", + "claimsSupported": "Claims supported", + "submit": "Submit", + "addClaimButton": "Add new Supported Claim Button", + "newClaimInput": "New Supported Claim Input", + "removeClaimButton":"Remove Supported Claim Button", + "editClaimInput" : "Edit Supported Claim Input" } diff --git a/js/apps/admin-ui/src/realm-settings/OpenIdEndpointConfigurationTab.tsx b/js/apps/admin-ui/src/realm-settings/OpenIdEndpointConfigurationTab.tsx new file mode 100644 index 000000000000..5b81bcedccaa --- /dev/null +++ b/js/apps/admin-ui/src/realm-settings/OpenIdEndpointConfigurationTab.tsx @@ -0,0 +1,140 @@ +import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; +import { + ActionGroup, + Button, + FormGroup, + PageSection, + InputGroup, + TextInput, +} from "@patternfly/react-core"; +import { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { FormAccess } from "../components/form/FormAccess"; +import { HelpItem } from "ui-shared"; +import { MinusIcon, PlusIcon } from "@patternfly/react-icons"; + +type RealmSettingsThemesTabProps = { + realm: RealmRepresentation; + save: (realm: RealmRepresentation) => void; +}; + +export const OpenIdEndpointConfigurationTab = ({ + realm, + save, +}: RealmSettingsThemesTabProps) => { + const { t } = useTranslation(); + + const [newClaim, setNewClaim] = useState(""); + const { control, handleSubmit, setValue } = useForm(); + + const setupForm = () => { + const attributes = realm.attributes || {}; + setValue("attributes", attributes); + }; + + useEffect(setupForm, []); + + return ( + + + + } + > + { + const claimsSupported = field.value?.claimsSupported?.trim() + ? field.value.claimsSupported.split(",") + : []; + return ( + <> + {claimsSupported + .filter(Boolean) + .map((claim: string, index: number) => ( + + { + const attributes = field.value || {}; + claimsSupported[index] = value; + attributes.claimsSupported = + claimsSupported.join(","); + field.onChange(attributes); + }} + aria-label={t("editClaimInput")} + /> + + + + + + ); +}; diff --git a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx index 99268035645f..26def7e9828b 100644 --- a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx +++ b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx @@ -48,6 +48,7 @@ import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies"; import { RealmSettingsTab, toRealmSettings } from "./routes/RealmSettings"; import { SecurityDefenses } from "./security-defences/SecurityDefenses"; import { UserProfileTab } from "./user-profile/UserProfileTab"; +import { OpenIdEndpointConfigurationTab } from "./OpenIdEndpointConfigurationTab"; type RealmSettingsHeaderProps = { onChange: (value: boolean) => void; @@ -239,6 +240,9 @@ export const RealmSettingsTabs = ({ const clientPoliciesTab = useTab("client-policies"); const userProfileTab = useTab("user-profile"); const userRegistrationTab = useTab("user-registration"); + const openIdEndpointConfigurationTab = useTab( + "openid-endpoint-configuration", + ); const useClientPoliciesTab = (tab: ClientPoliciesTab) => useRoutableTab( @@ -345,6 +349,15 @@ export const RealmSettingsTabs = ({ > + {t("openIdEndpointConfiguration")} + } + data-testid="rs-openid-tab" + {...openIdEndpointConfigurationTab} + > + + {t("tokens")}} data-testid="rs-tokens-tab" diff --git a/js/apps/admin-ui/src/realm-settings/routes/RealmSettings.tsx b/js/apps/admin-ui/src/realm-settings/routes/RealmSettings.tsx index 781bc692236a..545eb269bc56 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/RealmSettings.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/RealmSettings.tsx @@ -16,7 +16,8 @@ export type RealmSettingsTab = | "tokens" | "client-policies" | "user-profile" - | "user-registration"; + | "user-registration" + | "openid-endpoint-configuration"; export type RealmSettingsParams = { realm: string; diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index ee082565d747..78a9f5fc3590 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -641,6 +641,18 @@ public void setActionTokenGeneratedByUserLifespan(String actionTokenId, Integer } } + @Override + public List getClaimsSupported() { + if (isUpdated()) return updated.getClaimsSupported(); + return cached.getClaimsSupported(); + } + + @Override + public void setClaimsSupported(List claimsSupported) { + getDelegateForUpdate(); + updated.setClaimsSupported(claimsSupported); + } + @Override public Stream getRequiredCredentialsStream() { if (isUpdated()) return updated.getRequiredCredentialsStream(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index df4097a1b954..142bce4d7f19 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -173,6 +173,8 @@ public Set getIdentityProviderMapperSet() { protected Map> realmLocalizationTexts; + protected List claimsSupported; + public CachedRealm(Long revision, RealmModel model) { super(revision, model.getId()); name = model.getName(); @@ -239,6 +241,7 @@ public CachedRealm(Long revision, RealmModel model) { requiredCredentials = model.getRequiredCredentialsStream().collect(Collectors.toList()); userActionTokenLifespans = Collections.unmodifiableMap(new HashMap<>(model.getUserActionTokenLifespans())); + claimsSupported = model.getClaimsSupported(); this.identityProviders = model.getIdentityProvidersStream().map(IdentityProviderModel::new) .collect(Collectors.toList()); @@ -743,4 +746,8 @@ public boolean isAllowUserManagedAccess() { public Map> getRealmLocalizationTexts() { return realmLocalizationTexts; } + + public List getClaimsSupported() { + return claimsSupported; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index cff752cce093..6dc1cbd74b0f 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -37,6 +37,7 @@ import jakarta.persistence.TypedQuery; import java.util.*; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Objects.nonNull; @@ -629,6 +630,20 @@ public void setActionTokenGeneratedByUserLifespan(String actionTokenId, Integer setAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + "." + actionTokenId, actionTokenGeneratedByUserLifespan); } + @Override + public List getClaimsSupported() { + if (getAttribute(RealmAttributes.CLAIMS_SUPPORTED) != null) { + return new ArrayList(Arrays.asList(getAttribute(RealmAttributes.CLAIMS_SUPPORTED).split(","))); + } else { + return null; + } + } + + @Override + public void setClaimsSupported(List claimsSupported) { + setAttribute(RealmAttributes.CLAIMS_SUPPORTED, claimsSupported.stream().collect(Collectors.joining(","))); + } + protected RequiredCredentialModel initRequiredCredentialModel(String type) { RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type); if (model == null) { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java index c6d5bbefb61d..db33c6bf1f7f 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java @@ -54,4 +54,6 @@ public interface RealmAttributes { String ADMIN_EVENTS_EXPIRATION = "adminEventsExpiration"; + String CLAIMS_SUPPORTED = "claimsSupported"; + } diff --git a/model/legacy-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java b/model/legacy-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java new file mode 100644 index 000000000000..577c65bf217a --- /dev/null +++ b/model/legacy-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java @@ -0,0 +1,33 @@ +package org.keycloak.migration.migrators; + +import org.keycloak.migration.ModelVersion; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.RealmRepresentation; + +import java.util.Arrays; +import java.util.List; + +public class MigrateTo23_0_0 implements Migration { + + public static final ModelVersion VERSION = new ModelVersion("23.0.0"); + private static final List DEFAULT_CLAIMS_SUPPORTED = Arrays.asList("aud", "sub", "iss", IDToken.AUTH_TIME, IDToken.NAME, IDToken.GIVEN_NAME, IDToken.FAMILY_NAME, IDToken.PREFERRED_USERNAME, IDToken.EMAIL, IDToken.ACR); + + @Override + public void migrate(KeycloakSession session) { + session.realms().getRealmsStream().forEach(realm -> realm.setClaimsSupported(DEFAULT_CLAIMS_SUPPORTED)); + } + + @Override + public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) { + realm.setClaimsSupported(DEFAULT_CLAIMS_SUPPORTED); + } + + + @Override + public ModelVersion getVersion() { + return VERSION; + } +} diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java index 8c9c1a41dec8..75c4bf307cd7 100644 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java @@ -16,6 +16,8 @@ */ package org.keycloak.models.map.realm; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -86,6 +88,7 @@ public class MapRealmAdapter extends AbstractRealmModel implemen private static final String MINIMUM_QUICK_LOGIN_WAIT_SECONDS = "minimumQuickLoginWaitSeconds"; private static final String MAX_DELTA_SECONDS = "maxDeltaTimeSeconds"; private static final String FAILURE_FACTOR = "failureFactor"; + private static final String CLAIMS_SUPPORTED = "claimsSupported"; private PasswordPolicy passwordPolicy; @@ -1716,6 +1719,20 @@ public void setBrowserSecurityHeaders(Map headers) { entity.setBrowserSecurityHeaders(headers); } + @Override + public List getClaimsSupported() { + if (getAttribute(CLAIMS_SUPPORTED) != null) { + return new ArrayList(Arrays.asList(getAttribute(CLAIMS_SUPPORTED).split(","))); + } else { + return null; + } + } + + @Override + public void setClaimsSupported(List claimsSupported) { + setAttribute(CLAIMS_SUPPORTED, claimsSupported.stream().collect(Collectors.joining(","))); + } + @Override public ClientInitialAccessModel createClientInitialAccessModel(int expiration, int count) { MapClientInitialAccessEntity clientInitialAccess = MapClientInitialAccessEntity.createEntity(expiration, count); diff --git a/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTestHelpers.java b/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTestHelpers.java index acea768b6ec9..1a8ea5c0bbf3 100644 --- a/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTestHelpers.java +++ b/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTestHelpers.java @@ -5,6 +5,7 @@ import org.keycloak.models.*; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -1747,6 +1748,16 @@ public Stream getClientInitialAccesses() { public void decreaseRemainingCount(ClientInitialAccessModel clientInitialAccess) { } + + @Override + public List getClaimsSupported() { + return null; + } + + @Override + public void setClaimsSupported(List claimsSupported) { + + } } } diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index b2eb90930187..e5f4a04df7c7 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -24,6 +24,7 @@ import org.keycloak.provider.ProviderEvent; import org.keycloak.storage.SearchableModelField; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -239,6 +240,10 @@ default Boolean getAttribute(String name, Boolean defaultValue) { void setAccessCodeLifespanUserAction(int seconds); + List getClaimsSupported(); + + void setClaimsSupported(List claimsSupported); + OAuth2DeviceConfig getOAuth2DeviceConfig(); CibaConfig getCibaPolicy(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index 895c6cd67edb..d7b37b3398af 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -165,7 +165,7 @@ public Object getConfig() { config.setAuthorizationEncryptionAlgValuesSupported(getSupportedEncryptionAlg(false)); config.setAuthorizationEncryptionEncValuesSupported(getSupportedEncryptionEnc(false)); - config.setClaimsSupported(DEFAULT_CLAIMS_SUPPORTED); + config.setClaimsSupported(realm.getClaimsSupported()); config.setClaimTypesSupported(DEFAULT_CLAIM_TYPES_SUPPORTED); config.setClaimsParameterSupported(true); diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 7a460152930c..45e202f0cff8 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -43,6 +43,7 @@ import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; +import org.keycloak.protocol.oidc.OIDCWellKnownProvider; import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ClientRepresentation; @@ -254,6 +255,7 @@ protected void setupRealmDefaults(RealmModel realm) { realm.setSslRequired(SslRequired.EXTERNAL); realm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY); realm.setLoginWithEmailAllowed(true); + realm.setClaimsSupported(OIDCWellKnownProvider.DEFAULT_CLAIMS_SUPPORTED); realm.setEventsListeners(Collections.singleton("jboss-logging")); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java index 4749331b4341..ebf59b511ec3 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java @@ -39,6 +39,7 @@ import org.keycloak.models.OAuth2DeviceConfig; import org.keycloak.models.OTPPolicy; import org.keycloak.models.ParConfig; +import org.keycloak.models.jpa.entities.RealmAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.representations.adapters.action.GlobalRequestResult; @@ -242,11 +243,12 @@ public void excludesFieldsFromAttributes() { Set attributesKeys = rep2.getAttributes().keySet(); - int expectedAttributesCount = 3; + int expectedAttributesCount = 4; final Set expectedAttributes = Sets.newHashSet( OAuth2DeviceConfig.OAUTH2_DEVICE_CODE_LIFESPAN, OAuth2DeviceConfig.OAUTH2_DEVICE_POLLING_INTERVAL, - ParConfig.PAR_REQUEST_URI_LIFESPAN + ParConfig.PAR_REQUEST_URI_LIFESPAN, + RealmAttributes.CLAIMS_SUPPORTED ); // This attribute is represented in Legacy store as attribute and for Map store as a field diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java index 7a7c4d9ad9fe..7180622516f9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java @@ -40,10 +40,12 @@ import org.keycloak.models.Constants; import org.keycloak.models.LDAPConstants; import org.keycloak.models.UserModel; +import org.keycloak.models.jpa.entities.RealmAttributes; import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; +import org.keycloak.protocol.oidc.OIDCWellKnownProvider; import org.keycloak.protocol.saml.SamlConfigAttributes; import org.keycloak.protocol.saml.util.ArtifactBindingUtils; import org.keycloak.representations.AccessToken; @@ -363,6 +365,11 @@ protected void testMigrationTo22_0_0() { testHttpChallengeFlow(migrationRealm); } + protected void testMigrationTo23_0_0() { + testDefaultClaimsSupported(masterRealm); + testDefaultClaimsSupported(migrationRealm); + } + protected void testDeleteAccount(RealmResource realm) { ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0); ClientResource accountResource = realm.clients().get(accountClient.getId()); @@ -1142,6 +1149,12 @@ protected void testExtremelyLongClientAttribute(RealmResource realm) { }); } + private void testDefaultClaimsSupported(RealmResource realm){ + String claimsSupported = realm.toRepresentation().getAttributes().get(RealmAttributes.CLAIMS_SUPPORTED); + Assert.assertNotNull(claimsSupported); + Assert.assertNames(Arrays.asList(claimsSupported.split(",")), OIDCWellKnownProvider.DEFAULT_CLAIMS_SUPPORTED.toArray(new String[OIDCWellKnownProvider.DEFAULT_CLAIMS_SUPPORTED.size()])); + } + protected void testRealmAttributesMigration() { log.info("testing realm attributes migration"); Map realmAttributes = migrationRealm.toRepresentation().getAttributes(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index d9b674bc6ac5..09e9ce3b587e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -31,8 +31,10 @@ import org.keycloak.jose.jwe.JWEConstants; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.models.Constants; +import org.keycloak.models.jpa.entities.RealmAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; +import org.keycloak.protocol.oidc.OIDCWellKnownProvider; import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory; import org.keycloak.protocol.oidc.representations.MTLSEndpointAliases; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; @@ -66,6 +68,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -401,10 +405,30 @@ public void testGrantTypesSupportedWithTokenExchange() throws IOException { } } + @Test + public void testChangeClaimsSupported() throws IOException { + Client client = AdminClientUtil.createResteasyClient(); + RealmResource testRealm = adminClient.realm("test"); + RealmRepresentation realmRep = testRealm.toRepresentation(); + try { + realmRep.getAttributes().put(RealmAttributes.CLAIMS_SUPPORTED,Stream.of("aud", "sub", "iss", IDToken.AUTH_TIME, IDToken.NAME, IDToken.GIVEN_NAME, IDToken.FAMILY_NAME, IDToken.PREFERRED_USERNAME, IDToken.EMAIL, IDToken.ACR,"email_verified").collect(Collectors.joining(","))); + testRealm.update(realmRep); + + OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT); + Assert.assertNames(oidcConfig.getClaimsSupported(), "aud", "sub", "iss", IDToken.AUTH_TIME, IDToken.NAME, IDToken.GIVEN_NAME, IDToken.FAMILY_NAME, IDToken.PREFERRED_USERNAME, IDToken.EMAIL, IDToken.ACR,"email_verified"); + + } finally { + realmRep.getAttributes().put(RealmAttributes.CLAIMS_SUPPORTED,OIDCWellKnownProvider.DEFAULT_CLAIMS_SUPPORTED.stream().collect(Collectors.joining(","))); + testRealm.update(realmRep); + client.close(); + } + } + private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) { Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS, OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE); + } private OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client, String uriTemplate) {