groups2 = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn());
+
+ assertThat(groups, containsInAnyOrder(containsString("upchuckers"), containsString("localDistribution")));
+ assertThat(groups2, containsInAnyOrder(containsString("upchuckers"), containsString("localDistribution")));
+ }
+
+
+ public static Settings buildAdSettings(String ldapUrl, String adDomainName) {
+ return ImmutableSettings.builder()
+ .putArray(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
+ .put(SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
+ .build();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/ApacheDsEmbedded.java b/src/test/java/org/elasticsearch/shield/authc/ldap/ApacheDsEmbedded.java
new file mode 100644
index 00000000000..3fa8da1f83f
--- /dev/null
+++ b/src/test/java/org/elasticsearch/shield/authc/ldap/ApacheDsEmbedded.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.shield.authc.ldap;
+
+import com.carrotsearch.randomizedtesting.SysGlobals;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
+import org.apache.directory.api.ldap.schemaextractor.SchemaLdifExtractor;
+import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor;
+import org.apache.directory.api.ldap.schemaloader.LdifSchemaLoader;
+import org.apache.directory.api.ldap.schemamanager.impl.DefaultSchemaManager;
+import org.apache.directory.api.util.exception.Exceptions;
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.DefaultDirectoryService;
+import org.apache.directory.server.core.api.CacheService;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.DnFactory;
+import org.apache.directory.server.core.api.InstanceLayout;
+import org.apache.directory.server.core.api.partition.Partition;
+import org.apache.directory.server.core.api.schema.SchemaPartition;
+import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
+import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
+import org.apache.directory.server.core.partition.ldif.LdifPartition;
+import org.apache.directory.server.i18n.I18n;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
+import org.apache.directory.server.protocol.shared.transport.TcpTransport;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper Class to start up an Apache DS LDAP server for testing. Here is a typical use example in tests:
+ *
+ * static ApacheDsEmbedded ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif");
+ *
+ * @BeforeClass public static void startServer() throws Exception {
+ * ldap.startServer();
+ * }
+ * @AfterClass public static void stopServer() throws Exception {
+ * ldap.stopAndCleanup();
+ * }
+ *
+ */
+public class ApacheDsEmbedded {
+ /**
+ * The child JVM ordinal of this JVM. Placed by the testing framework. Default is 0 Sequential number starting with 0
+ */
+ public static final int CHILD_JVM_ID = Integer.parseInt(System.getProperty(SysGlobals.CHILDVM_SYSPROP_JVM_ID, "0"));
+
+ private final File workDir;
+ private final String baseDN;
+ private final String ldifFileName;
+ private final int port;
+ /**
+ * The directory service
+ */
+ private DirectoryService service;
+
+ /**
+ * The LDAP server
+ */
+ private LdapServer server;
+
+
+ /**
+ * Creates a new instance of EmbeddedADS. It initializes the directory service.
+ *
+ * @throws Exception If something went wrong
+ */
+ public ApacheDsEmbedded(String baseDN, String ldifFileName, String testName) {
+ this.workDir = new File(System.getProperty("java.io.tmpdir") + "/server-work/" + testName);
+ this.baseDN = baseDN;
+ this.ldifFileName = ldifFileName;
+ this.port = 10389 + CHILD_JVM_ID;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+
+ public String getUrl() {
+ return "ldap://localhost:" + port;
+ }
+
+ /**
+ * starts the LdapServer
+ *
+ * @throws Exception
+ */
+ public void startServer() throws Exception {
+ initDirectoryService(workDir, baseDN, ldifFileName);
+ loadSchema(ldifFileName);
+
+ server = new LdapServer();
+ server.setTransports(new TcpTransport(port));
+ server.setDirectoryService(service);
+
+ server.start();
+ }
+
+ /**
+ * This will cleanup the junk left on the file system and shutdown the server
+ *
+ * @throws Exception
+ */
+ public void stopAndCleanup() throws Exception {
+ if (server != null) server.stop();
+ if (service != null) service.shutdown();
+ workDir.delete();
+ }
+
+
+ /**
+ * Add a new partition to the server
+ *
+ * @param partitionId The partition Id
+ * @param partitionDn The partition DN
+ * @param dnFactory the DN factory
+ * @return The newly added partition
+ * @throws Exception If the partition can't be added
+ */
+ private Partition addPartition(String partitionId, String partitionDn, DnFactory dnFactory) throws Exception {
+ // Create a new partition with the given partition id
+ JdbmPartition partition = new JdbmPartition(service.getSchemaManager(), dnFactory);
+ partition.setId(partitionId);
+ partition.setPartitionPath(new File(service.getInstanceLayout().getPartitionsDirectory(), partitionId).toURI());
+ partition.setSuffixDn(new Dn(partitionDn));
+ service.addPartition(partition);
+
+ return partition;
+ }
+
+
+ /**
+ * Add a new set of index on the given attributes
+ *
+ * @param partition The partition on which we want to add index
+ * @param attrs The list of attributes to index
+ */
+ private void addIndex(Partition partition, String... attrs) {
+ // Index some attributes on the apache partition
+ Set indexedAttributes = new HashSet();
+
+ for (String attribute : attrs) {
+ indexedAttributes.add(new JdbmIndex(attribute, false));
+ }
+
+ ((JdbmPartition) partition).setIndexedAttributes(indexedAttributes);
+ }
+
+
+ /**
+ * initialize the schema manager and add the schema partition to diectory service
+ *
+ * @throws Exception if the schema LDIF files are not found on the classpath
+ */
+ private void initSchemaPartition() throws Exception {
+ InstanceLayout instanceLayout = service.getInstanceLayout();
+
+ File schemaPartitionDirectory = new File(instanceLayout.getPartitionsDirectory(), "schema");
+
+ // Extract the schema on disk (a brand new one) and load the registries
+ if (schemaPartitionDirectory.exists()) {
+ System.out.println("schema partition already exists, skipping schema extraction");
+ } else {
+ SchemaLdifExtractor extractor = new DefaultSchemaLdifExtractor(instanceLayout.getPartitionsDirectory());
+ extractor.extractOrCopy();
+ }
+
+ SchemaLoader loader = new LdifSchemaLoader(schemaPartitionDirectory);
+ SchemaManager schemaManager = new DefaultSchemaManager(loader);
+
+ // We have to load the schema now, otherwise we won't be able
+ // to initialize the Partitions, as we won't be able to parse
+ // and normalize their suffix Dn
+ schemaManager.loadAllEnabled();
+
+ List errors = schemaManager.getErrors();
+
+ if (errors.size() != 0) {
+ throw new Exception(I18n.err(I18n.ERR_317, Exceptions.printErrors(errors)));
+ }
+
+ service.setSchemaManager(schemaManager);
+
+ // Init the LdifPartition with schema
+ LdifPartition schemaLdifPartition = new LdifPartition(schemaManager, service.getDnFactory());
+ schemaLdifPartition.setPartitionPath(schemaPartitionDirectory.toURI());
+
+ // The schema partition
+ SchemaPartition schemaPartition = new SchemaPartition(schemaManager);
+ schemaPartition.setWrappedPartition(schemaLdifPartition);
+ service.setSchemaPartition(schemaPartition);
+ }
+
+ /**
+ * Initialize the server. It creates the partition, adds the index, and
+ * injects the context entries for the created partitions.
+ *
+ * @param workDir the directory to be used for storing the data
+ * @param baseDn
+ * @param ldifFileName @throws Exception if there were some problems while initializing the system
+ */
+ private void initDirectoryService(File workDir, String baseDn, String ldifFileName) throws Exception {
+ // Initialize the LDAP service
+ service = new DefaultDirectoryService();
+ service.setInstanceLayout(new InstanceLayout(workDir));
+
+ CacheService cacheService = new CacheService();
+ cacheService.initialize(service.getInstanceLayout());
+
+ service.setCacheService(cacheService);
+
+ // first load the schema
+ initSchemaPartition();
+
+ // then the system partition
+ // this is a MANDATORY partition
+ // DO NOT add this via addPartition() method, trunk code complains about duplicate partition
+ // while initializing
+ JdbmPartition systemPartition = new JdbmPartition(service.getSchemaManager(), service.getDnFactory());
+ systemPartition.setId("system");
+ systemPartition.setPartitionPath(new File(service.getInstanceLayout().getPartitionsDirectory(), systemPartition.getId()).toURI());
+ systemPartition.setSuffixDn(new Dn(ServerDNConstants.SYSTEM_DN));
+ systemPartition.setSchemaManager(service.getSchemaManager());
+
+ // mandatory to call this method to set the system partition
+ // Note: this system partition might be removed from trunk
+ service.setSystemPartition(systemPartition);
+
+ // Disable the ChangeLog system
+ service.getChangeLog().setEnabled(false);
+ service.setDenormalizeOpAttrsEnabled(true);
+
+ Partition apachePartition = addPartition("ldapTest", baseDn, service.getDnFactory());
+
+
+ // Index some attributes on the apache partition
+ addIndex(apachePartition, "objectClass", "ou", "uid");
+
+ // And start the service
+ service.startup();
+
+
+ // Inject the context entry for dc=Apache,dc=Org partition
+ if (!service.getAdminSession().exists(apachePartition.getSuffixDn())) {
+ Dn dnApache = new Dn(baseDn);
+ Entry entryApache = service.newEntry(dnApache);
+ entryApache.add("objectClass", "top", "domain", "extensibleObject");
+ entryApache.add("dc", "Apache");
+ service.getAdminSession().add(entryApache);
+ }
+
+ // We are all done !
+ }
+
+ private void loadSchema(String ldifFileName) throws URISyntaxException {
+ // Load the directory as a resource
+ URL dir_url = this.getClass().getResource(ldifFileName);
+ if (dir_url == null) throw new NullPointerException("the LDIF file doesn't exist: " + ldifFileName);
+ File ldifFile = new File(dir_url.toURI());
+ LdifFileLoader ldifLoader = new LdifFileLoader(service.getAdminSession(), ldifFile, Collections.EMPTY_LIST);
+ ldifLoader.execute();
+ }
+
+ /**
+ * Main class.
+ *
+ * @param args Not used.
+ */
+ public static void main(String[] args) {
+ try {
+ String baseDir = "o=sevenSeas";
+ String ldifImport = "seven-seas.ldif";
+ // Create the server
+ ApacheDsEmbedded ads = new ApacheDsEmbedded(baseDir, ldifImport, "test");
+ ads.startServer();
+ // Read an entry
+ Entry result = ads.service.getAdminSession().lookup(new Dn(baseDir));
+
+ // And print it if available
+ System.out.println("Found entry : " + result);
+
+ // optionally we can start a server too
+ ads.stopAndCleanup();
+ } catch (Exception e) {
+ // Ok, we have something wrong going on ...
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java
new file mode 100644
index 00000000000..1a52d2da23e
--- /dev/null
+++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapConnectionTests.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.shield.authc.ldap;
+
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+
+public class LdapConnectionTests extends ElasticsearchTestCase {
+ public static String SETTINGS_PREFIX = LdapRealm.class.getPackage().getName().substring("com.elasticsearch.".length()) + '.';
+
+ static ApacheDsEmbedded ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif", LdapConnectionTests.class.getName());
+
+ @BeforeClass
+ public static void startServer() throws Exception {
+ ldap.startServer();
+ }
+ @AfterClass
+ public static void stopServer() throws Exception {
+ ldap.stopAndCleanup();
+ }
+
+ @Test
+ public void testBindWithTemplates() {
+ String[] ldapUrls = new String[]{ldap.getUrl()};
+ String groupSearchBase = "o=sevenSeas";
+ boolean isSubTreeSearch = true;
+ String[] userTemplates = new String[]{
+ "cn={0},ou=something,ou=obviously,ou=incorrect,o=sevenSeas",
+ "wrongname={0},ou=people,o=sevenSeas",
+ "cn={0},ou=people,o=sevenSeas", //this last one should work
+ };
+ StandardLdapConnectionFactory connectionFactory = new StandardLdapConnectionFactory(
+ buildLdapSettings(ldapUrls, userTemplates, groupSearchBase, isSubTreeSearch));
+
+ String user = "Horatio Hornblower";
+ char[] userPass = "pass".toCharArray();
+
+ LdapConnection ldap = connectionFactory.bind(user, userPass);
+ Map attrs = ldap.getUserAttrs(ldap.getAuthenticatedUserDn());
+
+ assertThat( attrs, hasKey("uid"));
+ assertThat( attrs.get("uid"), arrayContaining("hhornblo"));
+ }
+ @Test
+ public void testBindWithBogusTemplates() {
+ String[] ldapUrl = new String[]{ldap.getUrl()};
+ String groupSearchBase = "o=sevenSeas";
+ boolean isSubTreeSearch = true;
+ String[] userTemplates = new String[]{
+ "cn={0},ou=something,ou=obviously,ou=incorrect,o=sevenSeas",
+ "wrongname={0},ou=people,o=sevenSeas",
+ "asdf={0},ou=people,o=sevenSeas", //none of these should work
+ };
+ StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
+ buildLdapSettings(ldapUrl, userTemplates, groupSearchBase, isSubTreeSearch));
+
+ String user = "Horatio Hornblower";
+ char[] userPass = "pass".toCharArray();
+
+ try {
+ LdapConnection ldap = ldapFac.bind(user, userPass);
+ fail("bindWithUserTemplates should have failed");
+ } catch (LdapException le) {
+
+ }
+
+ }
+
+ @Test
+ public void testGroupLookup_Subtree() {
+ String groupSearchBase = "o=sevenSeas";
+ String userTemplate = "cn={0},ou=people,o=sevenSeas";
+
+ boolean isSubTreeSearch = true;
+ StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
+ buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
+
+ String user = "Horatio Hornblower";
+ char[] userPass = "pass".toCharArray();
+
+ LdapConnection ldap = ldapFac.bind(user, userPass);
+ List groups = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn());
+ System.out.println("groups:"+groups);
+ assertThat(groups, contains("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"));
+ }
+
+ @Test
+ public void testGroupLookup_OneLevel() {
+ String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
+ String userTemplate = "cn={0},ou=people,o=sevenSeas";
+ boolean isSubTreeSearch = false;
+ StandardLdapConnectionFactory ldapFac = new StandardLdapConnectionFactory(
+ buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
+
+ String user = "Horatio Hornblower";
+ LdapConnection ldap = ldapFac.bind(user, "pass".toCharArray());
+
+ List groups = ldap.getGroupsFromSearch(ldap.getAuthenticatedUserDn());
+ System.out.println("groups:"+groups);
+ assertThat(groups, contains("cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas"));
+ }
+
+ public static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
+ return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
+ }
+
+ public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
+ return ImmutableSettings.builder()
+ .putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.URLS_SETTING, ldapUrl)
+ .putArray(SETTINGS_PREFIX + StandardLdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
+ .put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
+ .put(SETTINGS_PREFIX + StandardLdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
+ }
+
+}
diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapperTest.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapperTest.java
new file mode 100644
index 00000000000..7546f3c408d
--- /dev/null
+++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapGroupToRoleMapperTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.shield.authc.ldap;
+
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.env.Environment;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.watcher.ResourceWatcherService;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.hasItems;
+
+public class LdapGroupToRoleMapperTest extends ElasticsearchTestCase {
+
+ private final String tonyStarkDN = "cn=tstark,ou=marvel,o=superheros";
+ private final String[] starkGroupDns = new String[] {
+ //groups can be named by different attributes, depending on the directory,
+ //we don't care what it is named by
+ "cn=shield,ou=marvel,o=superheros",
+ "cn=avengers,ou=marvel,o=superheros",
+ "group=genius, dc=mit, dc=edu",
+ "groupName = billionaire , ou = acme",
+ "gid = playboy , dc = example , dc = com",
+ "groupid=philanthropist,ou=groups,dc=unitedway,dc=org"
+ };
+ private final String roleShield = "shield";
+ private final String roleAvenger = "avenger";
+
+
+ @Test
+ public void testYaml() throws IOException {
+ File file = this.getResource("role_mapping.yml");
+ Settings settings = ImmutableSettings.settingsBuilder()
+ .put("shield.authc.ldap." + LdapGroupToRoleMapper.ROLE_MAPPING_FILE_SETTING, file.getCanonicalPath())
+ .build();
+
+ LdapGroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
+ new Environment(settings),
+ new ResourceWatcherService(settings, new ThreadPool("test")));
+
+ Set roles = mapper.mapRoles( Arrays.asList(starkGroupDns) );
+
+ //verify
+ assertThat(roles, hasItems(roleShield, roleAvenger));
+ }
+
+ @Test
+ public void testRelativeDN() {
+ Settings settings = ImmutableSettings.builder()
+ .put("shield.authc.ldap." + LdapGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
+ .build();
+
+ LdapGroupToRoleMapper mapper = new LdapGroupToRoleMapper(settings,
+ new Environment(settings),
+ new ResourceWatcherService(settings, new ThreadPool("test")));
+
+ Set roles = mapper.mapRoles(Arrays.asList(starkGroupDns));
+ assertThat(roles, hasItems("genius", "billionaire", "playboy", "philanthropist", "shield", "avengers"));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java
new file mode 100644
index 00000000000..3e1a9ff3f7d
--- /dev/null
+++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapRealmTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.shield.authc.ldap;
+
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.env.Environment;
+import org.elasticsearch.shield.User;
+import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.watcher.ResourceWatcherService;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.*;
+
+public class LdapRealmTest extends ElasticsearchTestCase {
+ static ApacheDsEmbedded ldap = new ApacheDsEmbedded("o=sevenSeas", "seven-seas.ldif", LdapRealmTest.class.getName());
+ public static String AD_IP = "54.213.145.20";
+ public static String AD_URL = "ldap://" + AD_IP + ":389";
+
+ public static final String VALID_USER_TEMPLATE = "cn={0},ou=people,o=sevenSeas";
+ public static final String VALID_USERNAME = "Thomas Masterman Hardy";
+ public static final String PASSWORD = "pass";
+
+ @BeforeClass
+ public static void startServer() throws Exception {
+ ldap.startServer();
+ }
+ @AfterClass
+ public static void stopServer() throws Exception {
+ ldap.stopAndCleanup();
+ }
+
+ @Test
+ public void testAuthenticate_subTreeGroupSearch(){
+ String groupSearchBase = "o=sevenSeas";
+ boolean isSubTreeSearch = true;
+ String userTemplate = VALID_USER_TEMPLATE;
+ Settings settings = LdapConnectionTests.buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch);
+ StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(settings);
+ LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
+
+ User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
+ assertThat( user, notNullValue());
+ assertThat(user.roles(), arrayContaining("HMS Victory"));
+ }
+
+ @Test
+ public void testAuthenticate_oneLevelGroupSearch(){
+ String groupSearchBase = "ou=crews,ou=groups,o=sevenSeas";
+ boolean isSubTreeSearch = false;
+ String userTemplate = VALID_USER_TEMPLATE;
+ StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
+ LdapConnectionTests.buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch));
+
+ LdapRealm ldap = new LdapRealm(buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
+
+ User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
+ assertThat( user, notNullValue());
+ assertThat( user.roles(), arrayContaining("HMS Victory"));
+ }
+
+ @Ignore //this is still failing. not sure why.
+ @Test
+ public void testAuthenticate_caching(){
+ String groupSearchBase = "o=sevenSeas";
+ boolean isSubTreeSearch = true;
+ String userTemplate = VALID_USER_TEMPLATE;
+ StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
+ LdapConnectionTests.buildLdapSettings( ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
+
+ ldapFactory = spy(ldapFactory);
+ LdapRealm ldap = new LdapRealm( buildCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
+ User user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
+ user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
+
+ //verify one and only one bind -> caching is working
+ verify(ldapFactory, times(1)).bind(anyString(), any(char[].class));
+ }
+
+ @Test
+ public void testAuthenticate_noncaching(){
+ String groupSearchBase = "o=sevenSeas";
+ boolean isSubTreeSearch = true;
+ String userTemplate = VALID_USER_TEMPLATE;
+ StandardLdapConnectionFactory ldapFactory = new StandardLdapConnectionFactory(
+ LdapConnectionTests.buildLdapSettings(ldap.getUrl(), userTemplate, groupSearchBase, isSubTreeSearch) );
+
+ ldapFactory = spy(ldapFactory);
+ LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
+ User user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
+ user = ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, PASSWORD.toCharArray()));
+
+ //verify two and only two binds -> caching is disabled
+ verify(ldapFactory, times(2)).bind(anyString(), any(char[].class));
+ }
+
+ @Ignore
+ @Test
+ public void testAD() {
+ String adDomain = "ad.test.elasticsearch.com";
+ String userSearchBaseDN = "dc=ad,dc=es,dc=com";
+
+ ActiveDirectoryConnectionFactory ldapFactory = new ActiveDirectoryConnectionFactory(
+ ActiveDirectoryFactoryTests.buildAdSettings(AD_URL, adDomain));
+
+ LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
+
+ User user = ldap.authenticate( new UsernamePasswordToken("george", "R))Tr0x".toCharArray()));
+
+ assertThat( user, notNullValue());
+ assertThat( user.roles(), hasItemInArray("upchuckers"));
+ }
+
+ @Ignore
+ @Test
+ public void testAD_defaults() {
+ //only set the adDomain, and see if it infers the rest correctly
+ String adDomain = AD_IP;
+ Settings settings = ImmutableSettings.builder()
+ .put(LdapConnectionTests.SETTINGS_PREFIX + ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomain)
+ .build();
+
+ ActiveDirectoryConnectionFactory ldapFactory = new ActiveDirectoryConnectionFactory( settings );
+ LdapRealm ldap = new LdapRealm( buildNonCachingSettings(), ldapFactory, buildGroupAsRoleMapper());
+ User user = ldap.authenticate( new UsernamePasswordToken("george", "R))Tr0x".toCharArray()));
+
+ assertThat( user, notNullValue());
+ assertThat( user.roles(), hasItemInArray("upchuckers"));
+ }
+
+
+
+ private Settings buildNonCachingSettings() {
+ return ImmutableSettings.builder()
+ .put("shield.authc.ldap."+LdapRealm.CACHE_TTL, -1)
+ .build();
+ }
+
+ private Settings buildCachingSettings() {
+ return ImmutableSettings.builder()
+ .put("shield.authc.ldap."+LdapRealm.CACHE_TTL, 1)
+ .put("shield.authc.ldap."+LdapRealm.CACHE_MAX_USERS, 10)
+ .build();
+ }
+
+ private LdapGroupToRoleMapper buildGroupAsRoleMapper() {
+ Settings settings = ImmutableSettings.builder()
+ .put("shield.authc.ldap." + LdapGroupToRoleMapper.USE_UNMAPPED_GROUPS_AS_ROLES_SETTING, true)
+ .build();
+
+ return new LdapGroupToRoleMapper(settings,
+ new Environment(settings),
+ new ResourceWatcherService(settings, new ThreadPool("test")));
+
+ }
+}
diff --git a/src/test/resources/org/elasticsearch/shield/authc/ldap/ldapWithGroupSearch.yaml b/src/test/resources/org/elasticsearch/shield/authc/ldap/ldapWithGroupSearch.yaml
new file mode 100644
index 00000000000..db70f28232a
--- /dev/null
+++ b/src/test/resources/org/elasticsearch/shield/authc/ldap/ldapWithGroupSearch.yaml
@@ -0,0 +1,11 @@
+# This LDAP connection does group lookup by a subtree search, no role mapping
+ldap:
+ urls:
+ - ldap://ldap.example.com:1389
+ userDnTemplate: "cn={0},ou=people,o=superheros"
+ groupSearch:
+ isSubtreeSearch: "true"
+ baseDn: "ou=marvel,o=superheros"
+ # no dnToRoleMapping mapping implies that the group name will be used directly as the ES role name
+
+
diff --git a/src/test/resources/org/elasticsearch/shield/authc/ldap/ldapWithRoleMapping.yaml b/src/test/resources/org/elasticsearch/shield/authc/ldap/ldapWithRoleMapping.yaml
new file mode 100644
index 00000000000..d98f48e90bc
--- /dev/null
+++ b/src/test/resources/org/elasticsearch/shield/authc/ldap/ldapWithRoleMapping.yaml
@@ -0,0 +1,10 @@
+# This LDAP connection does group lookup by attribute with group to role mapping
+ldap:
+ urls: # these connections are not round-robin, but primary, secondary, etc. When the first fails the second is attempted
+ - ldap://ldap.example.com:1389
+ userDnTemplate: "cn={0},ou=people,o=superheros"
+
+ # dnToRoleMapping as true means that group will be mapped to
+ # local ES roles using
+ dnToRoleMapping: true
+ #roleMappingFile: /etc/es/role_mapping.yaml
diff --git a/src/test/resources/org/elasticsearch/shield/authc/ldap/role_mapping.yml b/src/test/resources/org/elasticsearch/shield/authc/ldap/role_mapping.yml
new file mode 100644
index 00000000000..67e8fc14d9c
--- /dev/null
+++ b/src/test/resources/org/elasticsearch/shield/authc/ldap/role_mapping.yml
@@ -0,0 +1,5 @@
+shield:
+ - "cn=avengers,ou=marvel,o=superheros"
+ - "cn=shield,ou=marvel,o=superheros"
+avenger:
+ - "cn=avengers,ou=marvel,o=superheros"
\ No newline at end of file
diff --git a/src/test/resources/org/elasticsearch/shield/authc/ldap/seven-seas.ldif b/src/test/resources/org/elasticsearch/shield/authc/ldap/seven-seas.ldif
new file mode 100644
index 00000000000..f9462276e22
--- /dev/null
+++ b/src/test/resources/org/elasticsearch/shield/authc/ldap/seven-seas.ldif
@@ -0,0 +1,219 @@
+# Sample LDIF data for the ApacheDS v1.0 Basic User's Guide
+#
+# Some sailors and their ships
+# userpassword for all persons is "pass"
+#
+version: 1
+
+dn: ou=people,o=sevenSeas
+objectclass: organizationalUnit
+objectclass: top
+description: Contains entries which describe persons (seamen)
+ou: people
+
+dn: ou=groups,o=sevenSeas
+objectclass: organizationalUnit
+objectclass: top
+description: Contains entries which describe groups (crews, for instance)
+ou: groups
+
+dn: ou=crews,ou=groups,o=sevenSeas
+objectclass: organizationalUnit
+objectclass: top
+description: Contains entries which describe ship crews
+ou: crews
+
+dn: ou=ranks,ou=groups,o=sevenSeas
+objectclass: organizationalUnit
+objectclass: top
+description: Contains entries which describe naval ranks (e.g. captain)
+ou: ranks
+
+# HMS Lydia Crew
+# --------------
+
+dn: cn=Horatio Hornblower,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Horatio Hornblower
+description: Capt. Horatio Hornblower, R.N
+givenname: Horatio
+sn: Hornblower
+uid: hhornblo
+mail: hhornblo@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=William Bush,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: William Bush
+description: Lt. William Bush
+givenname: William
+manager: cn=Horatio Hornblower,ou=people,o=sevenSeas
+sn: Bush
+uid: wbush
+mail: wbush@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=Thomas Quist,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Thomas Quist
+description: Seaman Quist
+givenname: Thomas
+manager: cn=Horatio Hornblower,ou=people,o=sevenSeas
+sn: Quist
+uid: tquist
+mail: tquist@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=Moultrie Crystal,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Moultrie Crystal
+description: Lt. Crystal
+givenname: Moultrie
+manager: cn=Horatio Hornblower,ou=people,o=sevenSeas
+sn: Crystal
+uid: mchrysta
+mail: mchrysta@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas
+objectclass: groupOfUniqueNames
+objectclass: top
+cn: HMS Lydia
+uniquemember: cn=Horatio Hornblower,ou=people,o=sevenSeas
+uniquemember: cn=William Bush,ou=people,o=sevenSeas
+uniquemember: cn=Thomas Quist,ou=people,o=sevenSeas
+uniquemember: cn=Moultrie Crystal,ou=people,o=sevenSeas
+
+# HMS Victory Crew
+# ----------------
+
+dn: cn=Horatio Nelson,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Horatio Nelson
+description: Lord Horatio Nelson
+givenname: Horatio
+sn: Nelson
+uid: hnelson
+mail: hnelson@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=Thomas Masterman Hardy,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Thomas Masterman Hardy
+description: Sir Thomas Masterman Hardy
+givenname: Thomas
+manager: cn=Horatio Nelson,ou=people,o=sevenSeas
+sn: Hardy
+uid: thardy
+mail: thardy@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=Cornelius Buckley,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Cornelius Buckley
+description: LM Cornelius Buckley
+givenname: Cornelius
+manager: cn=Horatio Nelson,ou=people,o=sevenSeas
+sn: Buckley
+uid: cbuckley
+mail: cbuckley@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=HMS Victory,ou=crews,ou=groups,o=sevenSeas
+objectclass: groupOfUniqueNames
+objectclass: top
+cn: HMS Victory
+uniquemember: cn=Horatio Nelson,ou=people,o=sevenSeas
+uniquemember: cn=Thomas Masterman Hardy,ou=people,o=sevenSeas
+uniquemember: cn=Cornelius Buckley,ou=people,o=sevenSeas
+
+# HMS Bounty Crew
+# ---------------
+
+dn: cn=William Bligh,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: William Bligh
+description: Captain William Bligh
+givenname: William
+sn: Bligh
+uid: wbligh
+mail: wbligh@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=Fletcher Christian,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: Fletcher Christian
+description: Lieutenant Fletcher Christian
+givenname: Fletcher
+manager: cn=William Bligh,ou=people,o=sevenSeas
+sn: Christian
+uid: fchristi
+mail: fchristi@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=John Fryer,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: John Fryer
+description: Master John Fryer
+givenname: John
+manager: cn=William Bligh,ou=people,o=sevenSeas
+sn: Fryer
+uid: jfryer
+mail: jfryer@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=John Hallett,ou=people,o=sevenSeas
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: top
+cn: John Hallett
+description: Midshipman John Hallett
+givenname: John
+manager: cn=William Bligh,ou=people,o=sevenSeas
+sn: Hallett
+uid: jhallett
+mail: jhallett@royalnavy.mod.uk
+userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
+
+dn: cn=HMS Bounty,ou=crews,ou=groups,o=sevenSeas
+objectclass: groupOfUniqueNames
+objectclass: top
+cn: HMS Bounty
+uniquemember: cn=William Bligh,ou=people,o=sevenSeas
+uniquemember: cn=Fletcher Christian,ou=people,o=sevenSeas
+uniquemember: cn=John Fryer,ou=people,o=sevenSeas
+uniquemember: cn=John Hallett,ou=people,o=sevenSeas
+
+
+
diff --git a/tests.policy b/tests.policy
index d206149a6b6..ad06580d8b8 100644
--- a/tests.policy
+++ b/tests.policy
@@ -31,4 +31,12 @@ grant {
// Needed for accept all ssl certs in tests
permission javax.net.ssl.SSLPermission "setHostnameVerifier";
+ // Needed to startup embedded apacheDS LDAP server for tests
+ permission java.security.SecurityPermission "putProviderProperty.BC";
+ permission java.security.SecurityPermission "insertProvider.BC";
+ permission java.security.SecurityPermission "getProperty.ssl.KeyManagerFactory.algorithm";
+
+ //this shouldn't be in a production environment, just to run tests:
+ permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
+ permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler";
};