[KEYCLOAK-12305] [Testsuite] Check LDAP federated user (in)valid
login(s) using various authentication methods, bind credential types, and connection encryption mechanisms The tests cover various possible combinations of the following: * Authentication method: Anonymous or Simple (default), * Bind credential: Secret (default) or Vault, * Connection encryption: Plaintext (default), SSL, or startTLS Also, ignore the StartTLS LDAP tests for now till KEYCLOAK-14343 & KEYCLOAK-14354 are corrected (due these issues they aren't working with auth server Wildfly). They will be re-enabled later via KEYCLOAK-14358 once possible Signed-off-by: Jan Lieskovsky <jlieskov@redhat.com>
This commit is contained in:
parent
e8dc10b4a1
commit
a121f77ea4
3 changed files with 606 additions and 18 deletions
|
@ -17,12 +17,20 @@
|
|||
|
||||
package org.keycloak.testsuite.util;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.junit.Assume;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.util.ldap.LDAPEmbeddedServer;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
|
@ -33,10 +41,22 @@ import static org.keycloak.testsuite.utils.io.IOUtil.PROJECT_BUILD_DIRECTORY;
|
|||
*/
|
||||
public class LDAPRule extends ExternalResource {
|
||||
|
||||
private static final Logger log = Logger.getLogger(LDAPRule.class);
|
||||
|
||||
// Note: Be sure to annotate the testing class with the "EnableVault" annotation
|
||||
// to get the necessary FilePlaintext vault created automatically for the test
|
||||
private static final String VAULT_EXPRESSION = "${vault.ldap_bindCredential}";
|
||||
|
||||
public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "classpath:ldap/ldap-connection.properties";
|
||||
|
||||
private static final String PROPERTY_ENABLE_ACCESS_CONTROL = "enableAccessControl";
|
||||
|
||||
private static final String PROPERTY_ENABLE_ANONYMOUS_ACCESS = "enableAnonymousAccess";
|
||||
|
||||
private static final String PROPERTY_ENABLE_SSL = "enableSSL";
|
||||
|
||||
private static final String PROPERTY_ENABLE_STARTTLS = "enableStartTLS";
|
||||
|
||||
private static final String PROPERTY_KEYSTORE_FILE = "keystoreFile";
|
||||
|
||||
private static final String PRIVATE_KEY = "dependency/keystore/keycloak.jks";
|
||||
|
@ -47,12 +67,13 @@ public class LDAPRule extends ExternalResource {
|
|||
private LDAPEmbeddedServer ldapEmbeddedServer;
|
||||
private LDAPAssume assume;
|
||||
|
||||
protected Properties defaultProperties = new Properties();
|
||||
|
||||
public LDAPRule assumeTrue(LDAPAssume assume) {
|
||||
this.assume = assume;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
String connectionPropsLocation = getConnectionPropertiesLocation();
|
||||
|
@ -67,6 +88,80 @@ public class LDAPRule extends ExternalResource {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
// Default bind credential value
|
||||
defaultProperties.setProperty(LDAPConstants.BIND_CREDENTIAL, "secret");
|
||||
// Default values of the authentication / access control method and connection encryption to use on the embedded
|
||||
// LDAP server upon start if not (re)set later via the LDAPConnectionParameters annotation directly on the test
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ACCESS_CONTROL, "true");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ANONYMOUS_ACCESS, "false");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL, "true");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_STARTTLS, "false");
|
||||
// Default LDAP server confidentiality required value
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_SET_CONFIDENTIALITY_REQUIRED, "false");
|
||||
|
||||
// Don't auto-update LDAP connection URL read from properties file for LDAP over SSL case even if it's wrong
|
||||
// (AKA don't try to guess, let the user to get it corrected in the properties file first)
|
||||
defaultProperties.setProperty("AUTO_UPDATE_LDAP_CONNECTION_URL", "false");
|
||||
|
||||
Annotation ldapConnectionAnnotation = description.getAnnotation(LDAPConnectionParameters.class);
|
||||
if (ldapConnectionAnnotation != null) {
|
||||
// Mark the LDAP connection URL as auto-adjustable to correspond to specific annotation as necessary
|
||||
defaultProperties.setProperty("AUTO_UPDATE_LDAP_CONNECTION_URL", "true");
|
||||
LDAPConnectionParameters connectionParameters = (LDAPConnectionParameters) ldapConnectionAnnotation;
|
||||
// Configure the bind credential type of the LDAP rule depending on the provided annotation arguments
|
||||
switch (connectionParameters.bindCredential()) {
|
||||
case SECRET:
|
||||
log.debug("Setting bind credential to secret.");
|
||||
defaultProperties.setProperty(LDAPConstants.BIND_CREDENTIAL, "secret");
|
||||
break;
|
||||
case VAULT:
|
||||
log.debug("Setting bind credential to vault.");
|
||||
defaultProperties.setProperty(LDAPConstants.BIND_CREDENTIAL, VAULT_EXPRESSION);
|
||||
break;
|
||||
}
|
||||
// Configure the authentication method of the LDAP rule depending on the provided annotation arguments
|
||||
switch (connectionParameters.bindType()) {
|
||||
case NONE:
|
||||
log.debug("Enabling anonymous authentication method on the LDAP server.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ANONYMOUS_ACCESS, "true");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ACCESS_CONTROL, "false");
|
||||
break;
|
||||
case SIMPLE:
|
||||
log.debug("Disabling anonymous authentication method on the LDAP server.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ANONYMOUS_ACCESS, "false");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ACCESS_CONTROL, "true");
|
||||
break;
|
||||
}
|
||||
// Configure the connection encryption of the LDAP rule depending on the provided annotation arguments
|
||||
switch (connectionParameters.encryption()) {
|
||||
case NONE:
|
||||
log.debug("Disabling connection encryption on the LDAP server.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL, "false");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_STARTTLS, "false");
|
||||
break;
|
||||
case SSL:
|
||||
log.debug("Enabling SSL connection encryption on the LDAP server.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL, "true");
|
||||
// Require the LDAP server to accept only secured connections with SSL enabled
|
||||
log.debug("Configuring the LDAP server to accepts only requests with a secured connection.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_SET_CONFIDENTIALITY_REQUIRED, "true");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_STARTTLS, "false");
|
||||
break;
|
||||
case STARTTLS:
|
||||
log.debug("Enabling StartTLS connection encryption on the LDAP server.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_STARTTLS, "true");
|
||||
// Require the LDAP server to accept only secured connections with StartTLS enabled
|
||||
log.debug("Configuring the LDAP server to accepts only requests with a secured connection.");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_SET_CONFIDENTIALITY_REQUIRED, "true");
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL, "false");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return super.apply(base, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
try {
|
||||
|
@ -85,10 +180,8 @@ public class LDAPRule extends ExternalResource {
|
|||
}
|
||||
|
||||
protected LDAPEmbeddedServer createServer() {
|
||||
Properties defaultProperties = new Properties();
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif");
|
||||
defaultProperties.setProperty(PROPERTY_ENABLE_SSL, "true");
|
||||
defaultProperties.setProperty(PROPERTY_CERTIFICATE_PASSWORD, "secret");
|
||||
defaultProperties.setProperty(PROPERTY_KEYSTORE_FILE, new File(PROJECT_BUILD_DIRECTORY, PRIVATE_KEY).getAbsolutePath());
|
||||
|
||||
|
@ -96,18 +189,113 @@ public class LDAPRule extends ExternalResource {
|
|||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return ldapTestConfiguration.getLDAPConfig();
|
||||
Map<String, String> config = ldapTestConfiguration.getLDAPConfig();
|
||||
String ldapConnectionUrl = config.get(LDAPConstants.CONNECTION_URL);
|
||||
if (ldapConnectionUrl != null && defaultProperties.getProperty("AUTO_UPDATE_LDAP_CONNECTION_URL").equals("true")) {
|
||||
if (
|
||||
ldapConnectionUrl.startsWith("ldap://") &&
|
||||
defaultProperties.getProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL).equals("true")
|
||||
)
|
||||
{
|
||||
// Switch protocol prefix to "ldaps://" in connection URL if LDAP over SSL is requested
|
||||
String updatedUrl = ldapConnectionUrl.replaceAll("ldap://", "ldaps://");
|
||||
// Flip port number from LDAP to LDAPS
|
||||
updatedUrl = updatedUrl.replaceAll(
|
||||
String.valueOf(ldapEmbeddedServer.getBindPort()),
|
||||
String.valueOf(ldapEmbeddedServer.getBindLdapsPort())
|
||||
);
|
||||
config.put(LDAPConstants.CONNECTION_URL, updatedUrl);
|
||||
log.debugf("Using LDAP over SSL \"%s\" connection URL form over: \"%s\" since SSL connection was requested.", updatedUrl, ldapConnectionUrl);
|
||||
}
|
||||
if (
|
||||
ldapConnectionUrl.startsWith("ldaps://") &&
|
||||
!defaultProperties.getProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL).equals("true")
|
||||
)
|
||||
{
|
||||
// Switch protocol prefix back to "ldap://" in connection URL if LDAP over SSL flag is not set
|
||||
String updatedUrl = ldapConnectionUrl.replaceAll("ldaps://", "ldap://");
|
||||
// Flip port number from LDAPS to LDAP
|
||||
updatedUrl = updatedUrl.replaceAll(
|
||||
String.valueOf(ldapEmbeddedServer.getBindLdapsPort()),
|
||||
String.valueOf(ldapEmbeddedServer.getBindPort())
|
||||
);
|
||||
config.put(LDAPConstants.CONNECTION_URL, updatedUrl);
|
||||
log.debugf("Using plaintext / startTLS \"%s\" connection URL form over: \"%s\" since plaintext / startTLS connection was requested.", updatedUrl, ldapConnectionUrl);
|
||||
}
|
||||
}
|
||||
switch (defaultProperties.getProperty(LDAPConstants.BIND_CREDENTIAL)) {
|
||||
case VAULT_EXPRESSION:
|
||||
config.put(LDAPConstants.BIND_CREDENTIAL, VAULT_EXPRESSION);
|
||||
break;
|
||||
default:
|
||||
// Default to secret as the bind credential
|
||||
config.put(LDAPConstants.BIND_CREDENTIAL, "secret");
|
||||
}
|
||||
switch (defaultProperties.getProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_ANONYMOUS_ACCESS)) {
|
||||
case "true":
|
||||
config.put(LDAPConstants.AUTH_TYPE, LDAPConstants.AUTH_TYPE_NONE);
|
||||
break;
|
||||
default:
|
||||
// Default to username + password LDAP authentication method
|
||||
config.put(LDAPConstants.AUTH_TYPE, LDAPConstants.AUTH_TYPE_SIMPLE);
|
||||
}
|
||||
switch (defaultProperties.getProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_STARTTLS)) {
|
||||
case "true":
|
||||
config.put(LDAPConstants.START_TLS, "true");
|
||||
break;
|
||||
default:
|
||||
// Default to startTLS disabled
|
||||
config.put(LDAPConstants.START_TLS, "false");
|
||||
}
|
||||
switch (defaultProperties.getProperty(LDAPEmbeddedServer.PROPERTY_SET_CONFIDENTIALITY_REQUIRED)) {
|
||||
case "true":
|
||||
System.setProperty("PROPERTY_SET_CONFIDENTIALITY_REQUIRED", "true");
|
||||
break;
|
||||
default:
|
||||
// Configure the LDAP server to accept not secured connections from clients by default
|
||||
System.setProperty("PROPERTY_SET_CONFIDENTIALITY_REQUIRED", "false");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public int getSleepTime() {
|
||||
return ldapTestConfiguration.getSleepTime();
|
||||
}
|
||||
|
||||
|
||||
/** Allows to run particular LDAP test just under specific conditions (eg. some test running just on Active Directory) **/
|
||||
public interface LDAPAssume {
|
||||
|
||||
boolean assumeTrue(LDAPTestConfiguration ldapConfig);
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface LDAPConnectionParameters {
|
||||
// Default to secret as the bind credential unless annotated otherwise
|
||||
BindCredential bindCredential() default LDAPConnectionParameters.BindCredential.SECRET;
|
||||
// Disable anonymous LDAP authentication by default unless annotated otherwise
|
||||
BindType bindType() default LDAPConnectionParameters.BindType.SIMPLE;
|
||||
// Enable SSL encrypted LDAP connections (along with the unencrypted ones) by default unless annotated otherwise
|
||||
Encryption encryption() default LDAPConnectionParameters.Encryption.SSL;
|
||||
|
||||
public enum BindCredential {
|
||||
SECRET,
|
||||
VAULT
|
||||
}
|
||||
|
||||
public enum BindType {
|
||||
NONE,
|
||||
SIMPLE
|
||||
}
|
||||
|
||||
public enum Encryption {
|
||||
NONE,
|
||||
// Important: Choosing either of "SSL" or "STARTTLS" connection encryption methods below
|
||||
// will also configure the LDAP server to accept only a secured connection from clients
|
||||
// (IOW plaintext client connections will be prohibited). Use those two options with care!
|
||||
SSL,
|
||||
STARTTLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.federation.ldap;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableVault;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.LDAPRule;
|
||||
import org.keycloak.testsuite.util.LDAPRule.LDAPConnectionParameters;
|
||||
import org.keycloak.testsuite.util.LDAPTestConfiguration;
|
||||
import org.keycloak.testsuite.util.LDAPTestUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Test user logins utilizing various LDAP authentication methods and different LDAP connection encryption mechanisms.
|
||||
*
|
||||
* @author <a href="mailto:jlieskov@redhat.com">Jan Lieskovsky</a>
|
||||
*/
|
||||
@EnableVault
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LDAPUserLoginTest extends AbstractLDAPTest {
|
||||
|
||||
@Rule
|
||||
// Start an embedded LDAP server with configuration derived from test annotations before each test
|
||||
public LDAPRule ldapRule = new LDAPRule()
|
||||
.assumeTrue((LDAPTestConfiguration ldapConfig) -> {
|
||||
|
||||
return ldapConfig.isStartEmbeddedLdapServer();
|
||||
|
||||
});
|
||||
|
||||
@Override
|
||||
protected LDAPRule getLDAPRule() {
|
||||
return ldapRule;
|
||||
}
|
||||
|
||||
@Rule
|
||||
// Recreate a new LDAP provider based on test annotations before each test
|
||||
public ExternalResource ldapProviderRule = new ExternalResource() {
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
// Delete the previously imported realm(s) after each test. This forces
|
||||
// a new LDAP provider with custom configuration (derived from the test
|
||||
// annotations) to be created each time the next test is run
|
||||
if (getTestingClient() != null) {
|
||||
getTestContext().getTestRealmReps().clear();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
protected static final Map<String, String> DEFAULT_TEST_USERS = new HashMap<String, String>();
|
||||
static {
|
||||
DEFAULT_TEST_USERS.put("EMPTY_USER_PASSWORD", new String());
|
||||
DEFAULT_TEST_USERS.put("INVALID_USER_NAME", "userUnknown");
|
||||
DEFAULT_TEST_USERS.put("INVALID_USER_EMAIL", "unknown@keycloak.org");
|
||||
DEFAULT_TEST_USERS.put("INVALID_USER_PASSWORD", "1nval!D");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_EMAIL", "jdoe@keycloak.org");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_NAME", "jdoe");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_FIRST_NAME", "John");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_LAST_NAME", "Doe");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_PASSWORD", "P@ssw0rd!");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_POSTAL_CODE", "12345");
|
||||
DEFAULT_TEST_USERS.put("VALID_USER_STREET", "1th Avenue");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterImportTestRealm() {
|
||||
getTestingClient().server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
// Delete all LDAP users
|
||||
LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
|
||||
// Add some new LDAP users for testing
|
||||
LDAPObject john = LDAPTestUtils.addLDAPUser
|
||||
(
|
||||
ctx.getLdapProvider(),
|
||||
appRealm,
|
||||
DEFAULT_TEST_USERS.get("VALID_USER_NAME"),
|
||||
DEFAULT_TEST_USERS.get("VALID_USER_FIRST_NAME"),
|
||||
DEFAULT_TEST_USERS.get("VALID_USER_LAST_NAME"),
|
||||
DEFAULT_TEST_USERS.get("VALID_USER_EMAIL"),
|
||||
DEFAULT_TEST_USERS.get("VALID_USER_STREET"),
|
||||
DEFAULT_TEST_USERS.get("VALID_USER_POSTAL_CODE")
|
||||
);
|
||||
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, DEFAULT_TEST_USERS.get("VALID_USER_PASSWORD"));
|
||||
});
|
||||
}
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
// Helper methods
|
||||
private void verifyLoginSucceededAndLogout(String username, String password) {
|
||||
loginPage.open();
|
||||
loginPage.login(username, password);
|
||||
appPage.assertCurrent();
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
appPage.logout();
|
||||
}
|
||||
|
||||
private void verifyLoginFailed(String username, String password) {
|
||||
// Clear the events queue before the actual test to catch all errors properly
|
||||
events.clear();
|
||||
// Run the test actions
|
||||
loginPage.open();
|
||||
loginPage.login(username, password);
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
if (username.equals(DEFAULT_TEST_USERS.get("INVALID_USER_EMAIL")) || username.equals(DEFAULT_TEST_USERS.get("INVALID_USER_NAME"))) {
|
||||
|
||||
events.expect(EventType.LOGIN_ERROR).user((String) null).error(Errors.USER_NOT_FOUND).assertEvent();
|
||||
|
||||
} else if (username.equals(DEFAULT_TEST_USERS.get("VALID_USER_EMAIL")) || username.equals(DEFAULT_TEST_USERS.get("VALID_USER_NAME"))) {
|
||||
|
||||
List<UserRepresentation> knownUsers = getAdminClient().realm(TEST_REALM_NAME).users().search(DEFAULT_TEST_USERS.get("VALID_USER_NAME"));
|
||||
Assert.assertTrue(!knownUsers.isEmpty());
|
||||
final String userId = knownUsers.get(0).getId();
|
||||
events.expect(EventType.LOGIN_ERROR).user(userId).error(Errors.INVALID_USER_CREDENTIALS).assertEvent();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void runLDAPLoginTest() {
|
||||
final String emptyPassword = DEFAULT_TEST_USERS.get("EMPTY_USER_PASSWORD");
|
||||
final String invalidEmail = DEFAULT_TEST_USERS.get("INVALID_USER_EMAIL");
|
||||
final String invalidPassword = DEFAULT_TEST_USERS.get("INVALID_USER_PASSWORD");
|
||||
final String invalidUsername = DEFAULT_TEST_USERS.get("INVALID_USER_NAME");
|
||||
final String validEmail = DEFAULT_TEST_USERS.get("VALID_USER_EMAIL");
|
||||
final String validPassword = DEFAULT_TEST_USERS.get("VALID_USER_PASSWORD");
|
||||
final String validUsername = DEFAULT_TEST_USERS.get("VALID_USER_NAME");
|
||||
|
||||
// Check LDAP login via valid username + valid password
|
||||
verifyLoginSucceededAndLogout(validUsername, validPassword);
|
||||
// Check LDAP login via valid email + valid password
|
||||
verifyLoginSucceededAndLogout(validEmail, validPassword);
|
||||
// Check LDAP login via valid username + empty password
|
||||
verifyLoginFailed(validUsername, emptyPassword);
|
||||
// Check LDAP login via valid email + empty password
|
||||
verifyLoginFailed(validEmail, emptyPassword);
|
||||
// Check LDAP login via valid username + invalid password
|
||||
verifyLoginFailed(validUsername, invalidPassword);
|
||||
// Check LDAP login via valid email + invalid password
|
||||
verifyLoginFailed(validEmail, invalidPassword);
|
||||
// Check LDAP login via invalid username
|
||||
verifyLoginFailed(invalidUsername, invalidPassword);
|
||||
// Check LDAP login via invalid email
|
||||
verifyLoginFailed(invalidEmail, invalidPassword);
|
||||
}
|
||||
|
||||
private void verifyConnectionUrlProtocolPrefix(String ldapProtocolPrefix) {
|
||||
final String ldapConnectionUrl = ldapRule.getConfig().get(LDAPConstants.CONNECTION_URL);
|
||||
Assert.assertTrue(!ldapConnectionUrl.isEmpty() && ldapConnectionUrl.startsWith(ldapProtocolPrefix));
|
||||
}
|
||||
|
||||
// Tests themselves
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with simple authentication & encryption (both SSL and startTLS) disabled
|
||||
// Test variant: Bind credential set to secret (default)
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindType=LDAPConnectionParameters.BindType.SIMPLE, encryption=LDAPConnectionParameters.Encryption.NONE)
|
||||
public void loginLDAPUserAuthenticationSimpleEncryptionNone() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with simple authentication & encryption (both SSL and startTLS) disabled
|
||||
// Test variant: Bind credential set to vault
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindCredential=LDAPConnectionParameters.BindCredential.VAULT, bindType=LDAPConnectionParameters.BindType.SIMPLE, encryption=LDAPConnectionParameters.Encryption.NONE)
|
||||
public void loginLDAPUserCredentialVaultAuthenticationSimpleEncryptionNone() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with simple authentication & SSL encryption enabled
|
||||
// Test variant: Bind credential set to secret (default)
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindType=LDAPConnectionParameters.BindType.SIMPLE, encryption=LDAPConnectionParameters.Encryption.SSL)
|
||||
public void loginLDAPUserAuthenticationSimpleEncryptionSSL() {
|
||||
verifyConnectionUrlProtocolPrefix("ldaps://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with simple authentication & SSL encryption enabled
|
||||
// Test variant: Bind credential set to vault
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindCredential=LDAPConnectionParameters.BindCredential.VAULT, bindType=LDAPConnectionParameters.BindType.SIMPLE, encryption=LDAPConnectionParameters.Encryption.SSL)
|
||||
public void loginLDAPUserCredentialVaultAuthenticationSimpleEncryptionSSL() {
|
||||
verifyConnectionUrlProtocolPrefix("ldaps://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with simple authentication & startTLS encryption enabled
|
||||
// Test variant: Bind credential set to secret (default)
|
||||
// KEYCLOAK-14358 - Disable the StartTLS LDAP tests till KEYCLOAK-14343 & KEYCLOAK-14354 are corrected
|
||||
// since they don't work properly with auth server Wildfly due these bugs
|
||||
@Ignore
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindType=LDAPConnectionParameters.BindType.SIMPLE, encryption=LDAPConnectionParameters.Encryption.STARTTLS)
|
||||
public void loginLDAPUserAuthenticationSimpleEncryptionStartTLS() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with simple authentication & startTLS encryption enabled
|
||||
// Test variant: Bind credential set to vault
|
||||
// KEYCLOAK-14358 - Disable the StartTLS LDAP tests till KEYCLOAK-14343 & KEYCLOAK-14354 are corrected
|
||||
// since they don't work properly with auth server Wildfly due these bugs
|
||||
@Ignore
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindCredential=LDAPConnectionParameters.BindCredential.VAULT, bindType=LDAPConnectionParameters.BindType.SIMPLE, encryption=LDAPConnectionParameters.Encryption.STARTTLS)
|
||||
public void loginLDAPUserCredentialVaultAuthenticationSimpleEncryptionStartTLS() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with anonymous authentication & encryption (both SSL and startTLS) disabled
|
||||
// Test variant: Bind credential set to secret (default)
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindType=LDAPConnectionParameters.BindType.NONE, encryption=LDAPConnectionParameters.Encryption.NONE)
|
||||
public void loginLDAPUserAuthenticationNoneEncryptionNone() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with anonymous authentication & encryption (both SSL and startTLS) disabled
|
||||
// Test variant: Bind credential set to vault
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindCredential=LDAPConnectionParameters.BindCredential.VAULT, bindType=LDAPConnectionParameters.BindType.NONE, encryption=LDAPConnectionParameters.Encryption.NONE)
|
||||
public void loginLDAPUserCredentialVaultAuthenticationNoneEncryptionNone() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with anonymous authentication & SSL encryption enabled
|
||||
// Test variant: Bind credential set to secret (default)
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindType=LDAPConnectionParameters.BindType.NONE, encryption=LDAPConnectionParameters.Encryption.SSL)
|
||||
public void loginLDAPUserAuthenticationNoneEncryptionSSL() {
|
||||
verifyConnectionUrlProtocolPrefix("ldaps://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with anonymous authentication & SSL encryption enabled
|
||||
// Test variant: Bind credential set to vault
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindCredential=LDAPConnectionParameters.BindCredential.VAULT, bindType=LDAPConnectionParameters.BindType.NONE, encryption=LDAPConnectionParameters.Encryption.SSL)
|
||||
public void loginLDAPUserCredentialVaultAuthenticationNoneEncryptionSSL() {
|
||||
verifyConnectionUrlProtocolPrefix("ldaps://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with anonymous authentication & startTLS encryption enabled
|
||||
// Test variant: Bind credential set to secret (default)
|
||||
// KEYCLOAK-14358 - Disable the StartTLS LDAP tests till KEYCLOAK-14343 & KEYCLOAK-14354 are corrected
|
||||
// since they don't work properly with auth server Wildfly due these bugs
|
||||
@Ignore
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindType=LDAPConnectionParameters.BindType.NONE, encryption=LDAPConnectionParameters.Encryption.STARTTLS)
|
||||
public void loginLDAPUserAuthenticationNoneEncryptionStartTLS() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
|
||||
// Check LDAP federated user (in)valid login(s) with anonymous authentication & startTLS encryption enabled
|
||||
// Test variant: Bind credential set to vault
|
||||
// KEYCLOAK-14358 - Disable the StartTLS LDAP tests till KEYCLOAK-14343 & KEYCLOAK-14354 are corrected
|
||||
// since they don't work properly with auth server Wildfly due these bugs
|
||||
@Ignore
|
||||
@Test
|
||||
@LDAPConnectionParameters(bindCredential=LDAPConnectionParameters.BindCredential.VAULT, bindType=LDAPConnectionParameters.BindType.NONE, encryption=LDAPConnectionParameters.Encryption.STARTTLS)
|
||||
public void loginLDAPUserCredentialVaultAuthenticationNoneEncryptionStartTLS() {
|
||||
verifyConnectionUrlProtocolPrefix("ldap://");
|
||||
runLDAPLoginTest();
|
||||
}
|
||||
}
|
|
@ -33,6 +33,8 @@ import org.apache.directory.server.core.factory.AvlPartitionFactory;
|
|||
import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
|
||||
import org.apache.directory.server.core.factory.JdbmPartitionFactory;
|
||||
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
|
||||
import org.apache.directory.server.ldap.ExtendedOperationHandler;
|
||||
import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
|
||||
import org.apache.directory.server.ldap.LdapServer;
|
||||
import org.apache.directory.server.ldap.handlers.extended.PwdModifyHandler;
|
||||
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
|
||||
|
@ -63,13 +65,17 @@ public class LDAPEmbeddedServer {
|
|||
public static final String PROPERTY_LDIF_FILE = "ldap.ldif";
|
||||
public static final String PROPERTY_SASL_PRINCIPAL = "ldap.saslPrincipal";
|
||||
public static final String PROPERTY_DSF = "ldap.dsf";
|
||||
public static final String PROPERTY_ENABLE_ACCESS_CONTROL = "enableAccessControl";
|
||||
public static final String PROPERTY_ENABLE_ANONYMOUS_ACCESS = "enableAnonymousAccess";
|
||||
public static final String PROPERTY_ENABLE_SSL = "enableSSL";
|
||||
public static final String PROPERTY_ENABLE_STARTTLS = "enableStartTLS";
|
||||
public static final String PROPERTY_SET_CONFIDENTIALITY_REQUIRED = "setConfidentialityRequired";
|
||||
|
||||
private static final String DEFAULT_BASE_DN = "dc=keycloak,dc=org";
|
||||
private static final String DEFAULT_BIND_HOST = "localhost";
|
||||
private static final String DEFAULT_BIND_PORT = "10389";
|
||||
private static final String DEFAULT_BIND_LDAPS_PORT = "10636";
|
||||
private static final String DEFAULT_LDIF_FILE = "classpath:ldap/default-users.ldif";
|
||||
private static final String PROPERTY_ENABLE_SSL = "enableSSL";
|
||||
private static final String PROPERTY_KEYSTORE_FILE = "keystoreFile";
|
||||
private static final String PROPERTY_CERTIFICATE_PASSWORD = "certificatePassword";
|
||||
|
||||
|
@ -86,13 +92,24 @@ public class LDAPEmbeddedServer {
|
|||
protected String ldifFile;
|
||||
protected String ldapSaslPrincipal;
|
||||
protected String directoryServiceFactory;
|
||||
protected boolean enableAccessControl = false;
|
||||
protected boolean enableAnonymousAccess = false;
|
||||
protected boolean enableSSL = false;
|
||||
protected boolean enableStartTLS = false;
|
||||
protected boolean setConfidentialityRequired = false;
|
||||
protected String keystoreFile;
|
||||
protected String certPassword;
|
||||
|
||||
protected DirectoryService directoryService;
|
||||
protected LdapServer ldapServer;
|
||||
|
||||
public int getBindPort() {
|
||||
return bindPort;
|
||||
}
|
||||
|
||||
public int getBindLdapsPort() {
|
||||
return bindLdapsPort;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Properties defaultProperties = new Properties();
|
||||
|
@ -132,7 +149,11 @@ public class LDAPEmbeddedServer {
|
|||
this.ldifFile = readProperty(PROPERTY_LDIF_FILE, DEFAULT_LDIF_FILE);
|
||||
this.ldapSaslPrincipal = readProperty(PROPERTY_SASL_PRINCIPAL, null);
|
||||
this.directoryServiceFactory = readProperty(PROPERTY_DSF, DEFAULT_DSF);
|
||||
this.enableAccessControl = Boolean.valueOf(readProperty(PROPERTY_ENABLE_ACCESS_CONTROL, "false"));
|
||||
this.enableAnonymousAccess = Boolean.valueOf(readProperty(PROPERTY_ENABLE_ANONYMOUS_ACCESS, "false"));
|
||||
this.enableSSL = Boolean.valueOf(readProperty(PROPERTY_ENABLE_SSL, "false"));
|
||||
this.enableStartTLS = Boolean.valueOf(readProperty(PROPERTY_ENABLE_STARTTLS, "false"));
|
||||
this.setConfidentialityRequired = Boolean.valueOf(readProperty(PROPERTY_SET_CONFIDENTIALITY_REQUIRED, "false"));
|
||||
this.keystoreFile = readProperty(PROPERTY_KEYSTORE_FILE, null);
|
||||
this.certPassword = readProperty(PROPERTY_CERTIFICATE_PASSWORD, null);
|
||||
}
|
||||
|
@ -161,15 +182,22 @@ public class LDAPEmbeddedServer {
|
|||
log.info("Importing LDIF: " + ldifFile);
|
||||
importLdif();
|
||||
|
||||
log.info("Creating LDAP Server");
|
||||
log.info("Creating LDAP server..");
|
||||
this.ldapServer = createLdapServer();
|
||||
}
|
||||
|
||||
|
||||
public void start() throws Exception {
|
||||
log.info("Starting LDAP Server");
|
||||
log.info("Starting LDAP server..");
|
||||
ldapServer.start();
|
||||
log.info("LDAP Server started");
|
||||
// Verify the server started properly
|
||||
if (ldapServer.isStarted() && ldapServer.getDirectoryService().isStarted()) {
|
||||
log.info("LDAP server started.");
|
||||
} else if(!ldapServer.isStarted()) {
|
||||
throw new RuntimeException("Failed to start the LDAP server!");
|
||||
} else if (!ldapServer.getDirectoryService().isStarted()) {
|
||||
throw new RuntimeException("Failed to start the directory service for the LDAP server!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -188,8 +216,8 @@ public class LDAPEmbeddedServer {
|
|||
|
||||
DefaultDirectoryServiceFactory dsf = new DefaultDirectoryServiceFactory();
|
||||
DirectoryService service = dsf.getDirectoryService();
|
||||
service.setAccessControlEnabled(false);
|
||||
service.setAllowAnonymousAccess(false);
|
||||
service.setAccessControlEnabled(enableAccessControl);
|
||||
service.setAllowAnonymousAccess(enableAnonymousAccess);
|
||||
service.getChangeLog().setEnabled(false);
|
||||
|
||||
dsf.init(dcName + "DS");
|
||||
|
@ -242,16 +270,44 @@ public class LDAPEmbeddedServer {
|
|||
|
||||
ldapServer.setServiceName("DefaultLdapServer");
|
||||
ldapServer.setSearchBaseDn(this.baseDN);
|
||||
// Tolerate plaintext LDAP connections from clients by default
|
||||
ldapServer.setConfidentialityRequired(this.setConfidentialityRequired);
|
||||
|
||||
// Read the transports
|
||||
Transport ldap = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
|
||||
ldapServer.addTransports( ldap );
|
||||
if (enableSSL || enableStartTLS) {
|
||||
ldapServer.setKeystoreFile(keystoreFile);
|
||||
ldapServer.setCertificatePassword(certPassword);
|
||||
if (enableSSL) {
|
||||
Transport ldaps = new TcpTransport(this.bindHost, this.bindLdapsPort, 3, 50);
|
||||
ldaps.setEnableSSL(true);
|
||||
ldapServer.setKeystoreFile(keystoreFile);
|
||||
ldapServer.setCertificatePassword(certPassword);
|
||||
ldapServer.addTransports( ldaps );
|
||||
if (ldaps.isSSLEnabled()) {
|
||||
log.info("Enabled SSL support on the LDAP server.");
|
||||
}
|
||||
}
|
||||
if (enableStartTLS) {
|
||||
try {
|
||||
ldapServer.addExtendedOperationHandler(new StartTlsHandler());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot add the StartTLS extension handler: ", e);
|
||||
}
|
||||
for (ExtendedOperationHandler eoh : ldapServer.getExtendedOperationHandlers()) {
|
||||
if (eoh.getOid().equals(StartTlsHandler.EXTENSION_OID)) {
|
||||
log.info("Enabled StartTLS support on the LDAP server.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Require the LDAP server to accept only encrypted connections if confidentiality requested
|
||||
if (setConfidentialityRequired) {
|
||||
ldapServer.setConfidentialityRequired(true);
|
||||
if (ldapServer.isConfidentialityRequired()) {
|
||||
log.info("Configured the LDAP server to accepts only requests with a secured connection.");
|
||||
}
|
||||
}
|
||||
|
||||
// Associate the DS to this LdapServer
|
||||
|
@ -264,8 +320,28 @@ public class LDAPEmbeddedServer {
|
|||
throw new IllegalStateException("It wasn't possible to add PwdModifyHandler");
|
||||
}
|
||||
|
||||
// Propagate the anonymous flag to the DS
|
||||
directoryService.setAllowAnonymousAccess(false);
|
||||
if (enableAccessControl) {
|
||||
if (enableAnonymousAccess) {
|
||||
throw new IllegalStateException("Illegal to enable both the access control subsystem and the anonymous access at the same time! See: http://directory.apache.org/apacheds/gen-docs/latest/apidocs/src-html/org/apache/directory/server/core/DefaultDirectoryService.html#line.399 for details.");
|
||||
} else {
|
||||
directoryService.setAccessControlEnabled(true);
|
||||
if (directoryService.isAccessControlEnabled()) {
|
||||
log.info("Enabled basic access control checks on the LDAP server.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (enableAnonymousAccess) {
|
||||
directoryService.setAllowAnonymousAccess(true);
|
||||
// Since per ApacheDS JavaDoc: http://directory.apache.org/apacheds/gen-docs/latest/apidocs/src-html/org/apache/directory/server/core/DefaultDirectoryService.html#line.399
|
||||
// "if the access control subsystem is enabled then access to some entries may not
|
||||
// be allowed even when full anonymous access is enabled", disable the access control
|
||||
// subsystem together with enabling anonymous access to prevent this
|
||||
directoryService.setAccessControlEnabled(false);
|
||||
if (directoryService.isAllowAnonymousAccess() && !directoryService.isAccessControlEnabled()) {
|
||||
log.info("Enabled anonymous access on the LDAP server.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ldapServer;
|
||||
}
|
||||
|
@ -299,7 +375,7 @@ public class LDAPEmbeddedServer {
|
|||
try {
|
||||
directoryService.getAdminSession().add(new DefaultEntry(directoryService.getSchemaManager(), ldifEntry.getEntry()));
|
||||
} catch (LdapEntryAlreadyExistsException ignore) {
|
||||
log.info("Entry " + ldifEntry.getDn() + " already exists. Ignoring");
|
||||
log.info("Entry " + ldifEntry.getDn() + " already exists. Ignoring.");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
|
Loading…
Reference in a new issue