Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2015-02-23 11:39:39 -05:00
commit d06b7a47ac
41 changed files with 1007 additions and 220 deletions

View file

@ -93,11 +93,6 @@
<artifactId>keycloak-broker-saml</artifactId> <artifactId>keycloak-broker-saml</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-social-github</artifactId> <artifactId>keycloak-social-github</artifactId>
@ -129,6 +124,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.picketlink</groupId> <groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId> <artifactId>picketlink-common</artifactId>

View file

@ -3,7 +3,7 @@ package org.keycloak.federation.kerberos;
import java.util.Map; import java.util.Map;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.utils.KerberosConstants; import org.keycloak.models.KerberosConstants;
/** /**
* Common configuration useful for all providers * Common configuration useful for all providers
@ -24,19 +24,19 @@ public abstract class CommonKerberosConfig {
} }
public String getKerberosRealm() { public String getKerberosRealm() {
return getConfig().get("kerberosRealm"); return getConfig().get(KerberosConstants.KERBEROS_REALM);
} }
public String getServerPrincipal() { public String getServerPrincipal() {
return getConfig().get("serverPrincipal"); return getConfig().get(KerberosConstants.SERVER_PRINCIPAL);
} }
public String getKeyTab() { public String getKeyTab() {
return getConfig().get("keyTab"); return getConfig().get(KerberosConstants.KEYTAB);
} }
public boolean getDebug() { public boolean getDebug() {
return Boolean.valueOf(getConfig().get("debug")); return Boolean.valueOf(getConfig().get(KerberosConstants.DEBUG));
} }
protected Map<String, String> getConfig() { protected Map<String, String> getConfig() {

View file

@ -1,5 +1,7 @@
package org.keycloak.federation.kerberos; package org.keycloak.federation.kerberos;
import org.keycloak.models.KerberosConstants;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
@ -15,7 +17,7 @@ public class KerberosConfig extends CommonKerberosConfig {
} }
public UserFederationProvider.EditMode getEditMode() { public UserFederationProvider.EditMode getEditMode() {
String editModeString = getConfig().get("editMode"); String editModeString = getConfig().get(LDAPConstants.EDIT_MODE);
if (editModeString == null) { if (editModeString == null) {
return UserFederationProvider.EditMode.UNSYNCED; return UserFederationProvider.EditMode.UNSYNCED;
} else { } else {
@ -24,11 +26,11 @@ public class KerberosConfig extends CommonKerberosConfig {
} }
public boolean isAllowPasswordAuthentication() { public boolean isAllowPasswordAuthentication() {
return Boolean.valueOf(getConfig().get("allowPasswordAuthentication")); return Boolean.valueOf(getConfig().get(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION));
} }
public boolean isUpdateProfileFirstLogin() { public boolean isUpdateProfileFirstLogin() {
return Boolean.valueOf(getConfig().get("updateProfileFirstLogin")); return Boolean.valueOf(getConfig().get(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN));
} }
} }

View file

@ -20,7 +20,7 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KerberosConstants; import org.keycloak.models.KerberosConstants;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -210,17 +210,19 @@ public class KerberosFederationProvider implements UserFederationProvider {
UserModel user = session.userStorage().getUserByUsername(username, realm); UserModel user = session.userStorage().getUserByUsername(username, realm);
if (user != null) { if (user != null) {
logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage"); logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage");
if (!isValid(user)) { if (isValid(user)) {
throw new IllegalStateException("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));
}
return proxy(user); return proxy(user);
} else { } else {
return importUserToKeycloak(realm, username); 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);
} }
} }
logger.debug("Kerberos authenticated user " + username + " not in Keycloak storage. Creating him");
return importUserToKeycloak(realm, username);
}
protected UserModel importUserToKeycloak(RealmModel realm, String username) { protected UserModel importUserToKeycloak(RealmModel realm, String username) {
// Just guessing email from kerberos realm // Just guessing email from kerberos realm
String email = username + "@" + kerberosConfig.getKerberosRealm().toLowerCase(); String email = username + "@" + kerberosConfig.getKerberosRealm().toLowerCase();

View file

@ -4,6 +4,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback; import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.NameCallback;
@ -25,11 +26,13 @@ public class KerberosUsernamePasswordAuthenticator {
private static final Logger logger = Logger.getLogger(KerberosUsernamePasswordAuthenticator.class); private static final Logger logger = Logger.getLogger(KerberosUsernamePasswordAuthenticator.class);
private final CommonKerberosConfig config; private final CommonKerberosConfig config;
private LoginContext loginContext;
public KerberosUsernamePasswordAuthenticator(CommonKerberosConfig config) { public KerberosUsernamePasswordAuthenticator(CommonKerberosConfig config) {
this.config = config; this.config = config;
} }
/** /**
* Returns true if user with given username exists in kerberos database * 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); logger.debug("Checking existence of principal: " + principal);
try { try {
LoginContext loginContext = new LoginContext("does-not-matter", null, loginContext = new LoginContext("does-not-matter", null,
createJaasCallbackHandler(principal, "fake-password-which-nobody-has"), createJaasCallbackHandler(principal, "fake-password-which-nobody-has"),
createJaasConfiguration()); createJaasConfiguration());
@ -58,6 +61,7 @@ public class KerberosUsernamePasswordAuthenticator {
} }
} }
/** /**
* Returns true if user was successfully authenticated against Kerberos * Returns true if user was successfully authenticated against Kerberos
* *
@ -66,18 +70,9 @@ public class KerberosUsernamePasswordAuthenticator {
* @return true if user was successfully authenticated * @return true if user was successfully authenticated
*/ */
public boolean validUser(String username, String password) { public boolean validUser(String username, String password) {
String principal = getKerberosPrincipal(username);
logger.debug("Validating password of principal: " + principal);
try { try {
LoginContext loginContext = new LoginContext("does-not-matter", null, authenticateSubject(username, password);
createJaasCallbackHandler(principal, password), logoutSubject();
createJaasConfiguration());
loginContext.login();
logger.debug("Principal " + principal + " authenticated succesfully");
loginContext.logout();
return true; return true;
} catch (LoginException le) { } catch (LoginException le) {
logger.debug("Failed to authenticate user " + username, 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) { protected String getKerberosPrincipal(String username) {
return username + "@" + config.getKerberosRealm(); return username + "@" + config.getKerberosRealm();
} }

View file

@ -47,14 +47,7 @@ public class SPNEGOAuthenticator {
Subject serverSubject = kerberosSubjectAuthenticator.authenticateServerSubject(); Subject serverSubject = kerberosSubjectAuthenticator.authenticateServerSubject();
authenticated = Subject.doAs(serverSubject, new AcceptSecContext()); authenticated = Subject.doAs(serverSubject, new AcceptSecContext());
} catch (Exception e) { } catch (Exception e) {
String message = e.getMessage(); log.warn("SPNEGO login failed: " + e.getMessage(), e);
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);
}
} finally { } finally {
kerberosSubjectAuthenticator.logoutServerSubject(); kerberosSubjectAuthenticator.logoutServerSubject();
} }

View file

@ -6,6 +6,7 @@ import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -14,7 +15,7 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; 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.IdentityManagementException;
import org.picketlink.idm.IdentityManager; import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager; import org.picketlink.idm.PartitionManager;
@ -39,7 +40,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class); private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
public static final String LDAP_ID = "LDAP_ID"; public static final String LDAP_ID = "LDAP_ID";
public static final String SYNC_REGISTRATIONS = "syncRegistrations"; public static final String SYNC_REGISTRATIONS = "syncRegistrations";
public static final String EDIT_MODE = "editMode";
protected LDAPFederationProviderFactory factory; protected LDAPFederationProviderFactory factory;
protected KeycloakSession session; protected KeycloakSession session;
@ -56,7 +56,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
this.model = model; this.model = model;
this.partitionManager = partitionManager; this.partitionManager = partitionManager;
this.kerberosConfig = new LDAPProviderKerberosConfig(model); this.kerberosConfig = new LDAPProviderKerberosConfig(model);
String editModeString = model.getConfig().get(EDIT_MODE); String editModeString = model.getConfig().get(LDAPConstants.EDIT_MODE);
if (editModeString == null) { if (editModeString == null) {
editMode = EditMode.READ_ONLY; editMode = EditMode.READ_ONLY;
} else { } else {
@ -353,6 +353,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
String username = spnegoAuthenticator.getAuthenticatedUsername(); String username = spnegoAuthenticator.getAuthenticatedUsername();
UserModel user = findOrCreateAuthenticatedUser(realm, username); 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); return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
} else { } else {
Map<String, Object> state = new HashMap<String, Object>(); Map<String, Object> state = new HashMap<String, Object>();
@ -404,15 +409,17 @@ public class LDAPFederationProvider implements UserFederationProvider {
UserModel user = session.userStorage().getUserByUsername(username, realm); UserModel user = session.userStorage().getUserByUsername(username, realm);
if (user != null) { if (user != null) {
logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage"); logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage");
if (!isValid(user)) { 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));
}
return proxy(user); return proxy(user);
} else { } 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);
}
}
// Creating user to local storage // Creating user to local storage
logger.debug("Kerberos authenticated user " + username + " not in Keycloak storage. Creating him");
return getUserByUsername(realm, username); return getUserByUsername(realm, username);
} }
}
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.federation.ldap.kerberos; package org.keycloak.federation.ldap.kerberos;
import org.keycloak.federation.kerberos.CommonKerberosConfig; import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.models.KerberosConstants;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
/** /**
@ -15,6 +16,6 @@ public class LDAPProviderKerberosConfig extends CommonKerberosConfig {
} }
public boolean isUseKerberosForPasswordAuthentication() { public boolean isUseKerberosForPasswordAuthentication() {
return Boolean.valueOf(getConfig().get("useKerberosForPasswordAuthentication")); return Boolean.valueOf(getConfig().get(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION));
} }
} }

View file

@ -547,10 +547,6 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
{ "id": "other", "name": "Other" } { "id": "other", "name": "Other" }
]; ];
$scope.usernameLDAPAttributes = [
"uid", "cn", "sAMAccountName", "entryDN"
];
$scope.realm = realm; $scope.realm = realm;
$scope.$watch('fullSyncEnabled', function(newVal, oldVal) { $scope.$watch('fullSyncEnabled', function(newVal, oldVal) {
@ -581,7 +577,7 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
$scope.lastVendor = $scope.instance.config.vendor; $scope.lastVendor = $scope.instance.config.vendor;
if ($scope.lastVendor === "ad") { if ($scope.lastVendor === "ad") {
$scope.instance.config.usernameLDAPAttribute = "cn"; $scope.instance.config.usernameLDAPAttribute = "sAMAccountName";
$scope.instance.config.userObjectClasses = "person, organizationalPerson, user"; $scope.instance.config.userObjectClasses = "person, organizationalPerson, user";
} else { } else {
$scope.instance.config.usernameLDAPAttribute = "uid"; $scope.instance.config.usernameLDAPAttribute = "uid";

View file

@ -78,15 +78,9 @@
<div class="form-group clearfix"> <div class="form-group clearfix">
<label class="col-sm-2 control-label" for="usernameLDAPAttribute">Username LDAP attribute<span class="required">*</span></label> <label class="col-sm-2 control-label" for="usernameLDAPAttribute">Username LDAP attribute<span class="required">*</span></label>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="select-kc"> <input class="form-control" id="usernameLDAPAttribute" type="text" ng-model="instance.config.usernameLDAPAttribute" placeholder="LDAP attribute for uid" required>
<select id="usernameLDAPAttribute"
ng-model="instance.config.usernameLDAPAttribute"
ng-options="usernameLDAPAttribute for usernameLDAPAttribute in usernameLDAPAttributes"
required>
</select>
</div> </div>
</div> <span tooltip-placement="right" tooltip="Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server vendors it's 'uid'. For Active directory it's usually 'sAMAccountName' or 'cn'" class="fa fa-info-circle"></span>
<span tooltip-placement="right" tooltip="Name of LDAP attribute, which is mapped as Keycloak username" class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group clearfix"> <div class="form-group clearfix">
<label class="col-sm-2 control-label" for="userObjectClasses">User Object Classes<span class="required">*</span></label> <label class="col-sm-2 control-label" for="userObjectClasses">User Object Classes<span class="required">*</span></label>

View file

@ -1,4 +1,4 @@
package org.keycloak.models.utils; package org.keycloak.models;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -10,26 +10,38 @@ public class KerberosConstants {
**/ **/
public static final String NEGOTIATE = "Negotiate"; public static final String NEGOTIATE = "Negotiate";
/** /**
* OID of SPNEGO mechanism. See http://www.oid-info.com/get/1.3.6.1.5.5.2 * 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"; 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 * 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"; 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 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 * 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"; public static final String RESPONSE_TOKEN = "SpnegoResponseToken";
/** /**
* Internal attribute used in "state" map . Contains credential from SPNEGO/Kerberos successful authentication * Internal attribute used in "state" map . Contains credential from SPNEGO/Kerberos successful authentication
*/ */

View file

@ -23,6 +23,8 @@ public class LDAPConstants {
public static final String CONNECTION_POOLING = "connectionPooling"; public static final String CONNECTION_POOLING = "connectionPooling";
public static final String PAGINATION = "pagination"; public static final String PAGINATION = "pagination";
public static final String EDIT_MODE = "editMode";
// Count of users processed per single transaction during sync process // Count of users processed per single transaction during sync process
public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync"; public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000; public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;

View file

@ -384,6 +384,7 @@ public class UserFederationManager implements UserProvider {
return CredentialValidationOutput.failed(); return CredentialValidationOutput.failed();
} }
logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType());
CredentialValidationOutput currentResult = providerSupportingCreds.validCredentials(realm, cred); CredentialValidationOutput currentResult = providerSupportingCreds.validCredentials(realm, cred);
result = (result == null) ? currentResult : result.merge(currentResult); result = (result == null) ? currentResult : result.merge(currentResult);
} }

View file

@ -595,7 +595,7 @@
<version>2.16</version> <version>2.16</version>
<configuration> <configuration>
<forkMode>once</forkMode> <forkMode>once</forkMode>
<argLine>-Xms512m -Xmx512m</argLine> <argLine>-Xms512m -Xmx512m -XX:MaxPermSize=256m</argLine>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>

View file

@ -7,6 +7,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
@ -18,7 +19,7 @@ import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; 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.protocol.oidc.TokenManager;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
@ -59,11 +60,15 @@ public class HttpAuthenticationManager {
boolean kerberosSupported = false; boolean kerberosSupported = false;
for (RequiredCredentialModel c : realm.getRequiredCredentials()) { for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
if (c.getType().equals(CredentialRepresentation.KERBEROS)) { if (c.getType().equals(CredentialRepresentation.KERBEROS)) {
logger.debug("Kerberos authentication is supported");
kerberosSupported = true; kerberosSupported = true;
} }
} }
if (logger.isTraceEnabled()) {
String log = kerberosSupported ? "SPNEGO authentication is supported" : "SPNEGO authentication is not supported";
logger.trace(log);
}
if (!kerberosSupported) { if (!kerberosSupported) {
return new HttpAuthOutput(null, null); return new HttpAuthOutput(null, null);
} }
@ -100,6 +105,10 @@ public class HttpAuthenticationManager {
// Send response after successful authentication // Send response after successful authentication
private HttpAuthOutput sendResponse(UserModel user, String authMethod) { private HttpAuthOutput sendResponse(UserModel user, String authMethod) {
if (logger.isTraceEnabled()) {
logger.trace("User " + user.getUsername() + " authenticated with " + authMethod);
}
Response response; Response response;
if (!user.isEnabled()) { if (!user.isEnabled()) {
event.error(Errors.USER_DISABLED); event.error(Errors.USER_DISABLED);
@ -107,7 +116,10 @@ public class HttpAuthenticationManager {
} else { } else {
UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false); UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false);
TokenManager.attachClientSession(userSession, clientSession); 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); response = AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
} }
@ -128,7 +140,6 @@ public class HttpAuthenticationManager {
loginFormsProvider.setStatus(Response.Status.UNAUTHORIZED); loginFormsProvider.setStatus(Response.Status.UNAUTHORIZED);
loginFormsProvider.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader); loginFormsProvider.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader);
loginFormsProvider.setWarning("errorKerberosLogin");
} }
}); });

View file

@ -10,7 +10,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel; 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.models.utils.ModelToRepresentation;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation; import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;

View file

@ -123,6 +123,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -27,7 +27,7 @@ import org.jboss.logging.Logger;
*/ */
public class KerberosEmbeddedServer extends LDAPEmbeddedServer { 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 String kerberosRealm;
private final int kdcPort; private final int kdcPort;
@ -117,7 +117,7 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
protected void stopKerberosServer() { protected void stopKerberosServer() {
log.info("Stoping Kerberos server."); log.info("Stopping Kerberos server.");
kdcServer.stop(); kdcServer.stop();
} }

View file

@ -1,55 +1,88 @@
package org.keycloak.testutils.ldap; package org.keycloak.testutils.ldap;
import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.jboss.logging.Logger;
import org.keycloak.models.KerberosConstants;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProvider;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class LDAPConfiguration { 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"; private String connectionPropertiesLocation;
protected String baseDn = "dc=keycloak,dc=org"; private boolean startEmbeddedLdapLerver = true;
protected String userDnSuffix = "ou=People,dc=keycloak,dc=org"; private Map<String, String> config;
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;
public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url"; protected static final Map<String, String> PROP_MAPPINGS = new HashMap<String, String>();
public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn"; protected static final Map<String, String> DEFAULT_VALUES = new HashMap<String, String>();
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";
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 ldapConfiguration = new LDAPConfiguration();
ldapConfiguration.setConnectionPropertiesLocation(connectionPropertiesLocation);
ldapConfiguration.loadConnectionProperties(); ldapConfiguration.loadConnectionProperties();
return ldapConfiguration; return ldapConfiguration;
} }
@ -57,109 +90,42 @@ public class LDAPConfiguration {
protected void loadConnectionProperties() { protected void loadConnectionProperties() {
Properties p = new Properties(); Properties p = new Properties();
try { 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); p.load(is);
} }
catch (Exception e) { catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
connectionUrl = p.getProperty(IDM_TEST_LDAP_CONNECTION_URL, connectionUrl); config = new HashMap<String, String>();
baseDn = p.getProperty(IDM_TEST_LDAP_BASE_DN, baseDn); for (Map.Entry<String, String> property : PROP_MAPPINGS.entrySet()) {
userDnSuffix = p.getProperty(IDM_TEST_LDAP_USER_DN_SUFFIX, userDnSuffix); String propertyName = property.getKey();
rolesDnSuffix = p.getProperty(IDM_TEST_LDAP_ROLES_DN_SUFFIX, rolesDnSuffix); String configName = property.getValue();
groupDnSuffix = p.getProperty(IDM_TEST_LDAP_GROUP_DN_SUFFIX, groupDnSuffix);
agentDnSuffix = p.getProperty(IDM_TEST_LDAP_AGENT_DN_SUFFIX, agentDnSuffix); String value = (String) p.get(configName);
startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true")); if (value == null) {
bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn); value = DEFAULT_VALUES.get(propertyName);
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")); config.put(propertyName, value);
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); startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty("idm.test.ldap.start.embedded.ldap.server", "true"));
userObjectClasses = p.getProperty(IDM_TEST_LDAP_USER_OBJECT_CLASSES); log.info("Start embedded server: " + startEmbeddedLdapLerver);
userAccountControlsAfterPasswordUpdate = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)); log.info("Read config: " + config);
} }
public Map<String,String> getLDAPConfig() { public Map<String,String> getLDAPConfig() {
Map<String,String> ldapConfig = new HashMap<String,String>(); return config;
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;
} }
public String getConnectionUrl() { public void setConnectionPropertiesLocation(String connectionPropertiesLocation) {
return connectionUrl; this.connectionPropertiesLocation = connectionPropertiesLocation;
}
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 boolean isStartEmbeddedLdapLerver() { public boolean isStartEmbeddedLdapLerver() {
return startEmbeddedLdapLerver; 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;
}
} }

View file

@ -150,7 +150,7 @@ public class LDAPEmbeddedServer {
} }
final String ldifContent = StrSubstitutor.replace(StreamUtil.readString(is), map); 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(); final SchemaManager schemaManager = directoryService.getSchemaManager();
for (LdifEntry ldifEntry : new LdifReader(IOUtils.toInputStream(ldifContent))) { for (LdifEntry ldifEntry : new LdifReader(IOUtils.toInputStream(ldifContent))) {
@ -170,13 +170,13 @@ public class LDAPEmbeddedServer {
protected void stopLdapServer() { protected void stopLdapServer() {
log.info("Stoping LDAP server."); log.info("Stopping LDAP server.");
ldapServer.stop(); ldapServer.stop();
} }
protected void shutdownDirectoryService() throws Exception { protected void shutdownDirectoryService() throws Exception {
log.info("Stoping Directory service."); log.info("Stopping Directory service.");
directoryService.shutdown(); directoryService.shutdown();
log.info("Removing Directory service workfiles."); log.info("Removing Directory service workfiles.");

View file

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

View file

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

View file

@ -0,0 +1,18 @@
[libdefaults]
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
dns_canonicalize_hostname = false
ignore_acceptor_hostname = true
[realms]
KEYCLOAK.ORG = {
kdc = localhost:6088
}
[domain_realm]
localhost = KEYCLOAK.ORG

View file

@ -54,8 +54,9 @@ objectClass: person
objectClass: inetOrgPerson objectClass: inetOrgPerson
objectClass: krb5principal objectClass: krb5principal
objectClass: krb5kdcentry objectClass: krb5kdcentry
cn: Horatio Nelson cn: Horatio
sn: Nelson sn: Nelson
mail: hnelson@keycloak.org
uid: hnelson uid: hnelson
userPassword: secret userPassword: secret
krb5PrincipalName: hnelson@KEYCLOAK.ORG krb5PrincipalName: hnelson@KEYCLOAK.ORG
@ -67,8 +68,9 @@ objectClass: person
objectClass: inetOrgPerson objectClass: inetOrgPerson
objectClass: krb5principal objectClass: krb5principal
objectClass: krb5kdcentry objectClass: krb5kdcentry
cn: Java Duke cn: Java
sn: duke sn: Duke
mail: jduke@keycloak.org
uid: jduke uid: jduke
userPassword: theduke userPassword: theduke
krb5PrincipalName: jduke@KEYCLOAK.ORG krb5PrincipalName: jduke@KEYCLOAK.ORG

View file

@ -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.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org
idm.test.ldap.group.dn.suffix=ou\=Groups,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.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.start.embedded.ldap.server=true
idm.test.ldap.bind.dn=uid\=admin,ou\=system idm.test.ldap.bind.dn=uid\=admin,ou\=system
idm.test.ldap.bind.credential=secret idm.test.ldap.bind.credential=secret

View file

@ -20,7 +20,7 @@ log4j.logger.org.keycloak=info
# log4j.logger.org.keycloak.broker.kerberos=trace # log4j.logger.org.keycloak.broker.kerberos=trace
# Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server # 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.xnio=off
log4j.logger.org.hibernate=off log4j.logger.org.hibernate=off

View file

@ -0,0 +1,266 @@
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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");
response.close();
}
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));
spnegoResponse.close();
}
@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();
spnegoResponse.close();
}
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<UserModel> 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);
}
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.forms; package org.keycloak.testsuite.federation;
import org.junit.Assert; import org.junit.Assert;
import org.junit.ClassRule; 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.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
@ -55,9 +56,9 @@ public class FederationProvidersIntegrationTest {
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app"); addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
Map<String,String> ldapConfig = ldapRule.getLdapConfig(); Map<String,String> ldapConfig = ldapRule.getConfig();
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true"); 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); 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(), UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(),
ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0); 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); appRealm.updateUserFederationProvider(model);
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm); UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertNotNull(user); Assert.assertNotNull(user);
@ -312,7 +313,7 @@ public class FederationProvidersIntegrationTest {
session = keycloakRule.startSession(); session = keycloakRule.startSession();
try { try {
RealmModel appRealm = session.realms().getRealmByName("test"); 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 { } finally {
keycloakRule.stopSession(session, false); keycloakRule.stopSession(session, false);
} }
@ -380,7 +381,7 @@ public class FederationProvidersIntegrationTest {
UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(),
ldapModel.getDisplayName(), -1, -1, 0); 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); appRealm.updateUserFederationProvider(model);
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm); UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertNotNull(user); Assert.assertNotNull(user);
@ -405,7 +406,7 @@ public class FederationProvidersIntegrationTest {
session = keycloakRule.startSession(); session = keycloakRule.startSession();
try { try {
RealmModel appRealm = session.realms().getRealmByName("test"); 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 { } finally {
keycloakRule.stopSession(session, false); keycloakRule.stopSession(session, false);
} }

View file

@ -0,0 +1,148 @@
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String,String> 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();
spnegoResponse.close();
events.clear();
}
}

View file

@ -0,0 +1,125 @@
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String,String> 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"));
spnegoResponse.close();
// 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);
}
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<ByteArrayHolder> {
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;
}
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.testsuite.forms; package org.keycloak.testsuite.federation;
import org.junit.Assert; import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
@ -12,6 +12,7 @@ import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
@ -48,9 +49,9 @@ public class SyncProvidersTest {
// Other tests may left Time offset uncleared, which could cause issues // Other tests may left Time offset uncleared, which could cause issues
Time.setOffset(0); Time.setOffset(0);
Map<String,String> ldapConfig = ldapRule.getLdapConfig(); Map<String,String> ldapConfig = ldapRule.getConfig();
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false"); 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", ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
-1, -1, 0); -1, -1, 0);

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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();
}
}

View file

@ -12,16 +12,19 @@ import org.keycloak.testutils.ldap.LDAPEmbeddedServer;
*/ */
public class LDAPRule extends ExternalResource { public class LDAPRule extends ExternalResource {
private LDAPConfiguration ldapConfiguration; public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties";
private LDAPEmbeddedServer ldapEmbeddedServer;
protected LDAPConfiguration ldapConfiguration;
protected LDAPEmbeddedServer ldapEmbeddedServer;
@Override @Override
protected void before() throws Throwable { protected void before() throws Throwable {
ldapConfiguration = LDAPConfiguration.readConfiguration(); String connectionPropsLocation = getConnectionPropertiesLocation();
ldapConfiguration = LDAPConfiguration.readConfiguration(connectionPropsLocation);
if (ldapConfiguration.isStartEmbeddedLdapLerver()) { if (ldapConfiguration.isStartEmbeddedLdapLerver()) {
EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration(); EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
ldapEmbeddedServer = factory.createLdapServer(); ldapEmbeddedServer = createServer(factory);
ldapEmbeddedServer.init(); ldapEmbeddedServer.init();
ldapEmbeddedServer.start(); ldapEmbeddedServer.start();
} }
@ -40,7 +43,15 @@ public class LDAPRule extends ExternalResource {
} }
} }
public Map<String, String> getLdapConfig() { protected String getConnectionPropertiesLocation() {
return LDAP_CONNECTION_PROPERTIES_LOCATION;
}
protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) {
return factory.createLdapServer();
}
public Map<String, String> getConfig() {
return ldapConfiguration.getLDAPConfig(); return ldapConfiguration.getLDAPConfig();
} }
} }

View file

@ -113,6 +113,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -113,6 +113,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -113,6 +113,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -118,6 +118,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -112,6 +112,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -113,6 +113,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>

View file

@ -112,6 +112,11 @@
<artifactId>keycloak-ldap-federation</artifactId> <artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId> <artifactId>keycloak-undertow-adapter</artifactId>