Explorar o código

ClientRegistration contains Provider Configuration Metadata

Fixes gh-5540
Joe Grandja %!s(int64=7) %!d(string=hai) anos
pai
achega
057587ef29

+ 29 - 0
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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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) ?

+ 9 - 5
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
  * <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig">OpenID Provider Configuration</a>.
@@ -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<String> scopes = getScopes(metadata);
+		Map<String, Object> 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);

+ 58 - 3
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<String> SCOPES = new LinkedHashSet<>(Arrays.asList("openid", "profile", "email"));
+	private static final Set<String> 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<String, Object> PROVIDER_CONFIGURATION_METADATA =
+			Collections.unmodifiableMap(createProviderConfigurationMetadata());
+
+	private static Map<String, Object> createProviderConfigurationMetadata() {
+		Map<String, Object> 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)

+ 7 - 4
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");
 	}