add support for authorization-grant-types key for oauth2 bindings (#69)

* add support for authorization-grant-types key for oauth2 bindings
* Introduce MapMapper.Source#when(Predicate<Object>)
* update readme with oauth2 -> authorization-grant-type(s)
This commit is contained in:
Daniel Garnier-Moiroux
2022-05-13 17:45:32 +02:00
committed by GitHub
parent e068759173
commit ac28f74c7a
5 changed files with 227 additions and 6 deletions

View File

@@ -290,6 +290,7 @@ Disable Property: `org.springframework.cloud.bindings.boot.oauth2.enable`
| `spring.security.oauth2.client.registration.{name}.provider` | `{provider}`
| `spring.security.oauth2.client.registration.{name}.client-name` | `{client-name}`
| `spring.security.oauth2.client.registration.{name}.client-authentication-method` | `{client-authentication-method}`
| `spring.security.oauth2.client.registration.{name}.authorization-grant-type` | `{authorization-grant-type}` or if not set then `{authorization-grant-types}` if it contains only one value (comma-separated)
| `spring.security.oauth2.client.registration.{name}.redirect-uri` | `{redirect-uri}`
| `spring.security.oauth2.client.registration.{name}.scope` | `{scope}`
| `spring.security.oauth2.client.provider.{provider}.issuer-uri` | `{issuer-uri}`

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.bindings.boot;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
final class MapMapper {
@@ -32,26 +33,49 @@ final class MapMapper {
}
Source from(String... keys) {
return new Source(keys);
return new SourceImpl(keys);
}
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
final class Source {
interface Source {
void to(String key);
void toIfAbsent(String key);
void to(String key, Function<String, Object> function);
void to(String key, TriFunction<String, String, String, Object> function);
Source when(Predicate<Object> predicate);
}
final class SourceImpl implements Source {
private final String[] keys;
private Source(String[] keys) {
private SourceImpl(String[] keys) {
this.keys = keys;
}
void to(String key) {
@Override
public void to(String key) {
to(key, v -> v);
}
void to(String key, Function<String, Object> function) {
@Override
public void toIfAbsent(String key) {
if (destination.containsKey(key)) {
return;
}
to(key, v -> v);
}
@Override
public void to(String key, Function<String, Object> function) {
if (keys.length != 1) {
throw new IllegalStateException(
String.format("source size %d cannot be transformed as one argument", keys.length));
@@ -64,7 +88,8 @@ final class MapMapper {
destination.put(key, function.apply(source.get(keys[0])));
}
void to(String key, TriFunction<String, String, String, Object> function) {
@Override
public void to(String key, TriFunction<String, String, String, Object> function) {
if (keys.length != 3) {
throw new IllegalStateException(
String.format("source size %d cannot be consumed as three arguments", keys.length));
@@ -77,6 +102,47 @@ final class MapMapper {
destination.put(key, function.apply(source.get(keys[0]), source.get(keys[1]), source.get(keys[2])));
}
@Override
public Source when(Predicate<Object> predicate) {
if (keys.length != 1) {
throw new IllegalStateException(
String.format("source size %d cannot be transformed as one argument", keys.length));
}
if (predicate.test(source.get(keys[0]))) {
return this;
} else {
return new NoopSource();
}
}
}
final static class NoopSource implements Source {
@Override
public void to(String key) {
}
@Override
public void toIfAbsent(String key) {
}
@Override
public void to(String key, Function<String, Object> function) {
}
@Override
public void to(String key, TriFunction<String, String, String, Object> function) {
}
@Override
public Source when(Predicate<Object> predicate) {
return this;
}
}
}

View File

@@ -25,6 +25,8 @@ import org.springframework.core.env.Environment;
import java.util.*;
import javax.annotation.Nullable;
import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled;
/**
@@ -58,6 +60,9 @@ public final class SpringSecurityOAuth2BindingsPropertiesProcessor implements Bi
map.from("client-secret").to(String.format("spring.security.oauth2.client.registration.%s.client-secret", clientName));
map.from("client-authentication-method").to(String.format("spring.security.oauth2.client.registration.%s.client-authentication-method", clientName));
map.from("authorization-grant-type").to(String.format("spring.security.oauth2.client.registration.%s.authorization-grant-type", clientName));
map.from("authorization-grant-types")
.when(SpringSecurityOAuth2BindingsPropertiesProcessor::hasSingleValue)
.toIfAbsent(String.format("spring.security.oauth2.client.registration.%s.authorization-grant-type", clientName));
map.from("redirect-uri").to(String.format("spring.security.oauth2.client.registration.%s.redirect-uri", clientName));
map.from("scope").to(String.format("spring.security.oauth2.client.registration.%s.scope", clientName));
map.from("client-name").to(String.format("spring.security.oauth2.client.registration.%s.client-name", clientName));
@@ -71,6 +76,15 @@ public final class SpringSecurityOAuth2BindingsPropertiesProcessor implements Bi
});
}
private static boolean hasSingleValue(@Nullable Object value) {
return Optional.ofNullable(value)
.filter(String.class::isInstance)
.map(String.class::cast)
.map(s -> s.split(","))
.filter(r -> r.length == 1)
.isPresent();
}
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
LOG.replayTo(getClass());

View File

@@ -17,12 +17,14 @@
package org.springframework.cloud.bindings.boot;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DisplayName("Map Mapper test")
final class MapMapperTest {
@@ -94,4 +96,99 @@ final class MapMapperTest {
assertThat(destination).doesNotContainKey("test-destination-key");
}
@Nested
class ToIfAbsentTests {
@Test
@DisplayName("puts if absent in destination")
void absent() {
source.put("test-source-key-1", "test-source-value-1");
map.from("test-source-key-1").toIfAbsent("test-destination-key");
assertThat(destination).containsEntry("test-destination-key", "test-source-value-1");
}
@Test
@DisplayName("does not put if present in destination")
void present() {
source.put("test-source-key-1", "test-source-value-1");
source.put("test-source-key-2", "test-source-value-2");
map.from("test-source-key-1").to("test-destination-key");
map.from("test-source-key-2").toIfAbsent("test-destination-key");
assertThat(destination).containsEntry("test-destination-key", "test-source-value-1");
}
}
@Nested
class WhenTests {
@Test
@DisplayName("puts when predicate is true")
void truePredicate() {
source.put("test-source-key-1", "test-source-value-1");
map.from("test-source-key-1").when(value -> true).to("test-destination-key");
assertThat(destination).containsEntry("test-destination-key", "test-source-value-1");
}
@Test
@DisplayName("does not put when predicate is false")
void falsePredicate() {
source.put("test-source-key-1", "test-source-value-1");
map.from("test-source-key-1").when(value -> false).to("test-destination-key");
assertThat(destination).doesNotContainKey("test-destination-key").doesNotContainValue("test-source-value-1");
}
@Test
@DisplayName("does not put when the key is missing")
void missingKey() {
source.put("test-source-key-1", "test-source-value-1");
map.from("missing-key").when(value -> true).to("test-destination-key");
assertThat(destination).doesNotContainKey("test-destination-key");
}
@Test
@DisplayName("only supports one key")
void onlySupportsOneKey() {
assertThatThrownBy(() -> map.from("one", "two", "three").when(value -> true))
.isInstanceOf(IllegalStateException.class);
}
@Test
@DisplayName("is complete noop when predicate is false")
void falsePredicateIsNoop() {
source.put("test-source-key-1", "test-source-value-1");
source.put("test-source-key-2", "test-source-value-2");
source.put("test-source-key-3", "test-source-value-3");
MapMapper.Source noopSource = map.from("test-source-key-1").when(value -> false);
noopSource.to("one");
noopSource.toIfAbsent("two");
noopSource.to("three", str -> "content");
noopSource.to("four", (a, b, c) -> "content");
assertThat(destination).isEmpty();
}
@Test
@DisplayName("noopSource.when with false predicate is idempotent")
void isIdemPotentWhenPredicateIsFalse() {
source.put("test-source-key-1", "test-source-value-1");
MapMapper.Source noopSource = map.from("test-source-key-1").when(value -> false);
assertThat(noopSource)
.isSameAs(noopSource.when(value -> true))
.isSameAs(noopSource.when(value -> false));
}
}
}

View File

@@ -123,6 +123,49 @@ final class SpringSecurityOAuth2BindingsPropertiesProcessorTest {
;
}
@Test
@DisplayName("contributes a authorization-grant-type is there is only one in authorization-grant-types")
void testAuthorizationGrantTypesOneEntry() {
Bindings bindings = new Bindings(new Binding("binding-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, TYPE)
.withEntry("provider", "some-provider")
.withEntry("authorization-grant-types", "authorization_code")
));
new SpringSecurityOAuth2BindingsPropertiesProcessor().process(new MockEnvironment(), bindings, properties);
assertThat(properties)
.containsEntry("spring.security.oauth2.client.registration.binding-name.authorization-grant-type", "authorization_code");
}
@Test
@DisplayName("does not contributes a authorization-grant-type is there are multiple entries in authorization-grant-types")
void testAuthorizationGrantTypesMultipleEntries() {
Bindings bindings = new Bindings(new Binding("binding-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, TYPE)
.withEntry("provider", "some-provider")
.withEntry("authorization-grant-types", "authorization_code,client_credentials")
));
new SpringSecurityOAuth2BindingsPropertiesProcessor().process(new MockEnvironment(), bindings, properties);
assertThat(properties)
.doesNotContainKey("spring.security.oauth2.client.registration.binding-name.authorization-grant-type");
}
@Test
@DisplayName("uses the value from authorization-grant-type if authorization-grant-types is also present")
void testAuthorizationGrantTypeAndAuthorizationGrantTypes() {
Bindings bindings = new Bindings(new Binding("binding-name", Paths.get("test-path"),
new FluentMap()
.withEntry(Binding.TYPE, TYPE)
.withEntry("provider", "some-provider")
.withEntry("authorization-grant-types", "authorization_code,refresh_token")
.withEntry("authorization-grant-type", "client_credentials")
));
new SpringSecurityOAuth2BindingsPropertiesProcessor().process(new MockEnvironment(), bindings, properties);
assertThat(properties)
.containsEntry("spring.security.oauth2.client.registration.binding-name.authorization-grant-type", "client_credentials");
}
@Test
@DisplayName("can be disabled")
void disabled() {