From c3b254579c81e02692ed6e464e3469a6306054df Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 10 Sep 2020 15:09:03 -0400 Subject: [PATCH] Add client configuration settings Closes gh-117 --- .../client/RegisteredClient.java | 50 +++++++++ .../authorization/config/ClientSettings.java | 75 +++++++++++++ .../server/authorization/config/Settings.java | 100 ++++++++++++++++++ .../authorization/config/TokenSettings.java | 74 +++++++++++++ .../client/RegisteredClientTests.java | 21 ++-- .../config/ClientSettingsTests.java | 49 +++++++++ .../authorization/config/SettingsTests.java | 78 ++++++++++++++ .../config/TokenSettingsTests.java | 52 +++++++++ 8 files changed, 485 insertions(+), 14 deletions(-) create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ClientSettings.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/Settings.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/TokenSettings.java create mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ClientSettingsTests.java create mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/SettingsTests.java create mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/TokenSettingsTests.java diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java index b15d5dd..a03930f 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java @@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.authorization.client; import org.springframework.security.core.SpringSecurityCoreVersion2; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.server.authorization.config.ClientSettings; +import org.springframework.security.oauth2.server.authorization.config.TokenSettings; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -46,6 +48,8 @@ public class RegisteredClient implements Serializable { private Set authorizationGrantTypes; private Set redirectUris; private Set scopes; + private ClientSettings clientSettings; + private TokenSettings tokenSettings; protected RegisteredClient() { } @@ -114,6 +118,24 @@ public class RegisteredClient implements Serializable { return this.scopes; } + /** + * Returns the {@link ClientSettings client configuration settings}. + * + * @return the {@link ClientSettings} + */ + public ClientSettings getClientSettings() { + return this.clientSettings; + } + + /** + * Returns the {@link TokenSettings token configuration settings}. + * + * @return the {@link TokenSettings} + */ + public TokenSettings getTokenSettings() { + return this.tokenSettings; + } + @Override public String toString() { return "RegisteredClient{" + @@ -160,6 +182,8 @@ public class RegisteredClient implements Serializable { private Set authorizationGrantTypes = new LinkedHashSet<>(); private Set redirectUris = new LinkedHashSet<>(); private Set scopes = new LinkedHashSet<>(); + private ClientSettings clientSettings; + private TokenSettings tokenSettings; protected Builder(String id) { this.id = id; @@ -181,6 +205,8 @@ public class RegisteredClient implements Serializable { if (!CollectionUtils.isEmpty(registeredClient.scopes)) { this.scopes.addAll(registeredClient.scopes); } + this.clientSettings = new ClientSettings(registeredClient.clientSettings.settings()); + this.tokenSettings = new TokenSettings(registeredClient.tokenSettings.settings()); } /** @@ -310,6 +336,28 @@ public class RegisteredClient implements Serializable { return this; } + /** + * Sets the {@link ClientSettings client configuration settings}. + * + * @param clientSettings the client configuration settings + * @return the {@link Builder} + */ + public Builder clientSettings(ClientSettings clientSettings) { + this.clientSettings = clientSettings; + return this; + } + + /** + * Sets the {@link TokenSettings token configuration settings}. + * + * @param tokenSettings the token configuration settings + * @return the {@link Builder} + */ + public Builder tokenSettings(TokenSettings tokenSettings) { + this.tokenSettings = tokenSettings; + return this; + } + /** * Builds a new {@link RegisteredClient}. * @@ -341,6 +389,8 @@ public class RegisteredClient implements Serializable { registeredClient.authorizationGrantTypes = Collections.unmodifiableSet(this.authorizationGrantTypes); registeredClient.redirectUris = Collections.unmodifiableSet(this.redirectUris); registeredClient.scopes = Collections.unmodifiableSet(this.scopes); + registeredClient.clientSettings = this.clientSettings != null ? this.clientSettings : new ClientSettings(); + registeredClient.tokenSettings = this.tokenSettings != null ? this.tokenSettings : new TokenSettings(); return registeredClient; } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ClientSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ClientSettings.java new file mode 100644 index 0000000..b22c909 --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ClientSettings.java @@ -0,0 +1,75 @@ +/* + * 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 + * + * https://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.security.oauth2.server.authorization.config; + +import java.util.HashMap; +import java.util.Map; + +/** + * A facility for client configuration settings. + * + * @author Joe Grandja + * @since 0.0.2 + * @see Settings + */ +public class ClientSettings extends Settings { + private static final String CLIENT_SETTING_BASE = "spring.security.oauth2.authorization-server.client."; + public static final String REQUIRE_PROOF_KEY = CLIENT_SETTING_BASE.concat("require-proof-key"); + + /** + * Constructs a {@code ClientSettings}. + */ + public ClientSettings() { + this(defaultSettings()); + } + + /** + * Constructs a {@code ClientSettings} using the provided parameters. + * + * @param settings the initial settings + */ + public ClientSettings(Map settings) { + super(settings); + } + + /** + * Returns {@code true} if the client is required to provide a proof key challenge and verifier + * when performing the Authorization Code Grant flow. The default is {@code false}. + * + * @return {@code true} if the client is required to provide a proof key challenge and verifier, {@code false} otherwise + */ + public boolean requireProofKey() { + return setting(REQUIRE_PROOF_KEY); + } + + /** + * Set to {@code true} if the client is required to provide a proof key challenge and verifier + * when performing the Authorization Code Grant flow. + * + * @param requireProofKey {@code true} if the client is required to provide a proof key challenge and verifier, {@code false} otherwise + * @return the {@link ClientSettings} + */ + public ClientSettings requireProofKey(boolean requireProofKey) { + setting(REQUIRE_PROOF_KEY, requireProofKey); + return this; + } + + protected static Map defaultSettings() { + Map settings = new HashMap<>(); + settings.put(REQUIRE_PROOF_KEY, false); + return settings; + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/Settings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/Settings.java new file mode 100644 index 0000000..07ba5c3 --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/Settings.java @@ -0,0 +1,100 @@ +/* + * 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 + * + * https://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.security.oauth2.server.authorization.config; + +import org.springframework.security.core.SpringSecurityCoreVersion2; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * A facility for configuration settings. + * + * @author Joe Grandja + * @since 0.0.2 + */ +public class Settings implements Serializable { + private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID; + private final Map settings; + + /** + * Constructs a {@code Settings}. + */ + public Settings() { + this.settings = new HashMap<>(); + } + + /** + * Constructs a {@code Settings} using the provided parameters. + * + * @param settings the initial settings + */ + public Settings(Map settings) { + Assert.notNull(settings, "settings cannot be null"); + this.settings = new HashMap<>(settings); + } + + /** + * Returns a configuration setting. + * + * @param name the name of the setting + * @param the type of the setting + * @return the value of the setting, or {@code null} if not available + */ + @SuppressWarnings("unchecked") + public T setting(String name) { + Assert.hasText(name, "name cannot be empty"); + return (T) this.settings.get(name); + } + + /** + * Sets a configuration setting. + * + * @param name the name of the setting + * @param value the value of the setting + * @return the {@link Settings} + */ + public Settings setting(String name, Object value) { + Assert.hasText(name, "name cannot be empty"); + Assert.notNull(value, "value cannot be null"); + this.settings.put(name, value); + return this; + } + + /** + * Returns a {@code Map} of the configuration settings. + * + * @return a {@code Map} of the configuration settings + */ + public Map settings() { + return this.settings; + } + + /** + * A {@code Consumer} of the configuration settings {@code Map} + * allowing the ability to add, replace, or remove. + * + * @param settingsConsumer a {@link Consumer} of the configuration settings {@code Map} + * @return the {@link Settings} + */ + public Settings settings(Consumer> settingsConsumer) { + settingsConsumer.accept(this.settings); + return this; + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/TokenSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/TokenSettings.java new file mode 100644 index 0000000..c2fdf43 --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/TokenSettings.java @@ -0,0 +1,74 @@ +/* + * 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 + * + * https://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.security.oauth2.server.authorization.config; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +/** + * A facility for token configuration settings. + * + * @author Joe Grandja + * @since 0.0.2 + * @see Settings + */ +public class TokenSettings extends Settings { + private static final String TOKEN_SETTING_BASE = "spring.security.oauth2.authorization-server.token."; + public static final String ACCESS_TOKEN_TIME_TO_LIVE = TOKEN_SETTING_BASE.concat("access-token-time-to-live"); + + /** + * Constructs a {@code TokenSettings}. + */ + public TokenSettings() { + this(defaultSettings()); + } + + /** + * Constructs a {@code TokenSettings} using the provided parameters. + * + * @param settings the initial settings + */ + public TokenSettings(Map settings) { + super(settings); + } + + /** + * Returns the time-to-live for an access token. The default is 5 minutes. + * + * @return the time-to-live for an access token + */ + public Duration accessTokenTimeToLive() { + return setting(ACCESS_TOKEN_TIME_TO_LIVE); + } + + /** + * Set the time-to-live for an access token. + * + * @param accessTokenTimeToLive the time-to-live for an access token + * @return the {@link TokenSettings} + */ + public TokenSettings accessTokenTimeToLive(Duration accessTokenTimeToLive) { + setting(ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive); + return this; + } + + protected static Map defaultSettings() { + Map settings = new HashMap<>(); + settings.put(ACCESS_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(5)); + return settings; + } +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java index 85a0267..c1d0710 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java @@ -322,6 +322,9 @@ public class RegisteredClientTests { RegisteredClient registration = TestRegisteredClients.registeredClient().build(); RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build(); + assertThat(registration.getId()).isEqualTo(updated.getId()); + assertThat(registration.getClientId()).isEqualTo(updated.getClientId()); + assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret()); assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods()); assertThat(registration.getClientAuthenticationMethods()).isNotSameAs(updated.getClientAuthenticationMethods()); assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes()); @@ -330,20 +333,10 @@ public class RegisteredClientTests { assertThat(registration.getRedirectUris()).isNotSameAs(updated.getRedirectUris()); assertThat(registration.getScopes()).isEqualTo(updated.getScopes()); assertThat(registration.getScopes()).isNotSameAs(updated.getScopes()); - } - - @Test - public void buildWhenRegisteredClientProvidedThenEachPropertyMatches() { - RegisteredClient registration = TestRegisteredClients.registeredClient().build(); - RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build(); - - assertThat(registration.getId()).isEqualTo(updated.getId()); - assertThat(registration.getClientId()).isEqualTo(updated.getClientId()); - assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret()); - assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods()); - assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes()); - assertThat(registration.getRedirectUris()).isEqualTo(updated.getRedirectUris()); - assertThat(registration.getScopes()).isEqualTo(updated.getScopes()); + assertThat(registration.getClientSettings().settings()).isEqualTo(updated.getClientSettings().settings()); + assertThat(registration.getClientSettings()).isNotSameAs(updated.getClientSettings()); + assertThat(registration.getTokenSettings().settings()).isEqualTo(updated.getTokenSettings().settings()); + assertThat(registration.getTokenSettings()).isNotSameAs(updated.getTokenSettings()); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ClientSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ClientSettingsTests.java new file mode 100644 index 0000000..f61cad0 --- /dev/null +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ClientSettingsTests.java @@ -0,0 +1,49 @@ +/* + * 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 + * + * https://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.security.oauth2.server.authorization.config; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link ClientSettings}. + * + * @author Joe Grandja + */ +public class ClientSettingsTests { + + @Test + public void constructorWhenDefaultThenDefaultsAreSet() { + ClientSettings clientSettings = new ClientSettings(); + assertThat(clientSettings.settings()).hasSize(1); + assertThat(clientSettings.requireProofKey()).isFalse(); + } + + @Test + public void constructorWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new ClientSettings(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("settings cannot be null"); + } + + @Test + public void requireProofKeyWhenTrueThenSet() { + ClientSettings clientSettings = new ClientSettings().requireProofKey(true); + assertThat(clientSettings.requireProofKey()).isTrue(); + } +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/SettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/SettingsTests.java new file mode 100644 index 0000000..e3fe6a0 --- /dev/null +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/SettingsTests.java @@ -0,0 +1,78 @@ +/* + * 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 + * + * https://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.security.oauth2.server.authorization.config; + +import org.junit.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; +import static org.assertj.core.api.Assertions.entry; + +/** + * Tests for {@link Settings}. + * + * @author Joe Grandja + */ +public class SettingsTests { + + @Test + public void constructorWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new Settings(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("settings cannot be null"); + } + + @Test + public void constructorWhenSettingsProvidedThenSettingsAreSet() { + Map initialSettings = new HashMap<>(); + initialSettings.put("setting1", "value1"); + initialSettings.put("setting2", "value2"); + + Settings settings = new Settings(initialSettings) + .setting("setting3", "value3") + .settings(s -> s.put("setting4", "value4")); + + assertThat(settings.settings()).contains( + entry("setting1", "value1"), + entry("setting2", "value2"), + entry("setting3", "value3"), + entry("setting4", "value4")); + } + + @Test + public void getSettingWhenNameNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new Settings().setting(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("name cannot be empty"); + } + + @Test + public void setSettingWhenNameNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new Settings().setting(null, "value")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("name cannot be empty"); + } + + @Test + public void setSettingWhenValueNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new Settings().setting("setting", null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("value cannot be null"); + } +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/TokenSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/TokenSettingsTests.java new file mode 100644 index 0000000..2a4e405 --- /dev/null +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/TokenSettingsTests.java @@ -0,0 +1,52 @@ +/* + * 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 + * + * https://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.security.oauth2.server.authorization.config; + +import org.junit.Test; + +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link TokenSettings}. + * + * @author Joe Grandja + */ +public class TokenSettingsTests { + + @Test + public void constructorWhenDefaultThenDefaultsAreSet() { + TokenSettings tokenSettings = new TokenSettings(); + assertThat(tokenSettings.settings()).hasSize(1); + assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5)); + } + + @Test + public void constructorWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new TokenSettings(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("settings cannot be null"); + } + + @Test + public void accessTokenTimeToLiveWhenProvidedThenSet() { + Duration accessTokenTimeToLive = Duration.ofMinutes(10); + TokenSettings tokenSettings = new TokenSettings().accessTokenTimeToLive(accessTokenTimeToLive); + assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive); + } +}