From bbffb3281c333126f3afd0e20bd1f9ddcfa0f8b9 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:13:06 +0800 Subject: [PATCH] fix(auth): report unconfigured legacy identity providers Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- .../IdentityBindingPreflightService.java | 32 ++++++++++++ .../ReconciledIdentityProviderCatalog.java | 23 ++++++++- .../repository/IdentityBindingRepository.java | 7 +++ .../IdentityBindingPreflightServiceTest.java | 50 +++++++++++++++++++ ...ReconciledIdentityProviderCatalogTest.java | 15 +++++- 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightService.java create mode 100644 server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightServiceTest.java diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightService.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightService.java new file mode 100644 index 00000000..5c4aca69 --- /dev/null +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightService.java @@ -0,0 +1,32 @@ +package com.iflytek.skillhub.auth.identity; + +import com.iflytek.skillhub.auth.repository.IdentityBindingRepository; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +class IdentityBindingPreflightService { + + private final IdentityBindingRepository bindingRepository; + + IdentityBindingPreflightService( + IdentityBindingRepository bindingRepository) { + this.bindingRepository = bindingRepository; + } + + @Transactional(readOnly = true) + public List findProvidersWithoutTrustedDescriptor( + List descriptors) { + Set trustedProviderCodes = descriptors.stream() + .map(ProviderDescriptor::providerCode) + .collect(Collectors.toUnmodifiableSet()); + return bindingRepository.findDistinctProviderCodes().stream() + .filter(providerCode -> + !trustedProviderCodes.contains(providerCode)) + .sorted() + .toList(); + } +} diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalog.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalog.java index eb51a822..9c87cdb9 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalog.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalog.java @@ -25,14 +25,17 @@ class ReconciledIdentityProviderCatalog private final TrustedProviderDescriptorSource descriptorSource; private final ProviderAuthorityLockService authorityLockService; + private final IdentityBindingPreflightService bindingPreflightService; private final AtomicReference> configuredProviders = new AtomicReference<>(List.of()); ReconciledIdentityProviderCatalog( TrustedProviderDescriptorSource descriptorSource, - ProviderAuthorityLockService authorityLockService) { + ProviderAuthorityLockService authorityLockService, + IdentityBindingPreflightService bindingPreflightService) { this.descriptorSource = descriptorSource; this.authorityLockService = authorityLockService; + this.bindingPreflightService = bindingPreflightService; } @Override @@ -53,6 +56,7 @@ class ReconciledIdentityProviderCatalog return; } configuredProviders.set(descriptors); + reportUnconfiguredBindingProviders(descriptors); for (ProviderDescriptor descriptor : descriptors) { try { authorityLockService.requirePinnedAuthority(descriptor); @@ -70,6 +74,23 @@ class ReconciledIdentityProviderCatalog } } + private void reportUnconfiguredBindingProviders( + List descriptors) { + try { + List unconfiguredProviders = bindingPreflightService + .findProvidersWithoutTrustedDescriptor(descriptors); + if (!unconfiguredProviders.isEmpty()) { + log.error( + "Identity binding preflight found provider codes without a trusted descriptor: {}", + unconfiguredProviders); + } + } catch (RuntimeException exception) { + log.error( + "Identity binding provider preflight failed", + exception); + } + } + @Override public List listReadyProviders() { return configuredProviders.get().stream() diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/IdentityBindingRepository.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/IdentityBindingRepository.java index 0d6067d8..fc0414a5 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/IdentityBindingRepository.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/IdentityBindingRepository.java @@ -18,6 +18,13 @@ import org.springframework.stereotype.Repository; public interface IdentityBindingRepository extends JpaRepository { Optional findByProviderCodeAndSubject(String providerCode, String subject); + @Query(""" + select distinct binding.providerCode + from IdentityBinding binding + order by binding.providerCode + """) + List findDistinctProviderCodes(); + @Lock(LockModeType.PESSIMISTIC_WRITE) @Query(""" select binding diff --git a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightServiceTest.java b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightServiceTest.java new file mode 100644 index 00000000..22c33190 --- /dev/null +++ b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/IdentityBindingPreflightServiceTest.java @@ -0,0 +1,50 @@ +package com.iflytek.skillhub.auth.identity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.iflytek.skillhub.auth.repository.IdentityBindingRepository; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class IdentityBindingPreflightServiceTest { + + @Test + void reportsHistoricalProviderCodesWithoutTrustedDescriptors() { + IdentityBindingRepository bindingRepository = + mock(IdentityBindingRepository.class); + when(bindingRepository.findDistinctProviderCodes()) + .thenReturn(List.of( + "removed-provider", + "github", + "ambiguous-provider")); + IdentityBindingPreflightService service = + new IdentityBindingPreflightService( + bindingRepository); + + assertThat(service.findProvidersWithoutTrustedDescriptor( + List.of(descriptor("github")))) + .containsExactly( + "ambiguous-provider", + "removed-provider"); + } + + private static ProviderDescriptor descriptor(String providerCode) { + return new ProviderDescriptor( + providerCode, + "oidc", + "https://" + providerCode + ".example", + providerCode, + "oidc_sub", + "oidc_sub", + Map.of( + "oidc_sub", + SubjectCanonicalizer.EXACT), + List.of("name"), + List.of("email"), + List.of("picture"), + EmailAssurance.VERIFIED); + } +} diff --git a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalogTest.java b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalogTest.java index 954ff17a..647925b4 100644 --- a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalogTest.java +++ b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/identity/ReconciledIdentityProviderCatalogTest.java @@ -1,10 +1,12 @@ package com.iflytek.skillhub.auth.identity; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; @@ -17,15 +19,22 @@ class ReconciledIdentityProviderCatalogTest { private TrustedProviderDescriptorSource descriptorSource; private ProviderAuthorityLockService authorityLockService; + private IdentityBindingPreflightService bindingPreflightService; private ReconciledIdentityProviderCatalog catalog; @BeforeEach void setUp() { descriptorSource = mock(TrustedProviderDescriptorSource.class); authorityLockService = mock(ProviderAuthorityLockService.class); + bindingPreflightService = mock( + IdentityBindingPreflightService.class); + when(bindingPreflightService + .findProvidersWithoutTrustedDescriptor(anyList())) + .thenReturn(List.of()); catalog = new ReconciledIdentityProviderCatalog( descriptorSource, - authorityLockService); + authorityLockService, + bindingPreflightService); } @Test @@ -37,6 +46,10 @@ class ReconciledIdentityProviderCatalogTest { catalog.reconcile(); + verify(bindingPreflightService) + .findProvidersWithoutTrustedDescriptor( + List.of(github)); + assertThat(catalog.listReadyProviders()) .containsExactly(new IdentityProviderLoginMethod( "github",