/**
* 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.realm;
import com.google.common.collect.Maps;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
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.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.graylog2.Configuration;
import org.graylog2.plugin.database.users.User;
import org.graylog2.security.ldap.LdapConnector;
import org.graylog2.security.ldap.LdapSettingsImpl;
import org.graylog2.security.ldap.LdapSettingsService;
import org.graylog2.shared.security.Permissions;
import org.graylog2.shared.security.ldap.LdapEntry;
import org.graylog2.shared.security.ldap.LdapSettings;
import org.graylog2.shared.users.UserService;
import org.graylog2.users.RoleService;
import org.graylog2.users.UserImpl;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = {
@CreateTransport(protocol = "LDAP")
})
@CreateDS(
name = "LdapUserAuthenticatorTest",
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 LdapUserAuthenticatorTest extends AbstractLdapTestUnit {
private static final String ADMIN_DN = "uid=admin,ou=system";
private static final String ADMIN_PASSWORD = "secret";
private static final AuthenticationToken VALID_TOKEN = new UsernamePasswordToken("john", "test");
private static final AuthenticationToken INVALID_TOKEN = new UsernamePasswordToken("john", "__invalid__");
private static final String PASSWORD_SECRET = "r8Om85b0zgHmiGsK86T3ZFlmSIdMd3hcKmOa4T60MSPEobfRCTLNOK4T91GdHbGx";
private LdapConnector ldapConnector;
private LdapServer server;
private LdapSettingsService ldapSettingsService;
private LdapSettings ldapSettings;
private Configuration configuration;
private UserService userService;
@Before
public void setUp() throws Exception {
server = getLdapServer();
final LdapConnectionConfig ldapConfig = new LdapConnectionConfig();
ldapConfig.setLdapHost("localHost");
ldapConfig.setLdapPort(server.getPort());
ldapConfig.setName(ADMIN_DN);
ldapConfig.setCredentials(ADMIN_PASSWORD);
configuration = mock(Configuration.class);
when(configuration.getPasswordSecret()).thenReturn(PASSWORD_SECRET);
ldapConnector = new LdapConnector(10000);
ldapSettingsService = mock(LdapSettingsService.class);
userService = mock(UserService.class);
ldapSettings = new LdapSettingsImpl(configuration, mock(RoleService.class));
ldapSettings.setEnabled(true);
ldapSettings.setUri(URI.create("ldap://localhost:" + server.getPort()));
ldapSettings.setUseStartTls(false);
ldapSettings.setSystemUsername(ADMIN_DN);
ldapSettings.setSystemPassword(ADMIN_PASSWORD);
ldapSettings.setSearchBase("ou=users,dc=example,dc=com");
ldapSettings.setSearchPattern("(&(objectClass=posixAccount)(uid={0}))");
ldapSettings.setDisplayNameAttribute("cn");
ldapSettings.setActiveDirectory(false);
ldapSettings.setGroupSearchBase("ou=groups,dc=example,dc=com");
ldapSettings.setGroupIdAttribute("cn");
ldapSettings.setGroupSearchPattern("(|(objectClass=groupOfNames)(objectClass=posixGroup))");
}
@Test
public void testDoGetAuthenticationInfo() throws Exception {
final LdapUserAuthenticator authenticator = spy(new LdapUserAuthenticator(ldapConnector,
ldapSettingsService,
userService,
mock(RoleService.class),
DateTimeZone.UTC));
when(ldapSettingsService.load())
.thenReturn(ldapSettings);
doReturn(mock(User.class))
.when(authenticator)
.syncFromLdapEntry(any(LdapEntry.class),any(LdapSettings.class), anyString());
assertThat(authenticator.doGetAuthenticationInfo(VALID_TOKEN)).isNotNull();
assertThat(authenticator.doGetAuthenticationInfo(INVALID_TOKEN)).isNull();
}
@Test
public void testDoGetAuthenticationInfoDeniesEmptyPassword() throws Exception {
final LdapUserAuthenticator authenticator = new LdapUserAuthenticator(ldapConnector,
ldapSettingsService,
userService,
mock(RoleService.class),
DateTimeZone.UTC);
when(ldapSettingsService.load()).thenReturn(ldapSettings);
assertThat(authenticator.doGetAuthenticationInfo(new UsernamePasswordToken("john", (char[]) null))).isNull();
assertThat(authenticator.doGetAuthenticationInfo(new UsernamePasswordToken("john", new char[0]))).isNull();
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void testSyncFromLdapEntry() {
final LdapUserAuthenticator authenticator = spy(new LdapUserAuthenticator(ldapConnector,
ldapSettingsService,
userService,
mock(RoleService.class),
DateTimeZone.UTC));
final LdapEntry userEntry = new LdapEntry();
final LdapSettings ldapSettings = mock(LdapSettings.class);
when(ldapSettings.getDisplayNameAttribute()).thenReturn("displayName");
when(ldapSettings.getDefaultGroupId()).thenReturn("54e3deadbeefdeadbeef0001");
when(ldapSettings.getAdditionalDefaultGroupIds()).thenReturn(Collections.emptySet());
when(userService.create())
.thenReturn(new UserImpl(null, new Permissions(Collections.emptySet()), Maps.newHashMap()));
final User ldapUser = authenticator.syncFromLdapEntry(userEntry, ldapSettings, "user");
assertThat(ldapUser).isNotNull();
assertThat(ldapUser.isExternalUser()).isTrue();
assertThat(ldapUser.getName()).isEqualTo("user");
assertThat(ldapUser.getEmail()).isEqualTo("user@localhost");
assertThat(ldapUser.getHashedPassword()).isEqualTo("User synced from LDAP.");
assertThat(ldapUser.getTimeZone()).isEqualTo(DateTimeZone.UTC);
assertThat(ldapUser.getRoleIds()).containsOnly("54e3deadbeefdeadbeef0001");
assertThat(ldapUser.getPermissions()).isNotEmpty();
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void testSyncFromLdapEntryExistingUser() {
final LdapUserAuthenticator authenticator = spy(new LdapUserAuthenticator(ldapConnector,
ldapSettingsService,
userService,
mock(RoleService.class),
DateTimeZone.UTC));
final LdapEntry userEntry = new LdapEntry();
final LdapSettings ldapSettings = mock(LdapSettings.class);
when(ldapSettings.getDisplayNameAttribute()).thenReturn("displayName");
when(ldapSettings.getDefaultGroupId()).thenReturn("54e3deadbeefdeadbeef0001");
when(ldapSettings.getAdditionalDefaultGroupIds()).thenReturn(Collections.emptySet());
final HashMap<String, Object> fields = Maps.newHashMap();
fields.put("permissions", Collections.singletonList("test:permission:1234"));
when(userService.load(anyString()))
.thenReturn(new UserImpl(null, new Permissions(Collections.emptySet()), fields));
final User ldapUser = authenticator.syncFromLdapEntry(userEntry, ldapSettings, "user");
assertThat(ldapUser).isNotNull();
assertThat(ldapUser.getPermissions()).contains("test:permission:1234");
assertThat(ldapUser.isExternalUser()).isTrue();
assertThat(ldapUser.getName()).isEqualTo("user");
assertThat(ldapUser.getEmail()).isEqualTo("user@localhost");
assertThat(ldapUser.getHashedPassword()).isEqualTo("User synced from LDAP.");
assertThat(ldapUser.getTimeZone()).isEqualTo(DateTimeZone.UTC);
assertThat(ldapUser.getRoleIds()).containsOnly("54e3deadbeefdeadbeef0001");
assertThat(ldapUser.getPermissions()).isNotEmpty();
}
}