Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
d06b7a47ac
41 changed files with 1007 additions and 220 deletions
10
dependencies/server-all/pom.xml
vendored
10
dependencies/server-all/pom.xml
vendored
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
*/
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue