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:
committed by
GitHub
parent
e068759173
commit
ac28f74c7a
@@ -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}`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user