Fallback to next LDAP/Kerberos provider when not able to find authenticated Kerberos principal (#22531)
closes #22352 #9422
This commit is contained in:
parent
248bb17c28
commit
6f989fc132
11 changed files with 339 additions and 22 deletions
|
@ -92,4 +92,9 @@ public class KerberosConstants {
|
|||
*/
|
||||
public static final String GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME = "gss delegation credential";
|
||||
|
||||
/**
|
||||
* Attribute attached to the credential, which contains authenticated SPNEGO context. This is used in case that some LDAP/Kerberos provider was able to authenticate user via SPNEGO, but wasn't able
|
||||
* to lookup it in his LDAP tree. In this case, LDAP lookup might be performed by other providers in the chain.
|
||||
*/
|
||||
public static final String AUTHENTICATED_SPNEGO_CONTEXT = "authenticatedSpnegoContext";
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ include::topics/templates/release-header.adoc[]
|
|||
== {project_name_full} 23.0.0
|
||||
include::topics/23_0_0.adoc[leveloffset=2]
|
||||
|
||||
== {project_name_full} 22.0.2
|
||||
include::topics/22_0_2.adoc[leveloffset=2]
|
||||
|
||||
== {project_name_full} 22.0.0
|
||||
include::topics/22_0_0.adoc[leveloffset=2]
|
||||
|
||||
|
|
4
docs/documentation/release_notes/topics/22_0_2.adoc
Normal file
4
docs/documentation/release_notes/topics/22_0_2.adoc
Normal file
|
@ -0,0 +1,4 @@
|
|||
= Improvements in LDAP and Kerberos integration
|
||||
|
||||
Keycloak now supports multiple LDAP providers in a realm, which support Kerberos integration with the same Kerberos realm. When an LDAP provider is not able to find the user which was authenticated through
|
||||
Kerberos/SPNEGO, Keycloak ties to fallback to the next LDAP provider.
|
|
@ -190,17 +190,27 @@ public class KerberosFederationProvider implements UserStorageProvider,
|
|||
if (!(input instanceof UserCredentialModel)) return null;
|
||||
UserCredentialModel credential = (UserCredentialModel)input;
|
||||
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {
|
||||
SPNEGOAuthenticator spnegoAuthenticator = (SPNEGOAuthenticator) credential.getNote(KerberosConstants.AUTHENTICATED_SPNEGO_CONTEXT);
|
||||
if (spnegoAuthenticator != null) {
|
||||
logger.debugf("SPNEGO authentication already performed by previous provider. Provider '%s' will try to lookup user with principal kerberos principal '%s'", this, spnegoAuthenticator.getAuthenticatedUsername());
|
||||
} else {
|
||||
String spnegoToken = credential.getChallengeResponse();
|
||||
SPNEGOAuthenticator spnegoAuthenticator = factory.createSPNEGOAuthenticator(spnegoToken, kerberosConfig);
|
||||
spnegoAuthenticator = factory.createSPNEGOAuthenticator(spnegoToken, kerberosConfig);
|
||||
|
||||
spnegoAuthenticator.authenticate();
|
||||
}
|
||||
|
||||
Map<String, String> state = new HashMap<String, String>();
|
||||
Map<String, String> state = new HashMap<>();
|
||||
if (spnegoAuthenticator.isAuthenticated()) {
|
||||
String username = spnegoAuthenticator.getAuthenticatedUsername();
|
||||
UserModel user = findOrCreateAuthenticatedUser(realm, username);
|
||||
if (user == null) {
|
||||
return CredentialValidationOutput.failed();
|
||||
// Adding the authenticated SPNEGO, in case that other LDAP/Kerberos providers in the chain are able to lookup user from their LDAP
|
||||
// This can be the case with more complex setup (like MSAD Forest Trust environment)
|
||||
// Note that SPNEGO authentication cannot be done again by the other provider due the Kerberos replay protection
|
||||
credential.setNote(KerberosConstants.AUTHENTICATED_SPNEGO_CONTEXT, spnegoAuthenticator);
|
||||
|
||||
return CredentialValidationOutput.fallback();
|
||||
} else {
|
||||
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
|
||||
if (delegationCredential != null) {
|
||||
|
@ -216,7 +226,7 @@ public class KerberosFederationProvider implements UserStorageProvider,
|
|||
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.CONTINUE, state);
|
||||
} else {
|
||||
logger.tracef("SPNEGO Handshake not successful");
|
||||
return CredentialValidationOutput.failed();
|
||||
return CredentialValidationOutput.fallback();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -282,4 +292,9 @@ public class KerberosFederationProvider implements UserStorageProvider,
|
|||
|
||||
return validate(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KerberosFederationProvider - " + model.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -725,14 +725,19 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
@Override
|
||||
public CredentialValidationOutput authenticate(RealmModel realm, CredentialInput cred) {
|
||||
if (!(cred instanceof UserCredentialModel)) return CredentialValidationOutput.failed();
|
||||
if (!(cred instanceof UserCredentialModel)) return CredentialValidationOutput.fallback();
|
||||
UserCredentialModel credential = (UserCredentialModel)cred;
|
||||
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {
|
||||
if (kerberosConfig.isAllowKerberosAuthentication()) {
|
||||
SPNEGOAuthenticator spnegoAuthenticator = (SPNEGOAuthenticator) credential.getNote(KerberosConstants.AUTHENTICATED_SPNEGO_CONTEXT);
|
||||
if (spnegoAuthenticator != null) {
|
||||
logger.debugf("SPNEGO authentication already performed by previous provider. Provider '%s' will try to lookup user with principal kerberos principal '%s'", this, spnegoAuthenticator.getAuthenticatedUsername());
|
||||
} else {
|
||||
String spnegoToken = credential.getChallengeResponse();
|
||||
SPNEGOAuthenticator spnegoAuthenticator = factory.createSPNEGOAuthenticator(spnegoToken, kerberosConfig);
|
||||
spnegoAuthenticator = factory.createSPNEGOAuthenticator(spnegoToken, kerberosConfig);
|
||||
|
||||
spnegoAuthenticator.authenticate();
|
||||
}
|
||||
|
||||
Map<String, String> state = new HashMap<>();
|
||||
if (spnegoAuthenticator.isAuthenticated()) {
|
||||
|
@ -743,8 +748,13 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
UserModel user = findOrCreateAuthenticatedUser(realm, username);
|
||||
|
||||
if (user == null) {
|
||||
logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getName());
|
||||
return CredentialValidationOutput.failed();
|
||||
logger.debugf("Kerberos/SPNEGO authentication succeeded with kerberos principal [%s], but couldn't find or create user with federation provider [%s]", username, model.getName());
|
||||
|
||||
// Adding the authenticated SPNEGO, in case that other LDAP/Kerberos providers in the chain are able to lookup user from their LDAP
|
||||
// This can be the case with more complex setup (like MSAD Forest Trust environment)
|
||||
// Note that SPNEGO authentication cannot be done again by the other provider due the Kerberos replay protection
|
||||
credential.setNote(KerberosConstants.AUTHENTICATED_SPNEGO_CONTEXT, spnegoAuthenticator);
|
||||
return CredentialValidationOutput.fallback();
|
||||
} else {
|
||||
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
|
||||
if (delegationCredential != null) {
|
||||
|
@ -760,12 +770,12 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.CONTINUE, state);
|
||||
} else {
|
||||
logger.tracef("SPNEGO Handshake not successful");
|
||||
return CredentialValidationOutput.failed();
|
||||
return CredentialValidationOutput.fallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CredentialValidationOutput.failed();
|
||||
return CredentialValidationOutput.fallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -899,4 +909,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
return ldapQuery.getResultList().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LDAPStorageProvider - " + getModel().getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,11 +146,25 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
credentialAuthenticationStream = Stream.concat(credentialAuthenticationStream,
|
||||
getCredentialProviders(session, CredentialAuthentication.class));
|
||||
|
||||
return credentialAuthenticationStream
|
||||
CredentialValidationOutput result = null;
|
||||
for (CredentialAuthentication credentialAuthentication : credentialAuthenticationStream
|
||||
.filter(credentialAuthentication -> credentialAuthentication.supportsCredentialAuthenticationFor(input.getType()))
|
||||
.map(credentialAuthentication -> credentialAuthentication.authenticate(realm, input))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst().orElse(null);
|
||||
.collect(Collectors.toList())) {
|
||||
CredentialValidationOutput validationOutput = credentialAuthentication.authenticate(realm, input);
|
||||
if (Objects.nonNull(validationOutput)) {
|
||||
CredentialValidationOutput.Status status = validationOutput.getAuthStatus();
|
||||
if (status == CredentialValidationOutput.Status.AUTHENTICATED || status == CredentialValidationOutput.Status.CONTINUE || status == CredentialValidationOutput.Status.FAILED) {
|
||||
logger.tracef("Attempt to authenticate credential '%s' with provider '%s' finished with '%s'.", input.getType(), credentialAuthentication, status);
|
||||
if (status == CredentialValidationOutput.Status.AUTHENTICATED) {
|
||||
logger.tracef("Authenticated user is '%s'", validationOutput.getAuthenticatedUser().getUsername());
|
||||
}
|
||||
result = validationOutput;
|
||||
break;
|
||||
}
|
||||
}
|
||||
logger.tracef("Did not authenticate user by provider '%s' with the credential type '%s'. Will try to fallback to other user storage providers", credentialAuthentication, input.getType());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void deleteInvalidUser(final RealmModel realm, final UserModel user) {
|
||||
|
|
|
@ -38,7 +38,11 @@ public class CredentialValidationOutput {
|
|||
}
|
||||
|
||||
public static CredentialValidationOutput failed() {
|
||||
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.FAILED, new HashMap<String, String>());
|
||||
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.FAILED, new HashMap<>());
|
||||
}
|
||||
|
||||
public static CredentialValidationOutput fallback() {
|
||||
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.FALLBACK, new HashMap<>());
|
||||
}
|
||||
|
||||
public UserModel getAuthenticatedUser() {
|
||||
|
@ -63,6 +67,27 @@ public class CredentialValidationOutput {
|
|||
}
|
||||
|
||||
public enum Status {
|
||||
AUTHENTICATED, FAILED, CONTINUE
|
||||
|
||||
/**
|
||||
* User was successfully authenticated. The {@link #getAuthenticatedUser()} must return authenticated user when this is used
|
||||
*/
|
||||
AUTHENTICATED,
|
||||
|
||||
/**
|
||||
* Federation provider failed to authenticate user. This is typically used when user storage provider recognizes the user, but credentials
|
||||
* are incorrect, so federation provider can mark whole authentication as not successful without eventual fallback to other user storage provider
|
||||
*/
|
||||
FAILED,
|
||||
|
||||
/**
|
||||
* Federation provider was not able to recognize the user. It is possible that credential was valid, but fereration provider was not able to lookup the user in it's storage.
|
||||
* Fallback to other user storage provider in the chain might be possible
|
||||
*/
|
||||
FALLBACK,
|
||||
|
||||
/**
|
||||
* Federation provider did not fully authenticate user. It may be needed to ask user for further challenge to then re-try authentication with same federation provider
|
||||
*/
|
||||
CONTINUE,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ public class UserCredentialModel implements CredentialInput {
|
|||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public void setNote(String key, String value) {
|
||||
public void setNote(String key, Object value) {
|
||||
this.notes.put(key, value);
|
||||
}
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
|
|||
}
|
||||
|
||||
|
||||
protected void assertUser(String expectedUsername, String expectedEmail, String expectedFirstname,
|
||||
protected UserRepresentation assertUser(String expectedUsername, String expectedEmail, String expectedFirstname,
|
||||
String expectedLastname, boolean updateProfileActionExpected) {
|
||||
try {
|
||||
UserRepresentation user = ApiUtil.findUserByUsername(testRealmResource(), expectedUsername);
|
||||
|
@ -294,10 +294,17 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
|
|||
} else {
|
||||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
||||
}
|
||||
return user;
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertUserStorageProvider(UserRepresentation user, String providerName) {
|
||||
if (user.getFederationLink() == null) Assert.fail("Federation link on user " + user.getUsername() + " was null");
|
||||
ComponentRepresentation rep = testRealmResource().components().component(user.getFederationLink()).toRepresentation();
|
||||
Assert.assertEquals(providerName, rep.getName());
|
||||
}
|
||||
|
||||
|
||||
protected OAuthClient.AccessTokenResponse assertAuthenticationSuccess(String codeUrl) throws Exception {
|
||||
List<NameValuePair> pairs = URLEncodedUtils.parse(new URI(codeUrl), "UTF-8");
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2023 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.testsuite.federation.kerberos;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.component.PrioritizedComponentModel;
|
||||
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
|
||||
import org.keycloak.storage.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.KerberosEmbeddedServer;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.KerberosRule;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class KerberosLdapMultipleLDAPProvidersTest extends AbstractKerberosTest {
|
||||
|
||||
private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-ldap-crt-connection.properties";
|
||||
|
||||
@ClassRule
|
||||
public static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION, KerberosEmbeddedServer.DEFAULT_KERBEROS_REALM);
|
||||
|
||||
@ClassRule
|
||||
public static KerberosRule kerberosRule2 = new KerberosRule(PROVIDER_CONFIG_LOCATION, KerberosEmbeddedServer.DEFAULT_KERBEROS_REALM_2);
|
||||
|
||||
|
||||
@Override
|
||||
protected KerberosRule getKerberosRule() {
|
||||
return kerberosRule;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected CommonKerberosConfig getKerberosConfig() {
|
||||
return new LDAPProviderKerberosConfig(getUserStorageConfiguration());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ComponentRepresentation getUserStorageConfiguration() {
|
||||
ComponentRepresentation rep = getUserStorageConfiguration("kerberos-ldap", LDAPStorageProviderFactory.PROVIDER_NAME);
|
||||
// This provider works. It would be executed as 2nd provider (individual tests are supposed to add other provider, which should have lower priority to be invoked first)
|
||||
rep.getConfig().putSingle(PrioritizedComponentModel.PRIORITY, "10");
|
||||
return rep;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test01spnegoWith1stProviderBrokenKerberosConfiguration() throws Exception {
|
||||
// Add LDAP, which is invoked first. The Kerberos configuration is broken, so SPNEGO workflow is failing entirely
|
||||
ComponentRepresentation rep = getUserStorageConfiguration();
|
||||
rep.setName("kerberos-ldap-foo");
|
||||
rep.getConfig().putSingle(PrioritizedComponentModel.PRIORITY, "1");
|
||||
rep.getConfig().putSingle(KerberosConstants.KERBEROS_REALM, "FOO.ORG");
|
||||
rep.getConfig().putSingle(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@FOO.ORG");
|
||||
Response resp = testRealmResource().components().add(rep);
|
||||
getCleanup().addComponentId(ApiUtil.getCreatedId(resp));
|
||||
resp.close();
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = assertSuccessfulSpnegoLogin("hnelson2@KC2.COM", "hnelson2", "secret");
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
Assert.assertEquals(token.getEmail(), "hnelson2@kc2.com");
|
||||
UserRepresentation user = assertUser("hnelson2", "hnelson2@kc2.com", "Horatio", "Nelson", false);
|
||||
assertUserStorageProvider(user, "kerberos-ldap");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02spnegoWith1stProviderBrokenLookupOfKerberosUser() throws Exception {
|
||||
// Add LDAP, which is invoked first. The Kerberos configuration is OK, so SPNEGO workflow should be fine.
|
||||
// However lookup LDAP based on Kerberos principal is broken, so fallback to next provider would be needed
|
||||
ComponentRepresentation rep = getUserStorageConfiguration();
|
||||
rep.setName("kerberos-ldap-broken-lookup");
|
||||
rep.getConfig().putSingle(PrioritizedComponentModel.PRIORITY, "1");
|
||||
rep.getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(mail=nonexistent@email.org)");
|
||||
Response resp = testRealmResource().components().add(rep);
|
||||
getCleanup().addComponentId(ApiUtil.getCreatedId(resp));
|
||||
resp.close();
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = assertSuccessfulSpnegoLogin("hnelson2@KC2.COM", "hnelson2", "secret");
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
Assert.assertEquals(token.getEmail(), "hnelson2@kc2.com");
|
||||
UserRepresentation user = assertUser("hnelson2", "hnelson2@kc2.com", "Horatio", "Nelson", false);
|
||||
assertUserStorageProvider(user, "kerberos-ldap");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright 2023 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.testsuite.federation.kerberos;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.component.PrioritizedComponentModel;
|
||||
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
||||
import org.keycloak.federation.kerberos.KerberosConfig;
|
||||
import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.KerberosEmbeddedServer;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.KerberosRule;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class KerberosStandaloneMultipleProvidersTest extends AbstractKerberosTest {
|
||||
|
||||
private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-standalone-connection.properties";
|
||||
|
||||
|
||||
@ClassRule
|
||||
public static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION, KerberosEmbeddedServer.DEFAULT_KERBEROS_REALM);
|
||||
|
||||
|
||||
@ClassRule
|
||||
public static KerberosRule kerberosRule2 = new KerberosRule(PROVIDER_CONFIG_LOCATION, KerberosEmbeddedServer.DEFAULT_KERBEROS_REALM_2);
|
||||
|
||||
|
||||
@Override
|
||||
protected KerberosRule getKerberosRule() {
|
||||
return kerberosRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommonKerberosConfig getKerberosConfig() {
|
||||
return new KerberosConfig(getUserStorageConfiguration());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ComponentRepresentation getUserStorageConfiguration() {
|
||||
ComponentRepresentation rep = getUserStorageConfiguration("kerberos-standalone", KerberosFederationProviderFactory.PROVIDER_NAME);
|
||||
// This provider works. It would be executed as 2nd provider (individual tests are supposed to add other provider, which should have lower priority to be invoked first)
|
||||
rep.getConfig().putSingle(PrioritizedComponentModel.PRIORITY, "10");
|
||||
return rep;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test01spnegoWith1stProviderBrokenKerberosConfiguration() throws Exception {
|
||||
// Add LDAP, which is invoked first. The Kerberos configuration is broken, so SPNEGO workflow is failing entirely
|
||||
ComponentRepresentation rep = getUserStorageConfiguration();
|
||||
rep.setName("kerberos-foo");
|
||||
rep.getConfig().putSingle(PrioritizedComponentModel.PRIORITY, "1");
|
||||
rep.getConfig().putSingle(KerberosConstants.KERBEROS_REALM, "FOO.ORG");
|
||||
rep.getConfig().putSingle(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@FOO.ORG");
|
||||
Response resp = testRealmResource().components().add(rep);
|
||||
getCleanup().addComponentId(ApiUtil.getCreatedId(resp));
|
||||
resp.close();
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = assertSuccessfulSpnegoLogin("hnelson2@KC2.COM", "hnelson2", "secret");
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
Assert.assertEquals(token.getEmail(), "hnelson2@keycloak.org");
|
||||
UserRepresentation user = assertUser("hnelson2", "hnelson2@keycloak.org", null, null, false);
|
||||
assertUserStorageProvider(user, "kerberos-standalone");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02spnegoWith1stProviderBrokenLookupOfKerberosUser() throws Exception {
|
||||
// Add LDAP, which is invoked first. The Kerberos configuration is OK, so SPNEGO workflow should be fine.
|
||||
// However lookup LDAP based on Kerberos principal is broken, so fallback to next provider would be needed
|
||||
ComponentRepresentation rep = getUserStorageConfiguration();
|
||||
rep.setName("kerberos-ldap-broken-lookup");
|
||||
rep.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
|
||||
rep.getConfig().putSingle(PrioritizedComponentModel.PRIORITY, "1");
|
||||
rep.getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(mail=nonexistent@email.org)");
|
||||
Response resp = testRealmResource().components().add(rep);
|
||||
getCleanup().addComponentId(ApiUtil.getCreatedId(resp));
|
||||
resp.close();
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = assertSuccessfulSpnegoLogin("hnelson2@KC2.COM", "hnelson2", "secret");
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
Assert.assertEquals(token.getEmail(), "hnelson2@keycloak.org");
|
||||
UserRepresentation user = assertUser("hnelson2", "hnelson2@keycloak.org", null, null, false);
|
||||
assertUserStorageProvider(user, "kerberos-standalone");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue