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>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-github</artifactId>
@ -129,6 +124,11 @@
<artifactId>keycloak-ldap-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>

View file

@ -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<String, String> getConfig() {

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {
@ -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<String, Object> state = new HashMap<String, Object>();
@ -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);
}
}

View file

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

View file

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

View file

@ -78,15 +78,9 @@
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="usernameLDAPAttribute">Username LDAP attribute<span class="required">*</span></label>
<div class="col-sm-4">
<div class="select-kc">
<select id="usernameLDAPAttribute"
ng-model="instance.config.usernameLDAPAttribute"
ng-options="usernameLDAPAttribute for usernameLDAPAttribute in usernameLDAPAttributes"
required>
</select>
</div>
<input class="form-control" id="usernameLDAPAttribute" type="text" ng-model="instance.config.usernameLDAPAttribute" placeholder="LDAP attribute for uid" required>
</div>
<span tooltip-placement="right" tooltip="Name of LDAP attribute, which is mapped as Keycloak username" class="fa fa-info-circle"></span>
<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>
</div>
<div class="form-group clearfix">
<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>
@ -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
*/

View file

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

View file

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

View file

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

View file

@ -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);
}
@ -128,7 +140,6 @@ public class HttpAuthenticationManager {
loginFormsProvider.setStatus(Response.Status.UNAUTHORIZED);
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.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;

View file

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

View file

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

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String, String> 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<String, String> PROP_MAPPINGS = new HashMap<String, String>();
protected static final Map<String, String> DEFAULT_VALUES = new HashMap<String, String>();
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<String, String>();
for (Map.Entry<String, String> 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<String,String> getLDAPConfig() {
Map<String,String> ldapConfig = new HashMap<String,String>();
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;
}
}

View file

@ -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.");

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

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

View file

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

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.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<String,String> ldapConfig = ldapRule.getLdapConfig();
Map<String,String> 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);
}

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.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<String,String> ldapConfig = ldapRule.getLdapConfig();
Map<String,String> 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);

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 {
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<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();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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