diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/InMemoryReactiveClientRegistrationRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/InMemoryReactiveClientRegistrationRepository.java new file mode 100644 index 0000000000..fa291c6a4f --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/InMemoryReactiveClientRegistrationRepository.java @@ -0,0 +1,82 @@ +/* + * 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.registration; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; + +import reactor.core.publisher.Mono; + +/** + * A Reactive {@link ClientRegistrationRepository} that stores {@link ClientRegistration}(s) in-memory. + * + * @author Rob Winch + * @since 5.1 + * @see ClientRegistrationRepository + * @see ClientRegistration + */ +public final class InMemoryReactiveClientRegistrationRepository + implements ReactiveClientRegistrationRepository, Iterable { + + private final Map clientIdToClientRegistration; + + /** + * Constructs an {@code InMemoryReactiveClientRegistrationRepository} using the provided parameters. + * + * @param registrations the client registration(s) + */ + public InMemoryReactiveClientRegistrationRepository(ClientRegistration... registrations) { + Assert.notEmpty(registrations, "registrations cannot be empty"); + this.clientIdToClientRegistration = new ConcurrentReferenceHashMap<>(); + for (ClientRegistration registration : registrations) { + Assert.notNull(registration, "registrations cannot contain null values"); + this.clientIdToClientRegistration.put(registration.getRegistrationId(), registration); + } + } + + /** + * Constructs an {@code InMemoryReactiveClientRegistrationRepository} using the provided parameters. + * + * @param registrations the client registration(s) + */ + public InMemoryReactiveClientRegistrationRepository(List registrations) { + Assert.notEmpty(registrations, "registrations cannot be null or empty"); + this.clientIdToClientRegistration = registrations.stream() + .collect(Collectors.toConcurrentMap(ClientRegistration::getRegistrationId, Function.identity())); + } + + + @Override + public Mono findByRegistrationId(String registrationId) { + return Mono.justOrEmpty(this.clientIdToClientRegistration.get(registrationId)); + } + + /** + * Returns an {@code Iterator} of {@link ClientRegistration}. + * + * @return an {@code Iterator} + */ + @Override + public Iterator iterator() { + return this.clientIdToClientRegistration.values().iterator(); + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ReactiveClientRegistrationRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ReactiveClientRegistrationRepository.java new file mode 100644 index 0000000000..e9a9575676 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ReactiveClientRegistrationRepository.java @@ -0,0 +1,45 @@ +/* + * 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.registration; + +import reactor.core.publisher.Mono; + +/** + * A reactive repository for OAuth 2.0 / OpenID Connect 1.0 {@link ClientRegistration}(s). + * + *

+ * NOTE: Client registration information is ultimately stored and owned + * by the associated Authorization Server. + * Therefore, this repository provides the capability to store a sub-set copy + * of the primary client registration information + * externally from the Authorization Server. + * + * @author Rob Winch + * @since 5.1 + * @see ClientRegistration + */ +public interface ReactiveClientRegistrationRepository { + + /** + * Returns the client registration identified by the provided {@code registrationId}, or {@code null} if not found. + * + * @param registrationId the registration identifier + * @return the {@link ClientRegistration} if found, otherwise {@code null} + */ + Mono findByRegistrationId(String registrationId); + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/InMemoryReactiveClientRegistrationRepositoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/InMemoryReactiveClientRegistrationRepositoryTests.java new file mode 100644 index 0000000000..601018c3fb --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/InMemoryReactiveClientRegistrationRepositoryTests.java @@ -0,0 +1,103 @@ +/* + * 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.registration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; + +import reactor.test.StepVerifier; + +/** + * @author Rob Winch + * @since 5.1 + */ +public class InMemoryReactiveClientRegistrationRepositoryTests { + + private ClientRegistration github = ClientRegistration.withRegistrationId("github") + .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read:user") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri("https://github.com/login/oauth/access_token") + .userInfoUri("https://api.github.com/user") + .userNameAttributeName("id") + .clientName("GitHub") + .clientId("clientId") + .clientSecret("clientSecret") + .build(); + + private InMemoryReactiveClientRegistrationRepository repository; + + @Before + public void setup() { + this.repository = new InMemoryReactiveClientRegistrationRepository(this.github); + } + + @Test + public void constructorWhenZeroVarArgsThenIllegalArgumentException() { + assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructorWhenClientRegistrationArrayThenIllegalArgumentException() { + ClientRegistration[] registrations = null; + assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registrations)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructorWhenClientRegistrationListThenIllegalArgumentException() { + List registrations = null; + assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registrations)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructorWhenClientRegistrationIsNullThenIllegalArgumentException() { + ClientRegistration registration = null; + assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registration)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void findByRegistrationIdWhenValidIdThenFound() { + StepVerifier.create(this.repository.findByRegistrationId(this.github.getRegistrationId())) + .expectNext(this.github) + .verifyComplete(); + } + + @Test + public void findByRegistrationIdWhenNotValidIdThenEmpty() { + StepVerifier.create(this.repository.findByRegistrationId(this.github.getRegistrationId() + "invalid")) + .verifyComplete(); + } + + @Test + public void iteratorWhenContainsGithubThenContains() { + assertThat(this.repository.iterator()) + .containsOnly(this.github); + } +}