From 9dd5ecd46400cf362b43170ec2b80b75a87487a6 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 19 Feb 2015 20:45:45 +0100 Subject: [PATCH 1/3] Kerberos automated test --- dependencies/server-all/pom.xml | 10 +- .../kerberos/CommonKerberosConfig.java | 10 +- .../federation/kerberos/KerberosConfig.java | 8 +- .../kerberos/KerberosFederationProvider.java | 2 +- ...KerberosUsernamePasswordAuthenticator.java | 51 +++- .../ldap/LDAPFederationProvider.java | 6 +- .../kerberos/LDAPProviderKerberosConfig.java | 3 +- .../models/{utils => }/KerberosConstants.java | 16 +- .../org/keycloak/models/LDAPConstants.java | 2 + .../managers/HttpAuthenticationManager.java | 18 +- .../admin/UserFederationResource.java | 2 +- testsuite/integration/pom.xml | 5 + .../testutils/ldap/LDAPConfiguration.java | 210 ++++++-------- .../kerberos-ldap-connection.properties | 17 ++ .../kerberos-standalone-connection.properties | 6 + .../main/resources/kerberos/test-krb5.conf | 17 ++ .../resources/kerberos/users-kerberos.ldif | 8 +- .../resources/ldap/ldap-connection.properties | 1 - .../src/main/resources/log4j.properties | 2 +- .../federation/AbstractKerberosTest.java | 262 ++++++++++++++++++ .../FederationProvidersIntegrationTest.java | 15 +- .../federation/KerberosLdapTest.java | 147 ++++++++++ .../federation/KerberosStandaloneTest.java | 124 +++++++++ .../KeycloakSPNegoSchemeFactory.java | 100 +++++++ .../SyncProvidersTest.java | 7 +- .../keycloak/testsuite/rule/KerberosRule.java | 39 +++ .../org/keycloak/testsuite/rule/LDAPRule.java | 21 +- testsuite/jetty/jetty81/pom.xml | 5 + testsuite/jetty/jetty91/pom.xml | 5 + testsuite/jetty/jetty92/pom.xml | 5 + testsuite/proxy/pom.xml | 5 + testsuite/tomcat6/pom.xml | 5 + testsuite/tomcat7/pom.xml | 5 + testsuite/tomcat8/pom.xml | 5 + 34 files changed, 966 insertions(+), 178 deletions(-) rename model/api/src/main/java/org/keycloak/models/{utils => }/KerberosConstants.java (65%) create mode 100644 testsuite/integration/src/main/resources/kerberos/kerberos-ldap-connection.properties create mode 100644 testsuite/integration/src/main/resources/kerberos/kerberos-standalone-connection.properties create mode 100644 testsuite/integration/src/main/resources/kerberos/test-krb5.conf create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java rename testsuite/integration/src/test/java/org/keycloak/testsuite/{forms => federation}/FederationProvidersIntegrationTest.java (96%) create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java rename testsuite/integration/src/test/java/org/keycloak/testsuite/{forms => federation}/SyncProvidersTest.java (97%) create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index f697926c71..339afad74c 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -93,11 +93,6 @@ keycloak-broker-saml ${project.version} - - org.keycloak - keycloak-kerberos-federation - ${project.version} - org.keycloak keycloak-social-github @@ -129,6 +124,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.picketlink picketlink-common diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java index 6fe528c351..04b498e48f 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java @@ -3,7 +3,7 @@ package org.keycloak.federation.kerberos; import java.util.Map; import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.utils.KerberosConstants; +import org.keycloak.models.KerberosConstants; /** * Common configuration useful for all providers @@ -24,19 +24,19 @@ public abstract class CommonKerberosConfig { } public String getKerberosRealm() { - return getConfig().get("kerberosRealm"); + return getConfig().get(KerberosConstants.KERBEROS_REALM); } public String getServerPrincipal() { - return getConfig().get("serverPrincipal"); + return getConfig().get(KerberosConstants.SERVER_PRINCIPAL); } public String getKeyTab() { - return getConfig().get("keyTab"); + return getConfig().get(KerberosConstants.KEYTAB); } public boolean getDebug() { - return Boolean.valueOf(getConfig().get("debug")); + return Boolean.valueOf(getConfig().get(KerberosConstants.DEBUG)); } protected Map getConfig() { diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java index 950f9cd38a..c1f59b76ec 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java @@ -1,5 +1,7 @@ package org.keycloak.federation.kerberos; +import org.keycloak.models.KerberosConstants; +import org.keycloak.models.LDAPConstants; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; @@ -15,7 +17,7 @@ public class KerberosConfig extends CommonKerberosConfig { } public UserFederationProvider.EditMode getEditMode() { - String editModeString = getConfig().get("editMode"); + String editModeString = getConfig().get(LDAPConstants.EDIT_MODE); if (editModeString == null) { return UserFederationProvider.EditMode.UNSYNCED; } else { @@ -24,11 +26,11 @@ public class KerberosConfig extends CommonKerberosConfig { } public boolean isAllowPasswordAuthentication() { - return Boolean.valueOf(getConfig().get("allowPasswordAuthentication")); + return Boolean.valueOf(getConfig().get(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION)); } public boolean isUpdateProfileFirstLogin() { - return Boolean.valueOf(getConfig().get("updateProfileFirstLogin")); + return Boolean.valueOf(getConfig().get(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN)); } } diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java index 35f58859d6..b0d23dcffa 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java @@ -20,7 +20,7 @@ import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KerberosConstants; +import org.keycloak.models.KerberosConstants; /** * @author Marek Posolda diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java index 6515950d35..c8e6fa040d 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; @@ -25,11 +26,13 @@ public class KerberosUsernamePasswordAuthenticator { private static final Logger logger = Logger.getLogger(KerberosUsernamePasswordAuthenticator.class); private final CommonKerberosConfig config; + private LoginContext loginContext; public KerberosUsernamePasswordAuthenticator(CommonKerberosConfig config) { this.config = config; } + /** * Returns true if user with given username exists in kerberos database * @@ -41,7 +44,7 @@ public class KerberosUsernamePasswordAuthenticator { logger.debug("Checking existence of principal: " + principal); try { - LoginContext loginContext = new LoginContext("does-not-matter", null, + loginContext = new LoginContext("does-not-matter", null, createJaasCallbackHandler(principal, "fake-password-which-nobody-has"), createJaasConfiguration()); @@ -58,6 +61,7 @@ public class KerberosUsernamePasswordAuthenticator { } } + /** * Returns true if user was successfully authenticated against Kerberos * @@ -66,18 +70,9 @@ public class KerberosUsernamePasswordAuthenticator { * @return true if user was successfully authenticated */ public boolean validUser(String username, String password) { - String principal = getKerberosPrincipal(username); - - logger.debug("Validating password of principal: " + principal); try { - LoginContext loginContext = new LoginContext("does-not-matter", null, - createJaasCallbackHandler(principal, password), - createJaasConfiguration()); - - loginContext.login(); - logger.debug("Principal " + principal + " authenticated succesfully"); - - loginContext.logout(); + authenticateSubject(username, password); + logoutSubject(); return true; } catch (LoginException le) { logger.debug("Failed to authenticate user " + username, le); @@ -86,6 +81,38 @@ public class KerberosUsernamePasswordAuthenticator { } + /** + * Returns true if user was successfully authenticated against Kerberos + * + * @param username username without Kerberos realm attached + * @param password kerberos password + * @return true if user was successfully authenticated + */ + public Subject authenticateSubject(String username, String password) throws LoginException { + String principal = getKerberosPrincipal(username); + + logger.debug("Validating password of principal: " + principal); + loginContext = new LoginContext("does-not-matter", null, + createJaasCallbackHandler(principal, password), + createJaasConfiguration()); + + loginContext.login(); + logger.debug("Principal " + principal + " authenticated succesfully"); + return loginContext.getSubject(); + } + + public void logoutSubject() { + if (loginContext != null) { + try { + loginContext.logout(); + } catch (LoginException le) { + logger.error("Failed to logout kerberos server subject: " + config.getServerPrincipal(), le); + } + } + } + + + protected String getKerberosPrincipal(String username) { return username + "@" + config.getKerberosRealm(); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index 9a926041ca..3e2237df53 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -6,6 +6,7 @@ import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; @@ -14,7 +15,7 @@ import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KerberosConstants; +import org.keycloak.models.KerberosConstants; import org.picketlink.idm.IdentityManagementException; import org.picketlink.idm.IdentityManager; import org.picketlink.idm.PartitionManager; @@ -39,7 +40,6 @@ public class LDAPFederationProvider implements UserFederationProvider { private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class); public static final String LDAP_ID = "LDAP_ID"; public static final String SYNC_REGISTRATIONS = "syncRegistrations"; - public static final String EDIT_MODE = "editMode"; protected LDAPFederationProviderFactory factory; protected KeycloakSession session; @@ -56,7 +56,7 @@ public class LDAPFederationProvider implements UserFederationProvider { this.model = model; this.partitionManager = partitionManager; this.kerberosConfig = new LDAPProviderKerberosConfig(model); - String editModeString = model.getConfig().get(EDIT_MODE); + String editModeString = model.getConfig().get(LDAPConstants.EDIT_MODE); if (editModeString == null) { editMode = EditMode.READ_ONLY; } else { diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java index b98c5cae71..4363ce927c 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java @@ -1,6 +1,7 @@ package org.keycloak.federation.ldap.kerberos; import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.models.KerberosConstants; import org.keycloak.models.UserFederationProviderModel; /** @@ -15,6 +16,6 @@ public class LDAPProviderKerberosConfig extends CommonKerberosConfig { } public boolean isUseKerberosForPasswordAuthentication() { - return Boolean.valueOf(getConfig().get("useKerberosForPasswordAuthentication")); + return Boolean.valueOf(getConfig().get(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION)); } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/KerberosConstants.java b/model/api/src/main/java/org/keycloak/models/KerberosConstants.java similarity index 65% rename from model/api/src/main/java/org/keycloak/models/utils/KerberosConstants.java rename to model/api/src/main/java/org/keycloak/models/KerberosConstants.java index 6f8a830a1f..b2a676e8b3 100644 --- a/model/api/src/main/java/org/keycloak/models/utils/KerberosConstants.java +++ b/model/api/src/main/java/org/keycloak/models/KerberosConstants.java @@ -1,4 +1,4 @@ -package org.keycloak.models.utils; +package org.keycloak.models; /** * @author Marek Posolda @@ -10,26 +10,38 @@ public class KerberosConstants { **/ public static final String NEGOTIATE = "Negotiate"; + /** * OID of SPNEGO mechanism. See http://www.oid-info.com/get/1.3.6.1.5.5.2 */ public static final String SPNEGO_OID = "1.3.6.1.5.5.2"; + /** * OID of Kerberos v5 mechanism. See http://www.oid-info.com/get/1.2.840.113554.1.2.2 */ public static final String KRB5_OID = "1.2.840.113554.1.2.2"; + /** - * Configuration federation provider model attribute. It's always true for KerberosFederationProvider and configurable for LDAPFederationProvider + * Configuration federation provider model attributes. */ public static final String ALLOW_KERBEROS_AUTHENTICATION = "allowKerberosAuthentication"; + public static final String KERBEROS_REALM = "kerberosRealm"; + public static final String SERVER_PRINCIPAL = "serverPrincipal"; + public static final String KEYTAB = "keyTab"; + public static final String DEBUG = "debug"; + public static final String ALLOW_PASSWORD_AUTHENTICATION = "allowPasswordAuthentication"; + public static final String UPDATE_PROFILE_FIRST_LOGIN = "updateProfileFirstLogin"; + public static final String USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION = "useKerberosForPasswordAuthentication"; + /** * Internal attribute used in "state" map . Contains token to be passed in HTTP Response back to browser to continue handshake */ public static final String RESPONSE_TOKEN = "SpnegoResponseToken"; + /** * Internal attribute used in "state" map . Contains credential from SPNEGO/Kerberos successful authentication */ diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java index 97651fc878..bf76be7be9 100644 --- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java +++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java @@ -23,6 +23,8 @@ public class LDAPConstants { public static final String CONNECTION_POOLING = "connectionPooling"; public static final String PAGINATION = "pagination"; + public static final String EDIT_MODE = "editMode"; + // Count of users processed per single transaction during sync process public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync"; public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000; diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java index ca0488e27b..34ed9008d1 100644 --- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java @@ -7,6 +7,7 @@ import javax.ws.rs.core.UriInfo; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; +import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.login.LoginFormsProvider; @@ -18,7 +19,7 @@ import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; -import org.keycloak.models.utils.KerberosConstants; +import org.keycloak.models.KerberosConstants; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.messages.Messages; @@ -59,11 +60,15 @@ public class HttpAuthenticationManager { boolean kerberosSupported = false; for (RequiredCredentialModel c : realm.getRequiredCredentials()) { if (c.getType().equals(CredentialRepresentation.KERBEROS)) { - logger.debug("Kerberos authentication is supported"); kerberosSupported = true; } } + if (logger.isTraceEnabled()) { + String log = kerberosSupported ? "SPNEGO authentication is supported" : "SPNEGO authentication is not supported"; + logger.trace(log); + } + if (!kerberosSupported) { return new HttpAuthOutput(null, null); } @@ -100,6 +105,10 @@ public class HttpAuthenticationManager { // Send response after successful authentication private HttpAuthOutput sendResponse(UserModel user, String authMethod) { + if (logger.isTraceEnabled()) { + logger.trace("User " + user.getUsername() + " authenticated with " + authMethod); + } + Response response; if (!user.isEnabled()) { event.error(Errors.USER_DISABLED); @@ -107,7 +116,10 @@ public class HttpAuthenticationManager { } else { UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false); TokenManager.attachClientSession(userSession, clientSession); - event.session(userSession); + event.user(user) + .session(userSession) + .detail(Details.AUTH_METHOD, authMethod) + .detail(Details.USERNAME, user.getUsername()); response = AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java index 730252cda1..04e3c7f9f4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java @@ -10,7 +10,7 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.utils.KerberosConstants; +import org.keycloak.models.KerberosConstants; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation; diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 98697e5497..d58465052c 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -123,6 +123,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java index 1312143306..63445b2a39 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java @@ -1,55 +1,88 @@ package org.keycloak.testutils.ldap; +import java.io.File; import java.io.InputStream; +import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import org.jboss.logging.Logger; +import org.keycloak.models.KerberosConstants; import org.keycloak.models.LDAPConstants; +import org.keycloak.models.UserFederationProvider; /** * @author Marek Posolda */ public class LDAPConfiguration { - public static final String CONNECTION_PROPERTIES = "ldap/ldap-connection.properties"; + private static final Logger log = Logger.getLogger(LDAPConfiguration.class); - protected String connectionUrl = "ldap://localhost:10389"; - protected String baseDn = "dc=keycloak,dc=org"; - protected String userDnSuffix = "ou=People,dc=keycloak,dc=org"; - protected String rolesDnSuffix = "ou=Roles,dc=keycloak,dc=org"; - protected String groupDnSuffix = "ou=Groups,dc=keycloak,dc=org"; - protected String agentDnSuffix = "ou=Agent,dc=keycloak,dc=org"; - protected boolean startEmbeddedLdapLerver = true; - protected String bindDn = "uid=admin,ou=system"; - protected String bindCredential = "secret"; - protected String vendor = LDAPConstants.VENDOR_OTHER; - protected boolean connectionPooling = true; - protected boolean pagination = true; - protected int batchSizeForSync = LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC; - protected String usernameLDAPAttribute; - protected String userObjectClasses; - protected boolean userAccountControlsAfterPasswordUpdate; + private String connectionPropertiesLocation; + private boolean startEmbeddedLdapLerver = true; + private Map config; - public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url"; - public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn"; - public static String IDM_TEST_LDAP_ROLES_DN_SUFFIX = "idm.test.ldap.roles.dn.suffix"; - public static String IDM_TEST_LDAP_GROUP_DN_SUFFIX = "idm.test.ldap.group.dn.suffix"; - public static String IDM_TEST_LDAP_USER_DN_SUFFIX = "idm.test.ldap.user.dn.suffix"; - public static String IDM_TEST_LDAP_AGENT_DN_SUFFIX = "idm.test.ldap.agent.dn.suffix"; - public static String IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER = "idm.test.ldap.start.embedded.ldap.server"; - public static String IDM_TEST_LDAP_BIND_DN = "idm.test.ldap.bind.dn"; - public static String IDM_TEST_LDAP_BIND_CREDENTIAL = "idm.test.ldap.bind.credential"; - public static String IDM_TEST_LDAP_VENDOR = "idm.test.ldap.vendor"; - public static String IDM_TEST_LDAP_CONNECTION_POOLING = "idm.test.ldap.connection.pooling"; - public static String IDM_TEST_LDAP_PAGINATION = "idm.test.ldap.pagination"; - public static String IDM_TEST_LDAP_BATCH_SIZE_FOR_SYNC = "idm.test.ldap.batch.size.for.sync"; - public static String IDM_TEST_LDAP_USERNAME_LDAP_ATTRIBUTE = "idm.test.ldap.username.ldap.attribute"; - public static String IDM_TEST_LDAP_USER_OBJECT_CLASSES = "idm.test.ldap.user.object.classes"; - public static String IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "idm.test.ldap.user.account.controls.after.password.update"; + protected static final Map PROP_MAPPINGS = new HashMap(); + protected static final Map DEFAULT_VALUES = new HashMap(); - public static LDAPConfiguration readConfiguration() { + static { + PROP_MAPPINGS.put(LDAPConstants.CONNECTION_URL, "idm.test.ldap.connection.url"); + PROP_MAPPINGS.put(LDAPConstants.BASE_DN, "idm.test.ldap.base.dn"); + PROP_MAPPINGS.put("rolesDnSuffix", "idm.test.ldap.roles.dn.suffix"); + PROP_MAPPINGS.put("groupDnSuffix", "idm.test.ldap.group.dn.suffix"); + PROP_MAPPINGS.put(LDAPConstants.USER_DN_SUFFIX, "idm.test.ldap.user.dn.suffix"); + PROP_MAPPINGS.put(LDAPConstants.BIND_DN, "idm.test.ldap.bind.dn"); + PROP_MAPPINGS.put(LDAPConstants.BIND_CREDENTIAL, "idm.test.ldap.bind.credential"); + PROP_MAPPINGS.put(LDAPConstants.VENDOR, "idm.test.ldap.vendor"); + PROP_MAPPINGS.put(LDAPConstants.CONNECTION_POOLING, "idm.test.ldap.connection.pooling"); + PROP_MAPPINGS.put(LDAPConstants.PAGINATION, "idm.test.ldap.pagination"); + PROP_MAPPINGS.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "idm.test.ldap.batch.size.for.sync"); + PROP_MAPPINGS.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "idm.test.ldap.username.ldap.attribute"); + PROP_MAPPINGS.put(LDAPConstants.USER_OBJECT_CLASSES, "idm.test.ldap.user.object.classes"); + PROP_MAPPINGS.put(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE, "idm.test.ldap.user.account.controls.after.password.update"); + PROP_MAPPINGS.put(LDAPConstants.EDIT_MODE, "idm.test.ldap.edit.mode"); + + PROP_MAPPINGS.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "idm.test.kerberos.allow.kerberos.authentication"); + PROP_MAPPINGS.put(KerberosConstants.KERBEROS_REALM, "idm.test.kerberos.realm"); + PROP_MAPPINGS.put(KerberosConstants.SERVER_PRINCIPAL, "idm.test.kerberos.server.principal"); + PROP_MAPPINGS.put(KerberosConstants.KEYTAB, "idm.test.kerberos.keytab"); + PROP_MAPPINGS.put(KerberosConstants.DEBUG, "idm.test.kerberos.debug"); + PROP_MAPPINGS.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "idm.test.kerberos.allow.password.authentication"); + PROP_MAPPINGS.put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "idm.test.kerberos.update.profile.first.login"); + PROP_MAPPINGS.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "idm.test.kerberos.use.kerberos.for.password.authentication"); + + DEFAULT_VALUES.put(LDAPConstants.CONNECTION_URL, "ldap://localhost:10389"); + DEFAULT_VALUES.put(LDAPConstants.BASE_DN, "dc=keycloak,dc=org"); + DEFAULT_VALUES.put("rolesDnSuffix", "ou=Roles,dc=keycloak,dc=org"); + DEFAULT_VALUES.put("groupDnSuffix", "ou=Groups,dc=keycloak,dc=org"); + DEFAULT_VALUES.put(LDAPConstants.USER_DN_SUFFIX, "ou=People,dc=keycloak,dc=org"); + DEFAULT_VALUES.put(LDAPConstants.BIND_DN, "uid=admin,ou=system"); + DEFAULT_VALUES.put(LDAPConstants.BIND_CREDENTIAL, "secret"); + DEFAULT_VALUES.put(LDAPConstants.VENDOR, LDAPConstants.VENDOR_OTHER); + DEFAULT_VALUES.put(LDAPConstants.CONNECTION_POOLING, "true"); + DEFAULT_VALUES.put(LDAPConstants.PAGINATION, "true"); + DEFAULT_VALUES.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, String.valueOf(LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC)); + DEFAULT_VALUES.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, null); + DEFAULT_VALUES.put(LDAPConstants.USER_OBJECT_CLASSES, null); + DEFAULT_VALUES.put(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE, "false"); + DEFAULT_VALUES.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString()); + + DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false"); + DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG"); + DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG"); + URL keytabUrl = LDAPConfiguration.class.getResource("/kerberos/http.keytab"); + String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath(); + DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath); + DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true"); + DEFAULT_VALUES.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "true"); + DEFAULT_VALUES.put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true"); + DEFAULT_VALUES.put(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION, "false"); + } + + public static LDAPConfiguration readConfiguration(String connectionPropertiesLocation) { LDAPConfiguration ldapConfiguration = new LDAPConfiguration(); + ldapConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation); ldapConfiguration.loadConnectionProperties(); return ldapConfiguration; } @@ -57,109 +90,42 @@ public class LDAPConfiguration { protected void loadConnectionProperties() { Properties p = new Properties(); try { - InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(CONNECTION_PROPERTIES); + log.info("Reading LDAP configuration from: " + connectionPropertiesLocation); + InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(connectionPropertiesLocation); p.load(is); } catch (Exception e) { throw new RuntimeException(e); } - connectionUrl = p.getProperty(IDM_TEST_LDAP_CONNECTION_URL, connectionUrl); - baseDn = p.getProperty(IDM_TEST_LDAP_BASE_DN, baseDn); - userDnSuffix = p.getProperty(IDM_TEST_LDAP_USER_DN_SUFFIX, userDnSuffix); - rolesDnSuffix = p.getProperty(IDM_TEST_LDAP_ROLES_DN_SUFFIX, rolesDnSuffix); - groupDnSuffix = p.getProperty(IDM_TEST_LDAP_GROUP_DN_SUFFIX, groupDnSuffix); - agentDnSuffix = p.getProperty(IDM_TEST_LDAP_AGENT_DN_SUFFIX, agentDnSuffix); - startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true")); - bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn); - bindCredential = p.getProperty(IDM_TEST_LDAP_BIND_CREDENTIAL, bindCredential); - vendor = p.getProperty(IDM_TEST_LDAP_VENDOR); - connectionPooling = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_CONNECTION_POOLING, "true")); - pagination = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_PAGINATION, "true")); - batchSizeForSync = Integer.parseInt(p.getProperty(IDM_TEST_LDAP_BATCH_SIZE_FOR_SYNC, String.valueOf(batchSizeForSync))); - usernameLDAPAttribute = p.getProperty(IDM_TEST_LDAP_USERNAME_LDAP_ATTRIBUTE); - userObjectClasses = p.getProperty(IDM_TEST_LDAP_USER_OBJECT_CLASSES); - userAccountControlsAfterPasswordUpdate = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)); + config = new HashMap(); + for (Map.Entry property : PROP_MAPPINGS.entrySet()) { + String propertyName = property.getKey(); + String configName = property.getValue(); + + String value = (String) p.get(configName); + if (value == null) { + value = DEFAULT_VALUES.get(propertyName); + } + + config.put(propertyName, value); + } + + startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty("idm.test.ldap.start.embedded.ldap.server", "true")); + log.info("Start embedded server: " + startEmbeddedLdapLerver); + log.info("Read config: " + config); } public Map getLDAPConfig() { - Map ldapConfig = new HashMap(); - ldapConfig.put(LDAPConstants.CONNECTION_URL, getConnectionUrl()); - ldapConfig.put(LDAPConstants.BASE_DN, getBaseDn()); - ldapConfig.put(LDAPConstants.BIND_DN, getBindDn()); - ldapConfig.put(LDAPConstants.BIND_CREDENTIAL, getBindCredential()); - ldapConfig.put(LDAPConstants.USER_DN_SUFFIX, getUserDnSuffix()); - ldapConfig.put(LDAPConstants.VENDOR, getVendor()); - ldapConfig.put(LDAPConstants.CONNECTION_POOLING, String.valueOf(isConnectionPooling())); - ldapConfig.put(LDAPConstants.PAGINATION, String.valueOf(isPagination())); - ldapConfig.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, String.valueOf(getBatchSizeForSync())); - ldapConfig.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, getUsernameLDAPAttribute()); - ldapConfig.put(LDAPConstants.USER_OBJECT_CLASSES, getUserObjectClasses()); - ldapConfig.put(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE, String.valueOf(isUserAccountControlsAfterPasswordUpdate())); - return ldapConfig; + return config; } - public String getConnectionUrl() { - return connectionUrl; - } - - public String getBaseDn() { - return baseDn; - } - - public String getUserDnSuffix() { - return userDnSuffix; - } - - public String getRolesDnSuffix() { - return rolesDnSuffix; - } - - public String getGroupDnSuffix() { - return groupDnSuffix; - } - - public String getAgentDnSuffix() { - return agentDnSuffix; + public void setConnectionPropertiesLocation(String connectionPropertiesLocation) { + this.connectionPropertiesLocation = connectionPropertiesLocation; } public boolean isStartEmbeddedLdapLerver() { return startEmbeddedLdapLerver; } - public String getBindDn() { - return bindDn; - } - - public String getBindCredential() { - return bindCredential; - } - - public String getVendor() { - return vendor; - } - - public boolean isConnectionPooling() { - return connectionPooling; - } - - public boolean isPagination() { - return pagination; - } - - public int getBatchSizeForSync() { - return batchSizeForSync; - } - - public String getUsernameLDAPAttribute() { - return usernameLDAPAttribute; - } - - public String getUserObjectClasses() { - return userObjectClasses; - } - - public boolean isUserAccountControlsAfterPasswordUpdate() { - return userAccountControlsAfterPasswordUpdate; - } } diff --git a/testsuite/integration/src/main/resources/kerberos/kerberos-ldap-connection.properties b/testsuite/integration/src/main/resources/kerberos/kerberos-ldap-connection.properties new file mode 100644 index 0000000000..4b7a741b35 --- /dev/null +++ b/testsuite/integration/src/main/resources/kerberos/kerberos-ldap-connection.properties @@ -0,0 +1,17 @@ +idm.test.ldap.connection.url=ldap\://localhost\:10389 +idm.test.ldap.base.dn=dc\=keycloak,dc\=org +idm.test.ldap.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org +idm.test.ldap.group.dn.suffix=ou\=Groups,dc\=keycloak,dc\=org +idm.test.ldap.user.dn.suffix=ou\=People,dc\=keycloak,dc\=org +idm.test.ldap.start.embedded.ldap.server=true +idm.test.ldap.bind.dn=uid\=admin,ou\=system +idm.test.ldap.bind.credential=secret +idm.test.ldap.connection.pooling=true +idm.test.ldap.pagination=true +idm.test.ldap.batch.size.for.sync=3 + +idm.test.kerberos.allow.kerberos.authentication=true +idm.test.kerberos.realm=KEYCLOAK.ORG +idm.test.kerberos.server.principal=HTTP/localhost@KEYCLOAK.ORG +idm.test.kerberos.debug=false +idm.test.kerberos.use.kerberos.for.password.authentication=false \ No newline at end of file diff --git a/testsuite/integration/src/main/resources/kerberos/kerberos-standalone-connection.properties b/testsuite/integration/src/main/resources/kerberos/kerberos-standalone-connection.properties new file mode 100644 index 0000000000..78bffb049f --- /dev/null +++ b/testsuite/integration/src/main/resources/kerberos/kerberos-standalone-connection.properties @@ -0,0 +1,6 @@ +idm.test.kerberos.allow.kerberos.authentication=true +idm.test.kerberos.realm=KEYCLOAK.ORG +idm.test.kerberos.server.principal=HTTP/localhost@KEYCLOAK.ORG +idm.test.kerberos.debug=false +idm.test.kerberos.allow.password.authentication=true +idm.test.kerberos.update.profile.first.login=false \ No newline at end of file diff --git a/testsuite/integration/src/main/resources/kerberos/test-krb5.conf b/testsuite/integration/src/main/resources/kerberos/test-krb5.conf new file mode 100644 index 0000000000..2ba050d9b5 --- /dev/null +++ b/testsuite/integration/src/main/resources/kerberos/test-krb5.conf @@ -0,0 +1,17 @@ +[libdefaults] + default_realm = KEYCLOAK.ORG + default_tgs_enctypes = des3-cbc-sha1-kd rc4-hmac + default_tkt_enctypes = des3-cbc-sha1-kd rc4-hmac + kdc_timeout = 30000 + dns_lookup_realm = false + dns_lookup_kdc = false + dns_canonicalize_hostname = false + ignore_acceptor_hostname = true + +[realms] + KEYCLOAK.ORG = { + kdc = localhost:6088 + } + +[domain_realm] + localhost = KEYCLOAK.ORG \ No newline at end of file diff --git a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif index fcde10e462..acdd570a68 100644 --- a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif +++ b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif @@ -54,8 +54,9 @@ objectClass: person objectClass: inetOrgPerson objectClass: krb5principal objectClass: krb5kdcentry -cn: Horatio Nelson +cn: Horatio sn: Nelson +mail: hnelson@keycloak.org uid: hnelson userPassword: secret krb5PrincipalName: hnelson@KEYCLOAK.ORG @@ -67,8 +68,9 @@ objectClass: person objectClass: inetOrgPerson objectClass: krb5principal objectClass: krb5kdcentry -cn: Java Duke -sn: duke +cn: Java +sn: Duke +mail: jduke@keycloak.org uid: jduke userPassword: theduke krb5PrincipalName: jduke@KEYCLOAK.ORG diff --git a/testsuite/integration/src/main/resources/ldap/ldap-connection.properties b/testsuite/integration/src/main/resources/ldap/ldap-connection.properties index c275c2fb15..c759f4a0ac 100644 --- a/testsuite/integration/src/main/resources/ldap/ldap-connection.properties +++ b/testsuite/integration/src/main/resources/ldap/ldap-connection.properties @@ -3,7 +3,6 @@ idm.test.ldap.base.dn=dc\=keycloak,dc\=org idm.test.ldap.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org idm.test.ldap.group.dn.suffix=ou\=Groups,dc\=keycloak,dc\=org idm.test.ldap.user.dn.suffix=ou\=People,dc\=keycloak,dc\=org -idm.test.ldap.agent.dn.suffix=ou\=Agent,dc\=keycloak,dc\=org idm.test.ldap.start.embedded.ldap.server=true idm.test.ldap.bind.dn=uid\=admin,ou\=system idm.test.ldap.bind.credential=secret diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties index 5e78b310ff..cfd1259474 100755 --- a/testsuite/integration/src/main/resources/log4j.properties +++ b/testsuite/integration/src/main/resources/log4j.properties @@ -20,7 +20,7 @@ log4j.logger.org.keycloak=info # log4j.logger.org.keycloak.broker.kerberos=trace # Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server -log4j.logger.org.apache.directory.server.kerberos=debug +# log4j.logger.org.apache.directory.server.kerberos=debug log4j.logger.org.xnio=off log4j.logger.org.hibernate=off diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java new file mode 100644 index 0000000000..2f8cb01b06 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java @@ -0,0 +1,262 @@ +package org.keycloak.testsuite.federation; + +import java.security.Principal; +import java.util.List; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.client.params.AuthPolicy; +import org.apache.http.impl.client.DefaultHttpClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; +import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.keycloak.OAuth2Constants; +import org.keycloak.adapters.HttpClientBuilder; +import org.keycloak.events.Details; +import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.federation.kerberos.KerberosConfig; +import org.keycloak.models.KerberosConstants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.AccountPasswordPage; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.openqa.selenium.WebDriver; + +/** + * @author Marek Posolda + */ +public abstract class AbstractKerberosTest { + + protected KeycloakSPNegoSchemeFactory spnegoSchemeFactory; + protected ResteasyClient client; + + @WebResource + protected OAuthClient oauth; + + @WebResource + protected WebDriver driver; + + @WebResource + protected LoginPage loginPage; + + @WebResource + protected AccountPasswordPage changePasswordPage; + + @WebResource + protected AppPage appPage; + + protected abstract CommonKerberosConfig getKerberosConfig(); + protected abstract KeycloakRule getKeycloakRule(); + protected abstract AssertEvents getAssertEvents(); + + @Before + public void before() { + CommonKerberosConfig kerberosConfig = getKerberosConfig(); + spnegoSchemeFactory = new KeycloakSPNegoSchemeFactory(kerberosConfig); + initHttpClient(true); + removeAllUsers(); + } + + @After + public void after() { + client.close(); + client = null; + } + + + @Test + public void spnegoNotAvailableTest() throws Exception { + initHttpClient(false); + Response response = client.target(oauth.getLoginFormUrl()).request().get(); + Assert.assertEquals(401, response.getStatus()); + Assert.assertEquals(KerberosConstants.NEGOTIATE, response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE)); + String responseText = response.readEntity(String.class); + responseText.contains("Log in to test"); + } + + + protected void spnegoLoginTestImpl() throws Exception { + KeycloakRule keycloakRule = getKeycloakRule(); + AssertEvents events = getAssertEvents(); + + Response spnegoResponse = spnegoLogin("hnelson", "secret"); + Assert.assertEquals(302, spnegoResponse.getStatus()); + + events.expectLogin() + .user(keycloakRule.getUser("test", "hnelson").getId()) + .detail(Details.AUTH_METHOD, "spnego") + .detail(Details.USERNAME, "hnelson") + .assertEvent(); + + String location = spnegoResponse.getLocation().toString(); + driver.navigate().to(location); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + } + + + @Test + public void usernamePasswordLoginTest() throws Exception { + KeycloakRule keycloakRule = getKeycloakRule(); + AssertEvents events = getAssertEvents(); + + // Change editMode to READ_ONLY + updateProviderEditMode(UserFederationProvider.EditMode.READ_ONLY); + + // Login with username/password from kerberos + changePasswordPage.open(); + loginPage.assertCurrent(); + loginPage.login("jduke", "theduke"); + changePasswordPage.assertCurrent(); + + // Change password is not possible as editMode is READ_ONLY + changePasswordPage.changePassword("theduke", "newPass", "newPass"); + Assert.assertTrue(driver.getPageSource().contains("You can't update your password as your account is read only")); + + // Change editMode to UNSYNCED + updateProviderEditMode(UserFederationProvider.EditMode.UNSYNCED); + + // Successfully change password now + changePasswordPage.changePassword("theduke", "newPass", "newPass"); + Assert.assertTrue(driver.getPageSource().contains("Your password has been updated")); + changePasswordPage.logout(); + + // Login with old password doesn't work, but with new password works + loginPage.login("jduke", "theduke"); + loginPage.assertCurrent(); + loginPage.login("jduke", "newPass"); + changePasswordPage.assertCurrent(); + changePasswordPage.logout(); + + // Assert SPNEGO login still with the old password as mode is unsynced + events.clear(); + Response spnegoResponse = spnegoLogin("jduke", "theduke"); + Assert.assertEquals(302, spnegoResponse.getStatus()); + events.expectLogin() + .user(keycloakRule.getUser("test", "jduke").getId()) + .detail(Details.AUTH_METHOD, "spnego") + .detail(Details.USERNAME, "jduke") + .assertEvent(); + } + + + + protected Response spnegoLogin(String username, String password) { + spnegoSchemeFactory.setCredentials(username, password); + return client.target(oauth.getLoginFormUrl()).request().get(); + } + + + protected void initHttpClient(boolean useSpnego) { + if (client != null) { + after(); + } + + DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build(); + httpClient.getAuthSchemes().register(AuthPolicy.SPNEGO, spnegoSchemeFactory); + + if (useSpnego) { + Credentials fake = new Credentials() { + + public String getPassword() { + return null; + } + + public Principal getUserPrincipal() { + return null; + } + + }; + + httpClient.getCredentialsProvider().setCredentials( + new AuthScope(null, -1, null), + fake); + } + + ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient); + client = new ResteasyClientBuilder().httpEngine(engine).build(); + } + + + protected void removeAllUsers() { + KeycloakRule keycloakRule = getKeycloakRule(); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + + RealmModel appRealm = manager.getRealm("test"); + List users = session.userStorage().getUsers(appRealm); + for (UserModel user : users) { + if (!user.getUsername().equals(AssertEvents.DEFAULT_USERNAME)) { + session.userStorage().removeUser(appRealm, user); + } + } + + Assert.assertEquals(1, session.userStorage().getUsers(appRealm).size()); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + protected void assertUser(String expectedUsername, String expectedEmail, String expectedFirstname, String expectedLastname, boolean updateProfileActionExpected) { + KeycloakRule keycloakRule = getKeycloakRule(); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + UserModel user = session.users().getUserByUsername(expectedUsername, appRealm); + Assert.assertNotNull(user); + Assert.assertEquals(user.getEmail(), expectedEmail); + Assert.assertEquals(user.getFirstName(), expectedFirstname); + Assert.assertEquals(user.getLastName(), expectedLastname); + + if (updateProfileActionExpected) { + Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next().name()); + } else { + Assert.assertTrue(user.getRequiredActions().isEmpty()); + } + } finally { + keycloakRule.stopSession(session, true); + } + } + + + protected void updateProviderEditMode(UserFederationProvider.EditMode editMode) { + KeycloakRule keycloakRule = getKeycloakRule(); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmModel realm = session.realms().getRealm("test"); + UserFederationProviderModel kerberosProviderModel = realm.getUserFederationProviders().get(0); + kerberosProviderModel.getConfig().put(LDAPConstants.EDIT_MODE, editMode.toString()); + realm.updateUserFederationProvider(kerberosProviderModel); + } finally { + keycloakRule.stopSession(session, true); + } + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java similarity index 96% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java index 833e56c664..611c6bd10e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.forms; +package org.keycloak.testsuite.federation; import org.junit.Assert; import org.junit.ClassRule; @@ -13,6 +13,7 @@ import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.LDAPFederationProviderFactory; import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; @@ -55,9 +56,9 @@ public class FederationProvidersIntegrationTest { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app"); - Map ldapConfig = ldapRule.getLdapConfig(); + Map ldapConfig = ldapRule.getConfig(); ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true"); - ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); + ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString()); ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); @@ -272,7 +273,7 @@ public class FederationProvidersIntegrationTest { UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0); - model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString()); + model.getConfig().put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString()); appRealm.updateUserFederationProvider(model); UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm); Assert.assertNotNull(user); @@ -312,7 +313,7 @@ public class FederationProvidersIntegrationTest { session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); - Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPFederationProvider.EDIT_MODE)); + Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPConstants.EDIT_MODE)); } finally { keycloakRule.stopSession(session, false); } @@ -380,7 +381,7 @@ public class FederationProvidersIntegrationTest { UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0); - model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString()); + model.getConfig().put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString()); appRealm.updateUserFederationProvider(model); UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm); Assert.assertNotNull(user); @@ -405,7 +406,7 @@ public class FederationProvidersIntegrationTest { session = keycloakRule.startSession(); try { RealmModel appRealm = session.realms().getRealmByName("test"); - Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPFederationProvider.EDIT_MODE)); + Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPConstants.EDIT_MODE)); } finally { keycloakRule.stopSession(session, false); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java new file mode 100644 index 0000000000..49efabc073 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java @@ -0,0 +1,147 @@ +package org.keycloak.testsuite.federation; + +import java.util.Map; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runners.MethodSorters; +import org.keycloak.OAuth2Constants; +import org.keycloak.events.Details; +import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.federation.kerberos.KerberosConfig; +import org.keycloak.federation.kerberos.KerberosFederationProviderFactory; +import org.keycloak.federation.ldap.LDAPFederationProviderFactory; +import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; +import org.keycloak.models.KerberosConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.AccountPasswordPage; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.RegisterPage; +import org.keycloak.testsuite.rule.KerberosRule; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.openqa.selenium.WebDriver; + +/** + * Test of LDAPFederationProvider (Kerberos backed by LDAP) + * + * @author Marek Posolda + */ +public class KerberosLdapTest extends AbstractKerberosTest { + + public static final String CONFIG_LOCATION = "kerberos/kerberos-ldap-connection.properties"; + + private static UserFederationProviderModel ldapModel = null; + + private static KerberosRule kerberosRule = new KerberosRule(CONFIG_LOCATION); + + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + Map ldapConfig = kerberosRule.getConfig(); + ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "kerberos-ldap", -1, -1, 0); + appRealm.addRequiredCredential(UserCredentialModel.KERBEROS); + } + }); + + @ClassRule + public static TestRule chain = RuleChain + .outerRule(kerberosRule) + .around(keycloakRule); + + @Rule + public WebRule webRule = new WebRule(this); + + @Rule + public AssertEvents events = new AssertEvents(keycloakRule); + + @Override + protected CommonKerberosConfig getKerberosConfig() { + return new LDAPProviderKerberosConfig(ldapModel); + } + + @Override + protected KeycloakRule getKeycloakRule() { + return keycloakRule; + } + + @Override + protected AssertEvents getAssertEvents() { + return events; + } + + + @Test + public void spnegoLoginTest() throws Exception { + spnegoLoginTestImpl(); + + // Assert user was imported and hasn't any required action on him. Profile info is synced from LDAP + assertUser("hnelson", "hnelson@keycloak.org", "Horatio", "Nelson", false); + } + + + @Test + public void writableEditModeTest() throws Exception { + KeycloakRule keycloakRule = getKeycloakRule(); + AssertEvents events = getAssertEvents(); + + // Change editMode to READ_ONLY + updateProviderEditMode(UserFederationProvider.EditMode.WRITABLE); + + // Login with username/password from kerberos + changePasswordPage.open(); + loginPage.assertCurrent(); + loginPage.login("jduke", "theduke"); + changePasswordPage.assertCurrent(); + + // Successfully change password now + changePasswordPage.changePassword("theduke", "newPass", "newPass"); + Assert.assertTrue(driver.getPageSource().contains("Your password has been updated")); + changePasswordPage.logout(); + + // Login with old password doesn't work, but with new password works + loginPage.login("jduke", "theduke"); + loginPage.assertCurrent(); + loginPage.login("jduke", "newPass"); + changePasswordPage.assertCurrent(); + changePasswordPage.logout(); + + // Assert SPNEGO login with the new password as mode is writable + events.clear(); + Response spnegoResponse = spnegoLogin("jduke", "newPass"); + Assert.assertEquals(302, spnegoResponse.getStatus()); + events.expectLogin() + .user(keycloakRule.getUser("test", "jduke").getId()) + .detail(Details.AUTH_METHOD, "spnego") + .detail(Details.USERNAME, "jduke") + .assertEvent(); + + // Change password back + loginPage.login("jduke", "newPass"); + changePasswordPage.assertCurrent(); + changePasswordPage.changePassword("newPass", "theduke", "theduke"); + Assert.assertTrue(driver.getPageSource().contains("Your password has been updated")); + changePasswordPage.logout(); + + events.clear(); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java new file mode 100644 index 0000000000..0decc0b4d3 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java @@ -0,0 +1,124 @@ +package org.keycloak.testsuite.federation; + +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.federation.kerberos.KerberosConfig; +import org.keycloak.federation.kerberos.KerberosFederationProviderFactory; +import org.keycloak.models.KerberosConstants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.rule.KerberosRule; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebRule; + +/** + * Test of KerberosFederationProvider (Kerberos not backed by LDAP) + * + * @author Marek Posolda + */ +public class KerberosStandaloneTest extends AbstractKerberosTest { + + public static final String CONFIG_LOCATION = "kerberos/kerberos-standalone-connection.properties"; + + private static UserFederationProviderModel kerberosModel; + + private static KerberosRule kerberosRule = new KerberosRule(CONFIG_LOCATION); + + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + Map kerberosConfig = kerberosRule.getConfig(); + + kerberosModel = appRealm.addUserFederationProvider(KerberosFederationProviderFactory.PROVIDER_NAME, kerberosConfig, 0, "kerberos-standalone", -1, -1, 0); + appRealm.addRequiredCredential(UserCredentialModel.KERBEROS); + } + }); + + + + @ClassRule + public static TestRule chain = RuleChain + .outerRule(kerberosRule) + .around(keycloakRule); + + @Rule + public WebRule webRule = new WebRule(this); + + @Rule + public AssertEvents events = new AssertEvents(keycloakRule); + + + @Override + protected CommonKerberosConfig getKerberosConfig() { + return new KerberosConfig(kerberosModel); + } + + @Override + protected KeycloakRule getKeycloakRule() { + return keycloakRule; + } + + @Override + protected AssertEvents getAssertEvents() { + return events; + } + + + @Test + public void spnegoLoginTest() throws Exception { + spnegoLoginTestImpl(); + + // Assert user was imported and hasn't any required action on him + assertUser("hnelson", "hnelson@keycloak.org", null, null, false); + } + + + @Test + public void updateProfileEnabledTest() throws Exception { + // Switch updateProfileOnFirstLogin to on + KeycloakSession session = keycloakRule.startSession(); + try { + RealmModel realm = session.realms().getRealm("test"); + UserFederationProviderModel kerberosProviderModel = realm.getUserFederationProviders().get(0); + kerberosProviderModel.getConfig().put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true"); + realm.updateUserFederationProvider(kerberosProviderModel); + } finally { + keycloakRule.stopSession(session, true); + } + + // Assert update profile page is displayed + Response spnegoResponse = spnegoLogin("hnelson", "secret"); + Assert.assertEquals(200, spnegoResponse.getStatus()); + String responseText = spnegoResponse.readEntity(String.class); + Assert.assertTrue(responseText.contains("You need to update your user profile to activate your account.")); + Assert.assertTrue(responseText.contains("hnelson@keycloak.org")); + + // Assert user was imported and has required action on him + assertUser("hnelson", "hnelson@keycloak.org", null, null, true); + + // Switch updateProfileOnFirstLogin to off + session = keycloakRule.startSession(); + try { + RealmModel realm = session.realms().getRealm("test"); + UserFederationProviderModel kerberosProviderModel = realm.getUserFederationProviders().get(0); + kerberosProviderModel.getConfig().put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "false"); + realm.updateUserFederationProvider(kerberosProviderModel); + } finally { + keycloakRule.stopSession(session, true); + } + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java new file mode 100644 index 0000000000..34032633ca --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java @@ -0,0 +1,100 @@ +package org.keycloak.testsuite.federation; + +import java.security.PrivilegedExceptionAction; + +import javax.security.auth.Subject; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.impl.auth.SPNegoScheme; +import org.apache.http.impl.auth.SPNegoSchemeFactory; +import org.apache.http.params.HttpParams; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.Oid; +import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; + +/** + * Usable for testing only. Username and password are shared for the whole factory + * + * @author Marek Posolda + */ +public class KeycloakSPNegoSchemeFactory extends SPNegoSchemeFactory { + + private final CommonKerberosConfig kerberosConfig; + + private String username; + private String password; + + + public KeycloakSPNegoSchemeFactory(CommonKerberosConfig kerberosConfig) { + super(true); + this.kerberosConfig = kerberosConfig; + } + + + public void setCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + + @Override + public AuthScheme newInstance(HttpParams params) { + return new KeycloakSPNegoScheme(isStripPort()); + } + + + public class KeycloakSPNegoScheme extends SPNegoScheme { + + public KeycloakSPNegoScheme(boolean stripPort) { + super(stripPort); + } + + + @Override + protected byte[] generateGSSToken(byte[] input, Oid oid, String authServer) throws GSSException { + KerberosUsernamePasswordAuthenticator authenticator = new KerberosUsernamePasswordAuthenticator(kerberosConfig); + try { + Subject clientSubject = authenticator.authenticateSubject(username, password); + + ByteArrayHolder holder = Subject.doAs(clientSubject, new ClientAcceptSecContext(input, oid, authServer)); + + return holder.bytes; + } catch (Exception le) { + throw new RuntimeException(le); + } finally { + authenticator.logoutSubject(); + } + } + + + private class ClientAcceptSecContext implements PrivilegedExceptionAction { + + private final byte[] input; + private final Oid oid; + private final String authServer; + + public ClientAcceptSecContext(byte[] input, Oid oid, String authServer) { + this.input = input; + this.oid = oid; + this.authServer = authServer; + } + + + @Override + public ByteArrayHolder run() throws Exception { + byte[] outputToken = KeycloakSPNegoScheme.super.generateGSSToken(input, oid, authServer); + + ByteArrayHolder result = new ByteArrayHolder(); + result.bytes = outputToken; + return result; + } + + } + + + private class ByteArrayHolder { + private byte[] bytes; + } + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java similarity index 97% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java index 917a544e8a..55f68bb656 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.forms; +package org.keycloak.testsuite.federation; import org.junit.Assert; import org.junit.ClassRule; @@ -12,6 +12,7 @@ import org.keycloak.federation.ldap.LDAPFederationProviderFactory; import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.LDAPConstants; import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; @@ -48,9 +49,9 @@ public class SyncProvidersTest { // Other tests may left Time offset uncleared, which could cause issues Time.setOffset(0); - Map ldapConfig = ldapRule.getLdapConfig(); + Map ldapConfig = ldapRule.getConfig(); ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false"); - ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString()); + ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString()); ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java new file mode 100644 index 0000000000..61d7f322cb --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java @@ -0,0 +1,39 @@ +package org.keycloak.testsuite.rule; + +import java.io.File; +import java.net.URL; + +import org.jboss.logging.Logger; +import org.keycloak.testutils.ldap.EmbeddedServersFactory; +import org.keycloak.testutils.ldap.LDAPConfiguration; +import org.keycloak.testutils.ldap.LDAPEmbeddedServer; + +/** + * @author Marek Posolda + */ +public class KerberosRule extends LDAPRule { + + private static final Logger log = Logger.getLogger(KerberosRule.class); + + private final String configLocation; + + public KerberosRule(String configLocation) { + this.configLocation = configLocation; + + // Global kerberos configuration + URL krb5ConfURL = LDAPConfiguration.class.getResource("/kerberos/test-krb5.conf"); + String krb5ConfPath = new File(krb5ConfURL.getFile()).getAbsolutePath(); + log.info("Krb5.conf file location is: " + krb5ConfPath); + System.setProperty("java.security.krb5.conf", krb5ConfPath); + } + + @Override + protected String getConnectionPropertiesLocation() { + return configLocation; + } + + @Override + protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) { + return factory.createKerberosServer(); + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java index 5b120377cb..2fa5f01faf 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java @@ -12,16 +12,19 @@ import org.keycloak.testutils.ldap.LDAPEmbeddedServer; */ public class LDAPRule extends ExternalResource { - private LDAPConfiguration ldapConfiguration; - private LDAPEmbeddedServer ldapEmbeddedServer; + public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties"; + + protected LDAPConfiguration ldapConfiguration; + protected LDAPEmbeddedServer ldapEmbeddedServer; @Override protected void before() throws Throwable { - ldapConfiguration = LDAPConfiguration.readConfiguration(); + String connectionPropsLocation = getConnectionPropertiesLocation(); + ldapConfiguration = LDAPConfiguration.readConfiguration(connectionPropsLocation); if (ldapConfiguration.isStartEmbeddedLdapLerver()) { EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration(); - ldapEmbeddedServer = factory.createLdapServer(); + ldapEmbeddedServer = createServer(factory); ldapEmbeddedServer.init(); ldapEmbeddedServer.start(); } @@ -40,7 +43,15 @@ public class LDAPRule extends ExternalResource { } } - public Map getLdapConfig() { + protected String getConnectionPropertiesLocation() { + return LDAP_CONNECTION_PROPERTIES_LOCATION; + } + + protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) { + return factory.createLdapServer(); + } + + public Map getConfig() { return ldapConfiguration.getLDAPConfig(); } } diff --git a/testsuite/jetty/jetty81/pom.xml b/testsuite/jetty/jetty81/pom.xml index a8936a8707..c6c57ced25 100755 --- a/testsuite/jetty/jetty81/pom.xml +++ b/testsuite/jetty/jetty81/pom.xml @@ -113,6 +113,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/jetty/jetty91/pom.xml b/testsuite/jetty/jetty91/pom.xml index 5ef51a152c..be3e483b20 100755 --- a/testsuite/jetty/jetty91/pom.xml +++ b/testsuite/jetty/jetty91/pom.xml @@ -113,6 +113,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/jetty/jetty92/pom.xml b/testsuite/jetty/jetty92/pom.xml index c7d91c7a8a..3ddef4272f 100755 --- a/testsuite/jetty/jetty92/pom.xml +++ b/testsuite/jetty/jetty92/pom.xml @@ -113,6 +113,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/proxy/pom.xml b/testsuite/proxy/pom.xml index 721e4c3eae..809feffe4f 100755 --- a/testsuite/proxy/pom.xml +++ b/testsuite/proxy/pom.xml @@ -118,6 +118,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/tomcat6/pom.xml b/testsuite/tomcat6/pom.xml index b8cbf7692b..42621a59fc 100755 --- a/testsuite/tomcat6/pom.xml +++ b/testsuite/tomcat6/pom.xml @@ -112,6 +112,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/tomcat7/pom.xml b/testsuite/tomcat7/pom.xml index 8e17fd322e..088ec7166a 100755 --- a/testsuite/tomcat7/pom.xml +++ b/testsuite/tomcat7/pom.xml @@ -113,6 +113,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter diff --git a/testsuite/tomcat8/pom.xml b/testsuite/tomcat8/pom.xml index b9c7af96ee..6941dd50ea 100755 --- a/testsuite/tomcat8/pom.xml +++ b/testsuite/tomcat8/pom.xml @@ -112,6 +112,11 @@ keycloak-ldap-federation ${project.version} + + org.keycloak + keycloak-kerberos-federation + ${project.version} + org.keycloak keycloak-undertow-adapter From 9f8b59dfb6bb28d6f5128337427635dd5ae4a23d Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 23 Feb 2015 13:12:41 +0100 Subject: [PATCH 2/3] Kerberos/LDAP fixes --- .../kerberos/KerberosFederationProvider.java | 14 ++++++----- .../kerberos/impl/SPNEGOAuthenticator.java | 9 +------- .../ldap/LDAPFederationProvider.java | 23 ++++++++++++------- .../base/resources/js/controllers/users.js | 6 +---- .../resources/partials/federated-ldap.html | 10 ++------ .../models/UserFederationManager.java | 1 + .../managers/HttpAuthenticationManager.java | 1 - .../main/resources/kerberos/test-krb5.conf | 1 + 8 files changed, 29 insertions(+), 36 deletions(-) diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java index b0d23dcffa..f1ea251547 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java @@ -210,15 +210,17 @@ public class KerberosFederationProvider implements UserFederationProvider { UserModel user = session.userStorage().getUserByUsername(username, realm); if (user != null) { logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage"); - if (!isValid(user)) { - throw new IllegalStateException("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + + if (isValid(user)) { + return proxy(user); + } else { + logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "] or kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL)); + session.userStorage().removeUser(realm, user); } - - return proxy(user); - } else { - return importUserToKeycloak(realm, username); } + + logger.debug("Kerberos authenticated user " + username + " not in Keycloak storage. Creating him"); + return importUserToKeycloak(realm, username); } protected UserModel importUserToKeycloak(RealmModel realm, String username) { diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java index 59983637ab..a320469967 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java @@ -47,14 +47,7 @@ public class SPNEGOAuthenticator { Subject serverSubject = kerberosSubjectAuthenticator.authenticateServerSubject(); authenticated = Subject.doAs(serverSubject, new AcceptSecContext()); } catch (Exception e) { - String message = e.getMessage(); - if (e instanceof PrivilegedActionException && e.getCause() != null) { - message = e.getCause().getMessage(); - } - log.warn("SPNEGO login failed: " + message); - if (log.isDebugEnabled()) { - log.debug("SPNEGO login failed: " + message, e); - } + log.warn("SPNEGO login failed: " + e.getMessage(), e); } finally { kerberosSubjectAuthenticator.logoutServerSubject(); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index 3e2237df53..9f927b4aef 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -353,6 +353,11 @@ public class LDAPFederationProvider implements UserFederationProvider { String username = spnegoAuthenticator.getAuthenticatedUsername(); UserModel user = findOrCreateAuthenticatedUser(realm, username); + if (user == null) { + logger.warn("Kerberos/SPNEGO authentication succeeded with username [" + username + "], but couldn't find or create user with federation provider [" + model.getDisplayName() + "]"); + return CredentialValidationOutput.failed(); + } + return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state); } else { Map state = new HashMap(); @@ -404,15 +409,17 @@ public class LDAPFederationProvider implements UserFederationProvider { UserModel user = session.userStorage().getUserByUsername(username, realm); if (user != null) { logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage"); - if (!isValid(user)) { - throw new IllegalStateException("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + - "] or LDAP_ID is not correct. LDAP_ID on user is: " + user.getAttribute(LDAP_ID)); + if (isValid(user)) { + return proxy(user); + } else { + logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + + "] or LDAP_ID is not correct. Stale LDAP_ID on local user is: " + user.getAttribute(LDAP_ID)); + session.userStorage().removeUser(realm, user); } - - return proxy(user); - } else { - // Creating user to local storage - return getUserByUsername(realm, username); } + + // Creating user to local storage + logger.debug("Kerberos authenticated user " + username + " not in Keycloak storage. Creating him"); + return getUserByUsername(realm, username); } } diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js index fb0ce1bef1..4603153e1c 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js @@ -547,10 +547,6 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, { "id": "other", "name": "Other" } ]; - $scope.usernameLDAPAttributes = [ - "uid", "cn", "sAMAccountName", "entryDN" - ]; - $scope.realm = realm; $scope.$watch('fullSyncEnabled', function(newVal, oldVal) { @@ -581,7 +577,7 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, $scope.lastVendor = $scope.instance.config.vendor; if ($scope.lastVendor === "ad") { - $scope.instance.config.usernameLDAPAttribute = "cn"; + $scope.instance.config.usernameLDAPAttribute = "sAMAccountName"; $scope.instance.config.userObjectClasses = "person, organizationalPerson, user"; } else { $scope.instance.config.usernameLDAPAttribute = "uid"; diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html index 66ff5c2d07..35323a64d1 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html @@ -78,15 +78,9 @@
-
- -
+
- +
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index 7d3ecf470b..a98618559f 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -384,6 +384,7 @@ public class UserFederationManager implements UserProvider { return CredentialValidationOutput.failed(); } + logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType()); CredentialValidationOutput currentResult = providerSupportingCreds.validCredentials(realm, cred); result = (result == null) ? currentResult : result.merge(currentResult); } diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java index 34ed9008d1..9791de321d 100644 --- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java @@ -140,7 +140,6 @@ public class HttpAuthenticationManager { loginFormsProvider.setStatus(Response.Status.UNAUTHORIZED); loginFormsProvider.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader); - loginFormsProvider.setWarning("errorKerberosLogin"); } }); diff --git a/testsuite/integration/src/main/resources/kerberos/test-krb5.conf b/testsuite/integration/src/main/resources/kerberos/test-krb5.conf index 2ba050d9b5..350c086af7 100644 --- a/testsuite/integration/src/main/resources/kerberos/test-krb5.conf +++ b/testsuite/integration/src/main/resources/kerberos/test-krb5.conf @@ -2,6 +2,7 @@ default_realm = KEYCLOAK.ORG default_tgs_enctypes = des3-cbc-sha1-kd rc4-hmac default_tkt_enctypes = des3-cbc-sha1-kd rc4-hmac + permitted_enctypes = des3-cbc-sha1-kd rc4-hmac kdc_timeout = 30000 dns_lookup_realm = false dns_lookup_kdc = false From 4b6b48dc3473731ae7528fb68d685fe88e461861 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 23 Feb 2015 17:05:10 +0100 Subject: [PATCH 3/3] Increase MaxPermSize for tests --- pom.xml | 2 +- .../org/keycloak/testutils/ldap/KerberosEmbeddedServer.java | 4 ++-- .../org/keycloak/testutils/ldap/LDAPEmbeddedServer.java | 6 +++--- .../keycloak/testsuite/federation/AbstractKerberosTest.java | 4 ++++ .../org/keycloak/testsuite/federation/KerberosLdapTest.java | 1 + .../testsuite/federation/KerberosStandaloneTest.java | 1 + 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index d43ce03c7d..bab4f97b85 100755 --- a/pom.xml +++ b/pom.xml @@ -595,7 +595,7 @@ 2.16 once - -Xms512m -Xmx512m + -Xms512m -Xmx512m -XX:MaxPermSize=256m diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java index 635c81764c..ff5b7791e1 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java @@ -27,7 +27,7 @@ import org.jboss.logging.Logger; */ public class KerberosEmbeddedServer extends LDAPEmbeddedServer { - private static final Logger log = Logger.getLogger(LDAPEmbeddedServer.class); + private static final Logger log = Logger.getLogger(KerberosEmbeddedServer.class); private final String kerberosRealm; private final int kdcPort; @@ -117,7 +117,7 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer { protected void stopKerberosServer() { - log.info("Stoping Kerberos server."); + log.info("Stopping Kerberos server."); kdcServer.stop(); } diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java index 9c18288ebf..1bf4f80df2 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java @@ -150,7 +150,7 @@ public class LDAPEmbeddedServer { } final String ldifContent = StrSubstitutor.replace(StreamUtil.readString(is), map); - log.info("Importing LDIF: " + ldifContent); + log.info("Content of LDIF: " + ldifContent); final SchemaManager schemaManager = directoryService.getSchemaManager(); for (LdifEntry ldifEntry : new LdifReader(IOUtils.toInputStream(ldifContent))) { @@ -170,13 +170,13 @@ public class LDAPEmbeddedServer { protected void stopLdapServer() { - log.info("Stoping LDAP server."); + log.info("Stopping LDAP server."); ldapServer.stop(); } protected void shutdownDirectoryService() throws Exception { - log.info("Stoping Directory service."); + log.info("Stopping Directory service."); directoryService.shutdown(); log.info("Removing Directory service workfiles."); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java index 2f8cb01b06..0bb3cd2587 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java @@ -93,6 +93,7 @@ public abstract class AbstractKerberosTest { Assert.assertEquals(KerberosConstants.NEGOTIATE, response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE)); String responseText = response.readEntity(String.class); responseText.contains("Log in to test"); + response.close(); } @@ -113,6 +114,8 @@ public abstract class AbstractKerberosTest { driver.navigate().to(location); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + spnegoResponse.close(); } @@ -158,6 +161,7 @@ public abstract class AbstractKerberosTest { .detail(Details.AUTH_METHOD, "spnego") .detail(Details.USERNAME, "jduke") .assertEvent(); + spnegoResponse.close(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java index 49efabc073..1f1092a86b 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java @@ -141,6 +141,7 @@ public class KerberosLdapTest extends AbstractKerberosTest { Assert.assertTrue(driver.getPageSource().contains("Your password has been updated")); changePasswordPage.logout(); + spnegoResponse.close(); events.clear(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java index 0decc0b4d3..65a753ea3d 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java @@ -106,6 +106,7 @@ public class KerberosStandaloneTest extends AbstractKerberosTest { String responseText = spnegoResponse.readEntity(String.class); Assert.assertTrue(responseText.contains("You need to update your user profile to activate your account.")); Assert.assertTrue(responseText.contains("hnelson@keycloak.org")); + spnegoResponse.close(); // Assert user was imported and has required action on him assertUser("hnelson", "hnelson@keycloak.org", null, null, true);