This commit is contained in:
Alexander Schwartz 2026-02-03 14:31:59 +01:00 committed by GitHub
commit d6bf8ec43a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 117 additions and 8 deletions

View file

@ -29,12 +29,20 @@ public class DefaultAlternativeLookupProvider implements AlternativeLookupProvid
}
}
IdentityProviderModel idp = session.identityProviders().getAllStream(IdentityProviderQuery.any())
List<IdentityProviderModel> idps = session.identityProviders().getAllStream(IdentityProviderQuery.any())
.filter(i -> issuerUrl.equals(i.getConfig().get(IdentityProviderModel.ISSUER)))
.findFirst().orElse(null);
if (idp != null && idp.getAlias() != null) {
lookupCache.put(alternativeKey, idp.getAlias());
.limit(2)
.toList();
IdentityProviderModel idp = null;
if (idps.size() == 1) {
idp = idps.get(0);
if (idp.getAlias() != null) {
lookupCache.put(alternativeKey, idp.getAlias());
}
} else if (idps.size() > 1) {
throw new RuntimeException("Multiple IDPs match the same issuer: " + idps.stream().map(IdentityProviderModel::getAlias).toList());
}
return idp;
}

View file

@ -48,21 +48,44 @@ public class FederatedClientAuthConflictsTest {
createIdp("idp1", "http://127.0.0.1:8500");
createIdp("idp2", "http://127.0.0.1:8500");
createClient("myclient", "external1", "idp1");
// Should fail as there are two IdPs with the same issuer URL
AccessTokenResponse response = oAuthClient.clientCredentialsGrantRequest().clientJwt(createDefaultToken("external1", "http://127.0.0.1:8500")).send();
Assertions.assertFalse(response.isSuccess());
Assertions.assertEquals("Invalid client or Invalid client credentials", response.getErrorDescription());
}
@Test
public void testChangedIssuer() {
IdentityProviderRepresentation idp1 = createIdp("idp1", "http://127.0.0.1:8500");
ClientRepresentation clientRep = createClient("myclient", "external1", "idp1");
// Should pass as the first matching IdP by alias is always used
// Should fail as there are two IdPs with the same issuer URL
AccessTokenResponse response = oAuthClient.clientCredentialsGrantRequest().clientJwt(createDefaultToken("external1", "http://127.0.0.1:8500")).send();
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("myclient", events.poll().getClientId());
clientRep.getAttributes().put(FederatedJWTClientAuthenticator.JWT_CREDENTIAL_ISSUER_KEY, "idp2");
createIdp("idp2", "http://127.0.0.1:8500");
clientRep.getAttributes().put(FederatedJWTClientAuthenticator.JWT_CREDENTIAL_ISSUER_KEY, "idp2");
realm.admin().clients().get(clientRep.getId()).update(clientRep);
// Should fail since it's using the second IdP
// Should fail since the old entry is still cached
response = oAuthClient.clientCredentialsGrantRequest().clientJwt(createDefaultToken("external1", "http://127.0.0.1:8500")).send();
Assertions.assertFalse(response.isSuccess());
Assertions.assertNull(events.poll().getClientId());
Assertions.assertEquals("Invalid client or Invalid client credentials", response.getErrorDescription());
// Update old entry, so next read will invalidate it
idp1.getConfig().put(IdentityProviderModel.ISSUER, "http://127.0.0.1:8501");
realm.admin().identityProviders().get(idp1.getAlias()).update(idp1);
// Should succeed as entry is updated in the cache
events.clear();
response = oAuthClient.clientCredentialsGrantRequest().clientJwt(createDefaultToken("external1", "http://127.0.0.1:8500")).send();
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("myclient", events.poll().getClientId());
}
@Test

View file

@ -0,0 +1,78 @@
/*
* Copyright 2026 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.tests.model;
import org.keycloak.cache.AlternativeLookupProvider;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.remote.annotations.TestOnServer;
import org.junit.jupiter.api.Assertions;
@KeycloakIntegrationTest
public class AlternativeLookupProviderTest {
@InjectRealm(attachTo = "master")
ManagedRealm realm;
@TestOnServer
public void testDuplicateIssuerFoundInDatabase(KeycloakSession session) {
try {
IdentityProviderModel idp = createModel("kubernetes1", "kubernetes");
idp.getConfig().put("issuer", "https://localhost");
session.identityProviders().create(idp);
idp = createModel("kubernetes2", "kubernetes");
idp.getConfig().put("issuer", "https://localhost");
session.identityProviders().create(idp);
RuntimeException ex = Assertions.assertThrows(RuntimeException.class, () ->
session.getProvider(AlternativeLookupProvider.class).lookupIdentityProviderFromIssuer(session, "https://localhost")
);
Assertions.assertEquals("Multiple IDPs match the same issuer: [kubernetes1, kubernetes2]", ex.getMessage());
} finally {
session.identityProviders().remove("kubernetes1");
session.identityProviders().remove("kubernetes2");
}
}
protected IdentityProviderModel createModel(String alias, String providerId) {
return createModel(alias, providerId,true);
}
protected IdentityProviderModel createModel(String alias, String providerId, boolean enabled) {
return createModel(alias, alias, providerId, enabled);
}
protected IdentityProviderModel createModel(String alias, String displayName, String providerId, boolean enabled) {
IdentityProviderModel idp = new IdentityProviderModel();
idp.setAlias(alias);
idp.setDisplayName(displayName);
idp.setProviderId(providerId);
idp.setEnabled(enabled);
return idp;
}
}