diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/UnauthenticatedServerOAuth2AuthorizedClientRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/UnauthenticatedServerOAuth2AuthorizedClientRepository.java new file mode 100644 index 0000000000..08c14ed898 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/UnauthenticatedServerOAuth2AuthorizedClientRepository.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2018 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.security.oauth2.client.web.server; + +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +/** + * Provides support for an unauthenticated user. This is useful when running as a process with no + * user associated to it. The implementation ensures that {@link ServerWebExchange} is null and that the + * {@link Authentication} is either null or anonymous to prevent using it incorrectly. + * + * @author Rob Winch + * @since 5.1 + */ +public class UnauthenticatedServerOAuth2AuthorizedClientRepository implements ServerOAuth2AuthorizedClientRepository { + private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + + private Map clientRegistrationIdToAuthorizedClient = new HashMap<>(); + + @Override + public Mono loadAuthorizedClient(String clientRegistrationId, + Authentication authentication, ServerWebExchange serverWebExchange) { + Assert.notNull(clientRegistrationId, "clientRegistrationId cannot be null"); + Assert.isNull(serverWebExchange, "serverWebExchange must be null"); + Assert.isTrue(isUnauthenticated(authentication), "The user " + authentication + " should not be authenticated"); + + return Mono.fromSupplier(() -> (T) this.clientRegistrationIdToAuthorizedClient.get(clientRegistrationId)); + } + + @Override + public Mono saveAuthorizedClient( + OAuth2AuthorizedClient authorizedClient, + Authentication authentication, ServerWebExchange serverWebExchange) { + Assert.notNull(authorizedClient, "authorizedClient cannot be null"); + Assert.isNull(serverWebExchange, "serverWebExchange must be null"); + Assert.isTrue(isUnauthenticated(authentication), "The user " + authentication + " should not be authenticated"); + return Mono.fromRunnable(() -> { + String clientRegistrationId = authorizedClient.getClientRegistration().getRegistrationId(); + this.clientRegistrationIdToAuthorizedClient.put(clientRegistrationId, authorizedClient); + }); + } + + @Override + public Mono removeAuthorizedClient(String clientRegistrationId, Authentication authentication, + ServerWebExchange serverWebExchange) { + Assert.notNull(clientRegistrationId, "clientRegistrationId cannot be null"); + Assert.isNull(serverWebExchange, "serverWebExchange " + serverWebExchange + "must be null"); + Assert.isTrue(isUnauthenticated(authentication), "The user " + authentication + " should not be authenticated"); + return Mono.fromRunnable(() -> { + this.clientRegistrationIdToAuthorizedClient.remove(clientRegistrationId); + }); + } + + private boolean isUnauthenticated(Authentication authentication) { + return authentication == null || this.trustResolver.isAnonymous(authentication); + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/UnauthenticatedServerOAuth2AuthorizedClientRepositoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/UnauthenticatedServerOAuth2AuthorizedClientRepositoryTests.java new file mode 100644 index 0000000000..9ad10dd39a --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/UnauthenticatedServerOAuth2AuthorizedClientRepositoryTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2018 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.security.oauth2.client.web.server; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.*; + +/** + * @author Rob Winch + */ +public class UnauthenticatedServerOAuth2AuthorizedClientRepositoryTests { + private UnauthenticatedServerOAuth2AuthorizedClientRepository repository = + new UnauthenticatedServerOAuth2AuthorizedClientRepository(); + + private ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build(); + + private String clientRegistrationId = this.clientRegistration.getRegistrationId(); + + private ServerWebExchange exchange; + + private Authentication anonymous = new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); + + private Authentication authentication; + + private OAuth2AuthorizedClient authorizedClient; + + @Before + public void setup() { + OAuth2AccessToken token = TestOAuth2AccessTokens.noScopes(); + this.authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, "anonymousUser", token); + } + + // loadAuthorizedClient + + @Test + public void loadAuthorizedClientWhenClientRegistrationIdNullThenIllegalArgumentException() { + this.clientRegistrationId = null; + assertThatThrownBy(() -> this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void loadAuthorizedClientWhenAuthenticationNotNullThenIllegalArgumentException() { + this.authentication = new TestingAuthenticationToken("a", "b", "ROLE_USER"); + assertThatThrownBy(() -> this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void loadAuthorizedClientWhenServerWebExchangeNotNullThenIllegalArgumentException() { + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build()); + assertThatThrownBy(() -> this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void loadAuthorizedClientWhenNotFoundThenEmpty() { + assertThat(this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()).isNull(); + } + + @Test + public void loadAuthorizedClientWhenFoundThenFound() { + this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block(); + + assertThat(this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()).isEqualTo(this.authorizedClient); + } + + @Test + public void loadAuthorizedClientWhenMultipleThenFound() { + ClientRegistration otherClientRegistration = TestClientRegistrations.clientRegistration() + .registrationId("other-client-registration") + .build(); + OAuth2AuthorizedClient otherAuthorizedClient = new OAuth2AuthorizedClient(otherClientRegistration, "anonymousUser", this.authorizedClient.getAccessToken()); + + this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block(); + this.repository.saveAuthorizedClient(otherAuthorizedClient, this.authentication, this.exchange).block(); + + assertThat(this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()).isEqualTo(this.authorizedClient); + } + + @Test + public void loadAuthorizedClientWhenAnonymousThenFound() { + this.authentication = this.anonymous; + this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block(); + + assertThat(this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()).isEqualTo(this.authorizedClient); + } + + // saveAuthorizedClient + + @Test + public void saveAuthorizedClientWhenAuthorizedClientNullThenIllegalArgumentException() { + this.authorizedClient = null; + assertThatThrownBy(() -> this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void saveAuthorizedClientWhenAuthenticationNotNullThenIllegalArgumentException() { + this.authentication = new TestingAuthenticationToken("a", "b", "ROLE_USER"); + assertThatThrownBy(() -> this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void saveAuthorizedClientWhenServerWebExchangeNotNullThenIllegalArgumentException() { + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build()); + assertThatThrownBy(() -> this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + // removeAuthorizedClient + + @Test + public void removeAuthorizedClientWhenClientRegistrationIdNullThenIllegalArgumentException() { + this.clientRegistrationId = null; + assertThatThrownBy(() -> this.repository.removeAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void removeAuthorizedClientWhenAuthenticationNotNullThenIllegalArgumentException() { + this.authentication = new TestingAuthenticationToken("a", "b", "ROLE_USER"); + assertThatThrownBy(() -> this.repository.removeAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void removeAuthorizedClientWhenServerWebExchangeNotNullThenIllegalArgumentException() { + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build()); + assertThatThrownBy(() -> this.repository.removeAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void removeAuthorizedClientWhenFoundThenFound() { + this.repository.saveAuthorizedClient(this.authorizedClient, this.authentication, this.exchange).block(); + this.repository.removeAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block(); + + assertThat(this.repository.loadAuthorizedClient(this.clientRegistrationId, this.authentication, this.exchange).block()).isNull(); + } +}