fix(auth): report unconfigured legacy identity providers

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-07-30 21:13:06 +08:00
parent c9a3933677
commit bbffb3281c
5 changed files with 125 additions and 2 deletions

View file

@ -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<String> findProvidersWithoutTrustedDescriptor(
List<ProviderDescriptor> descriptors) {
Set<String> trustedProviderCodes = descriptors.stream()
.map(ProviderDescriptor::providerCode)
.collect(Collectors.toUnmodifiableSet());
return bindingRepository.findDistinctProviderCodes().stream()
.filter(providerCode ->
!trustedProviderCodes.contains(providerCode))
.sorted()
.toList();
}
}

View file

@ -25,14 +25,17 @@ class ReconciledIdentityProviderCatalog
private final TrustedProviderDescriptorSource descriptorSource;
private final ProviderAuthorityLockService authorityLockService;
private final IdentityBindingPreflightService bindingPreflightService;
private final AtomicReference<List<ProviderDescriptor>>
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<ProviderDescriptor> descriptors) {
try {
List<String> 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<IdentityProviderLoginMethod> listReadyProviders() {
return configuredProviders.get().stream()

View file

@ -18,6 +18,13 @@ import org.springframework.stereotype.Repository;
public interface IdentityBindingRepository extends JpaRepository<IdentityBinding, Long> {
Optional<IdentityBinding> findByProviderCodeAndSubject(String providerCode, String subject);
@Query("""
select distinct binding.providerCode
from IdentityBinding binding
order by binding.providerCode
""")
List<String> findDistinctProviderCodes();
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("""
select binding

View file

@ -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);
}
}

View file

@ -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",