From 3bd0b2a30d2548e4f536fb2911bdb938e7efd9cb Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Wed, 19 Mar 2025 12:48:16 +0300 Subject: [PATCH] Add Support SupplierReactiveClientRegistrationRepository Closes gh-16750 Signed-off-by: Max Batischev --- ...rReactiveClientRegistrationRepository.java | 67 +++++++++++++ ...tiveClientRegistrationRepositoryTests.java | 98 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepository.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepositoryTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepository.java new file mode 100644 index 00000000000..76f2f6a7929 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepository.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2025 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.client.registration; + +import java.util.Iterator; +import java.util.function.Supplier; + +import reactor.core.publisher.Mono; + +import org.springframework.util.Assert; +import org.springframework.util.function.SingletonSupplier; + +/** + * A {@link ReactiveClientRegistrationRepository} that lazily calls to retrieve + * {@link ClientRegistration}(s) when requested. + * + * @author Max Batischev + * @since 6.5 + * @see ReactiveClientRegistrationRepository + * @see ClientRegistration + */ +public final class SupplierReactiveClientRegistrationRepository + implements ReactiveClientRegistrationRepository, Iterable { + + private final Supplier repositorySupplier; + + /** + * Constructs an {@code SupplierReactiveClientRegistrationRepository} using the + * provided parameters. + * @param repositorySupplier the client registration repository supplier + */ + public > SupplierReactiveClientRegistrationRepository( + Supplier repositorySupplier) { + Assert.notNull(repositorySupplier, "repositorySupplier cannot be null"); + this.repositorySupplier = SingletonSupplier.of(repositorySupplier); + } + + @Override + public Mono findByRegistrationId(String registrationId) { + Assert.hasText(registrationId, "registrationId cannot be empty"); + return this.repositorySupplier.get().findByRegistrationId(registrationId); + } + + /** + * Returns an {@code Iterator} of {@link ClientRegistration}. + * @return an {@code Iterator} + */ + @Override + public Iterator iterator() { + return ((Iterable) this.repositorySupplier.get()).iterator(); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepositoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepositoryTests.java new file mode 100644 index 00000000000..cb40fcb058c --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/SupplierReactiveClientRegistrationRepositoryTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2025 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.client.registration; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link SupplierReactiveClientRegistrationRepository}. + * + * @author Max Batischev + */ +@ExtendWith(MockitoExtension.class) +public class SupplierReactiveClientRegistrationRepositoryTests { + + private final ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); + + private final SupplierReactiveClientRegistrationRepository registrationRepository = new SupplierReactiveClientRegistrationRepository( + () -> new InMemoryReactiveClientRegistrationRepository(this.registration)); + + @Mock + private Supplier clientRegistrationRepositorySupplier; + + @Test + void constructWhenNullThenIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> new SupplierReactiveClientRegistrationRepository(null)); + } + + @Test + public void findRegistrationWhenRegistrationIsPresentThenReturns() { + String id = this.registration.getRegistrationId(); + assertThat(this.registrationRepository.findByRegistrationId(id).block()).isEqualTo(this.registration); + } + + @Test + public void findRegistrationWhenRegistrationIsNotPresentThenNull() { + String id = this.registration.getRegistrationId() + "MISSING"; + assertThat(this.registrationRepository.findByRegistrationId(id).block()).isNull(); + } + + @Test + public void findRegistrationWhenNullIdIsPresentThenThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> this.registrationRepository.findByRegistrationId(null).block()); + } + + @Test + public void findRegistrationWhenIdIsPresentThenSingletonSupplierCached() { + given(this.clientRegistrationRepositorySupplier.get()) + .willReturn(new InMemoryReactiveClientRegistrationRepository(this.registration)); + SupplierReactiveClientRegistrationRepository test = new SupplierReactiveClientRegistrationRepository( + this.clientRegistrationRepositorySupplier); + + String id = this.registration.getRegistrationId(); + assertThat(test.findByRegistrationId(id).block()).isEqualTo(this.registration); + + id = this.registration.getRegistrationId(); + assertThat(test.findByRegistrationId(id).block()).isEqualTo(this.registration); + verify(this.clientRegistrationRepositorySupplier, times(1)).get(); + } + + @Test + public void iteratorWhenRemoveThenThrowsUnsupportedOperationException() { + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(this.registrationRepository.iterator()::remove); + } + + @Test + public void iteratorWhenGetThenContainsAll() { + assertThat(this.registrationRepository).containsOnly(this.registration); + } + +}