diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index 411cd36712..1251489c70 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -24,7 +24,9 @@ import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; /** @@ -153,6 +155,7 @@ public final class ClientRegistration { private String tokenUri; private UserInfoEndpoint userInfoEndpoint = new UserInfoEndpoint(); private String jwkSetUri; + private Map configurationMetadata = Collections.emptyMap(); private ProviderDetails() { } @@ -193,6 +196,16 @@ public final class ClientRegistration { return this.jwkSetUri; } + /** + * Returns a {@code Map} of the metadata describing the provider's configuration. + * + * @since 5.1 + * @return a {@code Map} of the metadata describing the provider's configuration + */ + public Map getConfigurationMetadata() { + return this.configurationMetadata; + } + /** * Details of the UserInfo Endpoint. */ @@ -262,6 +275,7 @@ public final class ClientRegistration { private AuthenticationMethod userInfoAuthenticationMethod = AuthenticationMethod.HEADER; private String userNameAttributeName; private String jwkSetUri; + private Map configurationMetadata = Collections.emptyMap(); private String clientName; private Builder(String registrationId) { @@ -430,6 +444,20 @@ public final class ClientRegistration { return this; } + /** + * Sets the metadata describing the provider's configuration. + * + * @since 5.1 + * @param configurationMetadata the metadata describing the provider's configuration + * @return the {@link Builder} + */ + public Builder providerConfigurationMetadata(Map configurationMetadata) { + if (configurationMetadata != null) { + this.configurationMetadata = new LinkedHashMap<>(configurationMetadata); + } + return this; + } + /** * Sets the logical name of the client or registration. * @@ -476,6 +504,7 @@ public final class ClientRegistration { providerDetails.userInfoEndpoint.authenticationMethod = this.userInfoAuthenticationMethod; providerDetails.userInfoEndpoint.userNameAttributeName = this.userNameAttributeName; providerDetails.jwkSetUri = this.jwkSetUri; + providerDetails.configurationMetadata = Collections.unmodifiableMap(this.configurationMetadata); clientRegistration.providerDetails = providerDetails; clientRegistration.clientName = StringUtils.hasText(this.clientName) ? diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java index 8a87ff9770..587cd2e353 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java @@ -16,21 +16,22 @@ package org.springframework.security.oauth2.client.registration; -import java.net.URI; -import java.util.Collections; -import java.util.List; - import com.nimbusds.oauth2.sdk.GrantType; import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.Scope; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; - import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.web.client.RestTemplate; +import java.net.URI; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + /** * Allows creating a {@link ClientRegistration.Builder} from an * OpenID Provider Configuration. @@ -83,6 +84,8 @@ public final class ClientRegistrations { throw new IllegalArgumentException("Only AuthorizationGrantType.AUTHORIZATION_CODE is supported. The issuer \"" + issuer + "\" returned a configuration of " + grantTypes); } List scopes = getScopes(metadata); + Map configurationMetadata = new LinkedHashMap<>(metadata.toJSONObject()); + return ClientRegistration.withRegistrationId(name) .userNameAttributeName(IdTokenClaimNames.SUB) .scope(scopes) @@ -91,6 +94,7 @@ public final class ClientRegistrations { .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}") .authorizationUri(metadata.getAuthorizationEndpointURI().toASCIIString()) .jwkSetUri(metadata.getJWKSetURI().toASCIIString()) + .providerConfigurationMetadata(configurationMetadata) .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString()) .tokenUri(metadata.getTokenEndpointURI().toASCIIString()) .clientName(issuer); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java index 438894fab5..04c0333e56 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java @@ -20,9 +20,12 @@ import org.springframework.security.oauth2.core.AuthenticationMethod; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import java.util.Arrays; -import java.util.LinkedHashSet; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -37,11 +40,21 @@ public class ClientRegistrationTests { private static final String CLIENT_ID = "client-1"; private static final String CLIENT_SECRET = "secret"; private static final String REDIRECT_URI = "https://example.com"; - private static final Set SCOPES = new LinkedHashSet<>(Arrays.asList("openid", "profile", "email")); + private static final Set SCOPES = Collections.unmodifiableSet( + Stream.of("openid", "profile", "email").collect(Collectors.toSet())); private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorization"; private static final String TOKEN_URI = "https://provider.com/oauth2/token"; private static final String JWK_SET_URI = "https://provider.com/oauth2/keys"; private static final String CLIENT_NAME = "Client 1"; + private static final Map PROVIDER_CONFIGURATION_METADATA = + Collections.unmodifiableMap(createProviderConfigurationMetadata()); + + private static Map createProviderConfigurationMetadata() { + Map configurationMetadata = new LinkedHashMap<>(); + configurationMetadata.put("config-1", "value-1"); + configurationMetadata.put("config-2", "value-2"); + return configurationMetadata; + } @Test(expected = IllegalArgumentException.class) public void buildWhenAuthorizationGrantTypeIsNullThenThrowIllegalArgumentException() { @@ -73,6 +86,7 @@ public class ClientRegistrationTests { .tokenUri(TOKEN_URI) .userInfoAuthenticationMethod(AuthenticationMethod.FORM) .jwkSetUri(JWK_SET_URI) + .providerConfigurationMetadata(PROVIDER_CONFIGURATION_METADATA) .clientName(CLIENT_NAME) .build(); @@ -87,6 +101,7 @@ public class ClientRegistrationTests { assertThat(registration.getProviderDetails().getTokenUri()).isEqualTo(TOKEN_URI); assertThat(registration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod()).isEqualTo(AuthenticationMethod.FORM); assertThat(registration.getProviderDetails().getJwkSetUri()).isEqualTo(JWK_SET_URI); + assertThat(registration.getProviderDetails().getConfigurationMetadata()).isEqualTo(PROVIDER_CONFIGURATION_METADATA); assertThat(registration.getClientName()).isEqualTo(CLIENT_NAME); } @@ -276,6 +291,46 @@ public class ClientRegistrationTests { .build(); } + @Test + public void buildWhenAuthorizationCodeGrantProviderConfigurationMetadataIsNullThenDefaultToEmpty() { + ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID) + .clientId(CLIENT_ID) + .clientSecret(CLIENT_SECRET) + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate(REDIRECT_URI) + .scope(SCOPES.toArray(new String[0])) + .authorizationUri(AUTHORIZATION_URI) + .tokenUri(TOKEN_URI) + .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) + .providerConfigurationMetadata(null) + .jwkSetUri(JWK_SET_URI) + .clientName(CLIENT_NAME) + .build(); + assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isNotNull(); + assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isEmpty(); + } + + @Test + public void buildWhenAuthorizationCodeGrantProviderConfigurationMetadataEmptyThenIsEmpty() { + ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID) + .clientId(CLIENT_ID) + .clientSecret(CLIENT_SECRET) + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate(REDIRECT_URI) + .scope(SCOPES.toArray(new String[0])) + .authorizationUri(AUTHORIZATION_URI) + .tokenUri(TOKEN_URI) + .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) + .providerConfigurationMetadata(Collections.emptyMap()) + .jwkSetUri(JWK_SET_URI) + .clientName(CLIENT_NAME) + .build(); + assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isNotNull(); + assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isEmpty(); + } + @Test public void buildWhenImplicitGrantAllAttributesProvidedThenAllAttributesAreSet() { ClientRegistration registration = ClientRegistration.withRegistrationId(REGISTRATION_ID) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java index eb2e29fb1f..35576d7e6f 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java @@ -16,9 +16,6 @@ package org.springframework.security.oauth2.client.registration; -import java.util.Arrays; -import java.util.Map; - import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.mockwebserver.MockResponse; @@ -26,12 +23,14 @@ import okhttp3.mockwebserver.MockWebServer; import org.junit.After; import org.junit.Before; import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import java.util.Arrays; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -131,6 +130,10 @@ public class ClientRegistrationsTest { assertThat(provider.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth"); assertThat(provider.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token"); assertThat(provider.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs"); + assertThat(provider.getConfigurationMetadata()).containsKeys("authorization_endpoint", "claims_supported", + "code_challenge_methods_supported", "id_token_signing_alg_values_supported", "issuer", "jwks_uri", + "response_types_supported", "revocation_endpoint", "scopes_supported", "subject_types_supported", + "grant_types_supported", "token_endpoint", "token_endpoint_auth_methods_supported", "userinfo_endpoint"); assertThat(provider.getUserInfoEndpoint().getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo"); }