From c2a550e916a9bc479fd1684dae73a50f14da5e8b Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Fri, 19 Jun 2020 12:32:10 -0400 Subject: [PATCH] Adds support for Spring Security OAuth2 Autoconfigures spring.security.oauth2.* application properties. The name of the binding will become the name of the client. The name of the provider will be taken from metadata/provider. Resolves #35 Signed-off-by: Emily Casey --- README.md | 17 +++ ...rityOAuth2BindingsPropertiesProcessor.java | 60 +++++++++ src/main/resources/META-INF/spring.factories | 1 + ...gSpecificEnvironmentPostProcessorTest.java | 2 +- ...OAuth2BindingsPropertiesProcessorTest.java | 121 ++++++++++++++++++ 5 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessor.java create mode 100644 src/test/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessorTest.java diff --git a/README.md b/README.md index 6bf8f8b..1dfd0f1 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,23 @@ Disable Property: `org.springframework.cloud.bindings.boot.eureka.enable` | `eureka.client.region` | `default` | `eureka.client.serviceUrl.defaultZone` | `{secret/uri}/eureka/` +## Spring Security OAuth2 +Kind: `OAuth2` +Disable Property: `org.springframework.cloud.bindings.boot.oauth2.enable` + +| Property | Value +| -------- | ------------------ +| `spring.security.oauth2.client.registration.{name}.client-id` | `{secret/client-id}` +| `spring.security.oauth2.client.registration.{name}.client-secret` | `{secret/client-secret}` +| `spring.security.oauth2.client.registration.{name}.provider` | `{metadata/provider}` +| `spring.security.oauth2.client.provider.{metadata/provider}.issuer-uri` | `{secret/issuer-uri}` +| `spring.security.oauth2.client.provider.{metadata/provider}.authorization-uri` | `{secret/authorization-uri}` +| `spring.security.oauth2.client.provider.{metadata/provider}.token-uri` | `{secret/token-uri}` +| `spring.security.oauth2.client.provider.{metadata/provider}.user-info-uri` | `{secret/user-info-uri}` +| `spring.security.oauth2.client.provider.{metadata/provider}.user-info-authentication-method` | `{secret/user-info-authentication-method}` +| `spring.security.oauth2.client.provider.{metadata/provider}.jwk-set-uri` | `{secret/jwk-set-uri}` +| `spring.security.oauth2.client.provider.{metadata/provider}.user-name-attribute` | `{secret/user-name-attribute}` + ### SQLServer RDBMS Kind: `SQLServer` Disable Property: `org.springframework.cloud.bindings.boot.sqlserver.enable` diff --git a/src/main/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessor.java new file mode 100644 index 0000000..3577d68 --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bindings.boot; + +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.core.env.Environment; + +import java.util.*; + +import static org.springframework.cloud.bindings.boot.Guards.isKindEnabled; + +/** + * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of kind: {@value KIND}. + */ +public final class SpringSecurityOAuth2BindingsPropertiesProcessor implements BindingsPropertiesProcessor { + + /** + * The {@link Binding} kind that this processor is interested in: {@value}. + **/ + public static final String KIND = "OAuth2"; + + @Override + public void process(Environment environment, Bindings bindings, Map properties) { + if (!isKindEnabled(environment, KIND)) { + return; + } + + bindings.filterBindings(KIND).forEach(binding -> { + MapMapper map = new MapMapper(binding.getSecret(), properties); + String provider = binding.getProvider(); + String clientName = binding.getName(); + properties.put(String.format("spring.security.oauth2.client.registration.%s.provider", clientName), provider); + map.from("client-id").to(String.format("spring.security.oauth2.client.registration.%s.client-id", clientName)); + map.from("client-secret").to(String.format("spring.security.oauth2.client.registration.%s.client-secret", clientName)); + map.from("issuer-uri").to(String.format("spring.security.oauth2.client.provider.%s.issuer-uri", provider)); + map.from("authorization-uri").to(String.format("spring.security.oauth2.client.provider.%s.authorization-uri", provider)); + map.from("token-uri").to(String.format("spring.security.oauth2.client.provider.%s.token-uri", provider)); + map.from("user-info-uri").to(String.format("spring.security.oauth2.client.provider.%s.user-info-uri", provider)); + map.from("user-info-authentication-method").to(String.format("spring.security.oauth2.client.provider.%s.user-info-authentication-method", provider)); + map.from("jwk-set-uri").to(String.format("spring.security.oauth2.client.provider.%s.jwk-set-uri", provider)); + map.from("user-name-attribute").to(String.format("spring.security.oauth2.client.provider.%s.user-name-attribute", provider)); + }); + } + +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 9a942a1..0f3c20f 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -21,5 +21,6 @@ org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\ org.springframework.cloud.bindings.boot.PostgreSqlBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.RabbitMqBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.RedisBindingsPropertiesProcessor, \ + org.springframework.cloud.bindings.boot.SpringSecurityOAuth2BindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.SqlServerBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.WavefrontBindingsPropertiesProcessor \ No newline at end of file diff --git a/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java index 227b2b2..bc1eb60 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java @@ -98,7 +98,7 @@ final class BindingSpecificEnvironmentPostProcessorTest { @Test @DisplayName("included implementations are registered") void includedImplementations() { - assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(17); + assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(18); } } diff --git a/src/test/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessorTest.java new file mode 100644 index 0000000..957470b --- /dev/null +++ b/src/test/java/org/springframework/cloud/bindings/boot/SpringSecurityOAuth2BindingsPropertiesProcessorTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bindings.boot; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.cloud.bindings.FluentMap; +import org.springframework.mock.env.MockEnvironment; + +import java.nio.file.Paths; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.bindings.boot.SpringSecurityOAuth2BindingsPropertiesProcessor.KIND; + +@DisplayName("Spring Security OAuth2 BindingsPropertiesProcessor") +final class SpringSecurityOAuth2BindingsPropertiesProcessorTest { + + private final Bindings bindings = new Bindings( + new Binding("test-name-1", Paths.get("test-path"), + new FluentMap() + .withEntry("kind", KIND) + .withEntry("provider", "github"), + new FluentMap() + .withEntry("client-id", "github-client-id") + .withEntry("client-secret", "github-client-secret") + ), + new Binding("test-name-2", Paths.get("test-path"), + new FluentMap() + .withEntry("kind", KIND) + .withEntry("provider", "okta"), + new FluentMap() + .withEntry("client-id", "okta-client-id") + .withEntry("client-secret", "okta-client-secret") + .withEntry("issuer-uri", "okta-issuer-uri") + ), + new Binding("test-name-3", Paths.get("test-path"), + new FluentMap() + .withEntry("kind", KIND) + .withEntry("provider", "my-provider"), + new FluentMap() + .withEntry("client-id", "my-provider-client-id") + .withEntry("client-secret", "my-provider-client-secret") + .withEntry("authorization-uri", "my-provider-authorization-uri") + .withEntry("token-uri", "my-provider-token-uri") + .withEntry("user-info-uri", "my-provider-user-info-uri") + .withEntry("user-info-authentication-method", "my-provider-user-info-authentication-method") + .withEntry("jwk-set-uri", "my-provider-jwk-set-uri") + .withEntry("user-name-attribute", "my-provider-user-name-attribute") + ) + ); + + private final MockEnvironment environment = new MockEnvironment(); + + private final HashMap properties = new HashMap<>(); + + @Test + @DisplayName("contributes client properties for common providers") + void testCommonProvider() { + new SpringSecurityOAuth2BindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.security.oauth2.client.registration.test-name-1.client-id", "github-client-id") + .containsEntry("spring.security.oauth2.client.registration.test-name-1.client-secret", "github-client-secret") + .containsEntry("spring.security.oauth2.client.registration.test-name-1.provider", "github") + ; + } + + @DisplayName("contributes client properties for OIDC providers") + void testOIDCProvider() { + new SpringSecurityOAuth2BindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.security.oauth2.client.registration.test-name-2.client-id", "okta-client-id") + .containsEntry("spring.security.oauth2.client.registration.test-name-2.client-secret", "okta-client-secret") + .containsEntry("spring.security.oauth2.client.registration.test-name-2.provider", "okta") + .containsEntry("spring.security.oauth2.client.provider.okta.issuer-uri", "okta-issuer-uri") + ; + } + + @DisplayName("contributes client properties for non-OIDC providers") + void testProvider() { + new SpringSecurityOAuth2BindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.security.oauth2.client.registration.test-name-3.client-id", "my-provider-client-id") + .containsEntry("spring.security.oauth2.client.registration.test-name-3.client-secret", "my-provider-client-secret") + .containsEntry("spring.security.oauth2.client.registration.test-name-3.provider", "my-provider") + .containsEntry("spring.security.oauth2.client.provider.my-provider.authorization-uri", "my-provider-authorization-uri") + .containsEntry("spring.security.oauth2.client.provider.my-provider.token-uri", "my-provider-token-uri") + .containsEntry("spring.security.oauth2.client.provider.my-provider.user-info-uri", "my-provider-user-info-uri") + .containsEntry("spring.security.oauth2.client.provider.my-provider.user-info-authentication-method", "my-provider-user-info-authentication-method") + .containsEntry("spring.security.oauth2.client.provider.my-provider.jwk-set-uri", "my-provider-jwk-set-uri") + .containsEntry("spring.security.oauth2.client.provider.my-provider.user-name-attribute", "my-provider-user-name-attribute") + ; + } + + @Test + @DisplayName("can be disabled") + void disabled() { + environment.setProperty("org.springframework.cloud.bindings.boot.oauth2.enable", "false"); + + new SpringSecurityOAuth2BindingsPropertiesProcessor().process(environment, bindings, properties); + + assertThat(properties).isEmpty(); + } + +}