/**
* This file is part of Graylog.
*
* Graylog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.security.ldap;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.annotations.LoadSchema;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.core.partition.impl.avl.AvlPartition;
import org.apache.directory.server.ldap.LdapServer;
import org.graylog2.shared.security.ldap.LdapEntry;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = {
@CreateTransport(protocol = "LDAP")
})
@CreateDS(
name = "LdapConnectorTest",
partitions = {
@CreatePartition(
name = "example.com",
type = AvlPartition.class,
suffix = "dc=example,dc=com",
contextEntry = @ContextEntry(
entryLdif = "dn: dc=example,dc=com\n" +
"dc: example\n" +
"objectClass: top\n" +
"objectClass: domain\n\n"
),
indexes = {
@CreateIndex(attribute = "objectClass"),
@CreateIndex(attribute = "dc"),
@CreateIndex(attribute = "ou")
}
)
},
loadedSchemas = {
@LoadSchema(name = "nis", enabled = true)
}
)
@ApplyLdifFiles("org/graylog2/security/ldap/base.ldif")
public class LdapConnectorTest extends AbstractLdapTestUnit {
private static final String ADMIN_DN = "uid=admin,ou=system";
private static final String ADMIN_PASSWORD = "secret";
@Rule
public final ExpectedException expectedException = ExpectedException.none();
private LdapConnector connector;
private LdapNetworkConnection connection;
@Before
public void setUp() throws Exception {
final LdapServer server = getLdapServer();
final LdapConnectionConfig config = new LdapConnectionConfig();
config.setLdapHost("localHost");
config.setLdapPort(server.getPort());
config.setName(ADMIN_DN);
config.setCredentials(ADMIN_PASSWORD);
connector = new LdapConnector(10000);
connection = connector.connect(config);
}
@After
public void tearDown() throws Exception {
connection.close();
}
@Test
public void testUserLookup() throws Exception {
final LdapEntry entry = connector.search(connection,
"ou=users,dc=example,dc=com",
"(&(objectClass=posixAccount)(uid={0}))",
"cn",
"john",
false,
"ou=groups,dc=example,dc=com",
"cn",
"(|(objectClass=groupOfNames)(objectClass=posixGroup))");
assertThat(entry).isNotNull();
assertThat(entry.getDn())
.isNotNull()
.isEqualTo("cn=John Doe,ou=users,dc=example,dc=com");
assertThat(entry.getGroups())
.hasSize(2)
.contains("QA", "Developers");
}
@Test
public void testGroupOfNamesLookup() throws Exception {
final LdapEntry entry = connector.search(connection,
"ou=users,dc=example,dc=com",
"(&(objectClass=posixAccount)(uid={0}))",
"cn",
"john",
false,
"ou=groups,dc=example,dc=com",
"cn",
"(objectClass=groupOfNames)");
assertThat(entry).isNotNull();
assertThat(entry.getDn())
.isNotNull()
.isEqualTo("cn=John Doe,ou=users,dc=example,dc=com");
assertThat(entry.getGroups()).hasSize(1).contains("QA");
}
@Test
public void testGroupOfUniqueNamesLookup() throws Exception {
final LdapEntry entry = connector.search(connection,
"ou=users,dc=example,dc=com",
"(&(objectClass=posixAccount)(uid={0}))",
"cn",
"john",
false,
"ou=groups,dc=example,dc=com",
"cn",
"(objectClass=groupOfUniqueNames)");
assertThat(entry).isNotNull();
assertThat(entry.getDn())
.isNotNull()
.isEqualTo("cn=John Doe,ou=users,dc=example,dc=com");
assertThat(entry.getGroups()).hasSize(2).contains("Engineers", "Whitespace Engineers");
}
@Test
public void testPosixGroupLookup() throws Exception {
final LdapEntry entry = connector.search(connection,
"ou=users,dc=example,dc=com",
"(&(objectClass=posixAccount)(uid={0}))",
"cn",
"john",
false,
"ou=groups,dc=example,dc=com",
"cn",
"(objectClass=posixGroup)");
assertThat(entry).isNotNull();
assertThat(entry.getDn())
.isNotNull()
.isEqualTo("cn=John Doe,ou=users,dc=example,dc=com");
assertThat(entry.getGroups()).hasSize(1).contains("Developers");
}
@Test
public void testAllGroupClassesLookup() throws Exception {
final LdapEntry entry = connector.search(connection,
"ou=users,dc=example,dc=com",
"(&(objectClass=posixAccount)(uid={0}))",
"cn",
"john",
false,
"ou=groups,dc=example,dc=com",
"cn",
"(|(objectClass=posixGroup)(objectClass=groupOfNames)(objectclass=groupOfUniqueNames))");
assertThat(entry).isNotNull();
assertThat(entry.getDn())
.isNotNull()
.isEqualTo("cn=John Doe,ou=users,dc=example,dc=com");
assertThat(entry.getGroups())
.hasSize(4)
.contains("Developers", "QA", "Engineers", "Whitespace Engineers");
}
@Test
public void testListGroups() throws Exception {
final Set<String> groups = connector.listGroups(connection, "ou=groups,dc=example,dc=com", "(objectClass=top)", "cn");
assertThat(groups)
.hasSize(4)
.contains("Developers", "QA", "Engineers", "Whitespace Engineers");
}
@Test
public void testFindGroupsWithWhitespace() throws Exception {
final LdapEntry ldapEntry1 = new LdapEntry();
ldapEntry1.setDn("cn=John Doe,ou=users,dc=example,dc=com");
ldapEntry1.put("uid", "john");
final LdapEntry ldapEntry2 = new LdapEntry();
ldapEntry2.setDn("cn=John Doe, ou=users, dc=example, dc=com");
ldapEntry2.put("uid", "john");
final Set<String> groups1 = connector.findGroups(connection,
"ou=groups,dc=example,dc=com",
"(objectClass=groupOfUniqueNames)",
"cn",
ldapEntry1);
final Set<String> groups2 = connector.findGroups(connection,
"ou=groups,dc=example,dc=com",
"(objectClass=groupOfUniqueNames)",
"cn",
ldapEntry2);
assertThat(groups1).hasSize(2).containsOnly("Whitespace Engineers", "Engineers");
assertThat(groups2).hasSize(2).containsOnly("Whitespace Engineers", "Engineers");
}
@Test
public void authenticateThrowsIllegalArgumentExceptionIfPrincipalIsNull() throws LdapException {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Binding with empty principal is forbidden.");
connector.authenticate(connection, null, "secret");
}
@Test
public void authenticateThrowsIllegalArgumentExceptionIfPrincipalIsEmpty() throws LdapException {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Binding with empty principal is forbidden.");
connector.authenticate(connection, "", "secret");
}
@Test
public void authenticateThrowsIllegalArgumentExceptionIfCredentialsAreNull() throws LdapException {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Binding with empty credentials is forbidden.");
connector.authenticate(connection, "principal", null);
}
@Test
public void authenticateThrowsIllegalArgumentExceptionIfCredentialsAreEmpty() throws LdapException {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Binding with empty credentials is forbidden.");
connector.authenticate(connection, "principal", "");
}
}