/**
* Copyright 2015 StreamSets Inc.
*
* Licensed under the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.streamsets.datacollector.http;
import com.google.common.collect.ImmutableSet;
import com.streamsets.datacollector.main.MainStandalonePipelineManagerModule;
import com.streamsets.datacollector.main.RuntimeInfo;
import com.streamsets.datacollector.main.RuntimeModule;
import com.streamsets.datacollector.task.Task;
import com.streamsets.datacollector.task.TaskWrapper;
import com.streamsets.datacollector.util.Configuration;
import com.streamsets.testing.NetworkUtils;
import dagger.ObjectGraph;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.FileWriter;
import java.io.StringReader;
import java.io.Writer;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RunWith(FrameworkRunner.class)
@CreateLdapServer(
transports =
{
@CreateTransport(protocol = "LDAP")
})
public class TestLDAPAuthentication extends AbstractLdapTestUnit {
private static Logger LOG = LoggerFactory.getLogger(TestLDAPAuthentication.class);
private static String createTestDir() {
File dir = new File("target", UUID.randomUUID().toString());
Assert.assertTrue(dir.mkdirs());
return dir.getAbsolutePath();
}
private static String baseDir;
private static Task server;
private static String baseURL;
private static RuntimeInfo runtimeInfo;
@Before
public void setup() throws Exception {
System.clearProperty(WebServerTask.JAVA_SECURITY_AUTH_LOGIN_CONFIG);
server = null;
baseDir = createTestDir();
Assert.assertTrue(new File(baseDir, "etc").mkdir());
Assert.assertTrue(new File(baseDir, "data").mkdir());
Assert.assertTrue(new File(baseDir, "log").mkdir());
Assert.assertTrue(new File(baseDir, "web").mkdir());
System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR, baseDir + "/etc");
System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.DATA_DIR, baseDir + "/data");
System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.LOG_DIR, baseDir + "/log");
System.setProperty(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.STATIC_WEB_DIR, baseDir + "/web");
//Create users, groups and assign users to group in LDAP server
createPrincipal("admin1", "admin1");
createPrincipal("manager", "manager");
createPrincipal("creator", "creator");
createPrincipal("guest", "guest");
createGroup("Admins", "uid=admin1,ou=users,ou=system");
createGroup("Managers", "uid=manager,ou=users,ou=system");
createGroup("Creators", "uid=creator,ou=users,ou=system");
createGroup("Guests", "uid=guest,ou=users,ou=system");
}
@After
public void cleanup() {
if (server.getStatus() == Task.Status.RUNNING) {
stopServer();
}
System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR);
System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.DATA_DIR);
System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.LOG_DIR);
System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.STATIC_WEB_DIR);
}
private static String startServer(String authenticationType, String ldapConf) throws Exception {
int port = NetworkUtils.getRandomPort();
Configuration conf = new Configuration();
conf.set(WebServerTask.HTTP_PORT_KEY, port);
conf.set(WebServerTask.AUTHENTICATION_KEY, authenticationType);
conf.set(WebServerTask.HTTP_AUTHENTICATION_LOGIN_MODULE, "ldap");
conf.set(WebServerTask.HTTP_AUTHENTICATION_LDAP_ROLE_MAPPING,
"Admins:admin;Managers:manager;Creators:creator;Guests:guest");
Writer writer = new FileWriter(new File(System.getProperty(RuntimeModule.SDC_PROPERTY_PREFIX +
RuntimeInfo.CONFIG_DIR), "sdc.properties"));
conf.save(writer);
writer.close();
File ldapConfFile = new File(System.getProperty(RuntimeModule.SDC_PROPERTY_PREFIX +
RuntimeInfo.CONFIG_DIR), "ldap-login.conf");
writer = new FileWriter(ldapConfFile);
writer.write(ldapConf);
writer.close();
Files.setPosixFilePermissions(ldapConfFile.toPath(), ImmutableSet.of(PosixFilePermission.OWNER_EXECUTE,
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE));
ObjectGraph dagger = ObjectGraph.create(MainStandalonePipelineManagerModule.class);
runtimeInfo = dagger.get(RuntimeInfo.class);
runtimeInfo.setAttribute(RuntimeInfo.LOG4J_CONFIGURATION_URL_ATTR, new URL("file://" + baseDir + "/log4j.properties"));
server = dagger.get(TaskWrapper.class);
server.init();
server.run();
return "http://127.0.0.1:" + port;
}
private static void stopServer() {
if (server != null) {
server.stop();
}
}
private void createPrincipal(String principal, String password)
throws Exception {
DirectoryService ds = getService();
String baseDn = "ou=users,ou=system";
String content = "dn: uid=" + principal + "," + baseDn + "\n" +
"objectClass: top\n" +
"objectClass: person\n" +
"objectClass: inetOrgPerson\n" +
"cn: " + principal + "\n" +
"sn: " + principal + "\n" +
"uid: " + principal + "\n" +
"userPassword: " + password;
for (LdifEntry ldifEntry : new LdifReader(new StringReader(content))) {
ds.getAdminSession().add(new DefaultEntry(ds.getSchemaManager(),
ldifEntry.getEntry()));
}
}
private void createGroup(String groupName, String memberDn) throws Exception {
DirectoryService ds = getService();
String baseDn = "ou=groups,ou=system";
String content = "dn: cn=" + groupName + "," + baseDn + "\n" +
"objectClass: top\n" +
"objectClass: groupofnames\n" +
"cn: " + groupName + "\n" +
"description: " + groupName + "\n" +
"member: " + memberDn;
for (LdifEntry ldifEntry : new LdifReader(new StringReader(content))) {
ds.getAdminSession().add(new DefaultEntry(ds.getSchemaManager(),
ldifEntry.getEntry()));
}
}
@Test
public void testLDAPAuthenticationForm() throws Exception {
testLDAPAuthentication("form");
}
@Test
public void testLDAPAuthenticationBasic() throws Exception {
testLDAPAuthentication("basic");
}
@Test
public void testLDAPAuthenticationDigest() throws Exception {
testLDAPAuthentication("digest");
}
private void testLDAPAuthentication(String authType) throws Exception {
String single = "ldap {\n" +
" com.streamsets.datacollector.http.LdapLoginModule required\n" +
" debug=\"false\"\n" +
" useLdaps=\"false\"\n" +
" contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" +
" hostname=\"localhost\"\n" +
" port=\"" + ldapServer.getPort() + "\"\n" +
" bindDn=\"uid=admin,ou=system\"\n" +
" bindPassword=\"secret\"\n" +
" authenticationMethod=\"simple\"\n" +
" forceBindingLogin=\"false\"\n" +
" userBaseDn=\"ou=users,ou=system\"\n" +
" userRdnAttribute=\"uid\"\n" +
" userIdAttribute=\"uid\"\n" +
" userPasswordAttribute=\"userPassword\"\n" +
" userObjectClass=\"inetOrgPerson\"\n" +
" roleBaseDn=\"ou=groups,ou=system\"\n" +
" roleNameAttribute=\"cn\"\n" +
" roleMemberAttribute=\"member\"\n" +
" roleObjectClass=\"groupofnames\";\n" +
"};";
try {
String baseURL = startServer(authType, single);
testAuthenticationAndRoleMapping(baseURL, authType, "admin1", "admin1", "admin");
testAuthenticationAndRoleMapping(baseURL, authType, "manager", "manager", "manager");
testAuthenticationAndRoleMapping(baseURL, authType, "creator", "creator", "creator");
testAuthenticationAndRoleMapping(baseURL, authType, "guest", "guest", "guest");
stopServer();
} catch (Exception e) {
LOG.debug("Ignoring exception", e);
} finally {
stopServer();
}
}
@Test
public void testMultipleLDAPAuthentication() throws Exception {
String authType = "basic";
String multiple = "ldap {\n" + // Incorrect ldap entry. This should fail
" com.streamsets.datacollector.http.LdapLoginModule required\n" +
" debug=\"false\"\n" +
" useLdaps=\"false\"\n" +
" contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" +
" hostname=\"dummyhost\"\n" + // hostname is dummy host. should cause timeout error
" port=\"" + ldapServer.getPort() + "\"\n" +
" bindDn=\"uid=admin,ou=system\"\n" +
" bindPassword=\"dummy\"\n" +
" authenticationMethod=\"simple\"\n" +
" forceBindingLogin=\"false\"\n" +
" userBaseDn=\"ou=users,ou=system\"\n" +
" userRdnAttribute=\"uid\"\n" +
" userIdAttribute=\"uid\"\n" +
" userPasswordAttribute=\"userPassword\"\n" +
" userObjectClass=\"inetOrgPerson\"\n" +
" roleBaseDn=\"ou=groups,ou=system\"\n" +
" roleNameAttribute=\"cn\"\n" +
" roleMemberAttribute=\"member\"\n" +
" roleObjectClass=\"groupofnames\";\n" +
" \n" + // Correct ldap entry. This should succeed.
" com.streamsets.datacollector.http.LdapLoginModule required\n" +
" debug=\"false\"\n" +
" useLdaps=\"false\"\n" +
" contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" +
" hostname=\"localhost\"\n" +
" port=\"" + ldapServer.getPort() + "\"\n" +
" bindDn=\"uid=admin,ou=system\"\n" +
" bindPassword=\"secret\"\n" +
" authenticationMethod=\"simple\"\n" +
" forceBindingLogin=\"false\"\n" +
" userBaseDn=\"ou=users,ou=system\"\n" +
" userRdnAttribute=\"uid\"\n" +
" userIdAttribute=\"uid\"\n" +
" userPasswordAttribute=\"userPassword\"\n" +
" userObjectClass=\"inetOrgPerson\"\n" +
" roleBaseDn=\"ou=groups,ou=system\"\n" +
" roleNameAttribute=\"cn\"\n" +
" roleMemberAttribute=\"member\"\n" +
" roleObjectClass=\"groupofnames\";\n" +
"};";
try {
String baseURL = startServer(authType, multiple);
// first entry in ldap-login.conf is host unreachable, but second entry has correct ldap info.
// authentication should succeed.
testAuthenticationAndRoleMapping(baseURL, authType, "admin1", "admin1", "admin");
testAuthenticationAndRoleMapping(baseURL, authType, "manager", "manager", "manager");
testAuthenticationAndRoleMapping(baseURL, authType, "creator", "creator", "creator");
testAuthenticationAndRoleMapping(baseURL, authType, "guest", "guest", "guest");
stopServer();
} catch (Exception e) {
LOG.debug("Ignoring exception", e);
} finally {
stopServer();
}
}
@SuppressWarnings("unchecked")
private void testAuthenticationAndRoleMapping(String baseURL, String authType, String username, String password,
String role) {
String userInfoURI = baseURL + "/rest/v1/system/info/currentUser";
HttpAuthenticationFeature feature = null;
switch(authType) {
case "basic":
case "form":
feature = HttpAuthenticationFeature.basic(username, password);
break;
case "digest":
feature = HttpAuthenticationFeature.digest(username, password);
break;
}
Response response = ClientBuilder
.newClient()
.register(feature)
.target(userInfoURI)
.request()
.get();
Assert.assertEquals(200, response.getStatus());
Map userInfo = response.readEntity(Map.class);
Assert.assertTrue(userInfo.containsKey("user"));
Assert.assertEquals(username, userInfo.get("user"));
Assert.assertTrue(userInfo.containsKey("roles"));
List<String> roles = (List<String>)userInfo.get("roles");
Assert.assertEquals(1, roles.size());
Assert.assertEquals(role, roles.get(0));
}
@Test(expected = RuntimeException.class)
public void testDigesLDAPForceBinding() throws Exception {
String single = "ldap {\n" +
" com.streamsets.datacollector.http.LdapLoginModule required\n" +
" debug=\"false\"\n" +
" useLdaps=\"false\"\n" +
" contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" +
" hostname=\"localhost\"\n" +
" port=\"" + ldapServer.getPort() + "\"\n" +
" bindDn=\"uid=admin,ou=system\"\n" +
" bindPassword=\"secret\"\n" +
" authenticationMethod=\"simple\"\n" +
" forceBindingLogin=\"true\"\n" +
" userBaseDn=\"ou=users,ou=system\"\n" +
" userRdnAttribute=\"uid\"\n" +
" userIdAttribute=\"uid\"\n" +
" userPasswordAttribute=\"userPassword\"\n" +
" userObjectClass=\"inetOrgPerson\"\n" +
" roleBaseDn=\"ou=groups,ou=system\"\n" +
" roleNameAttribute=\"cn\"\n" +
" roleMemberAttribute=\"member\"\n" +
" roleObjectClass=\"groupofnames\";\n" +
"};";
startServer("digest", single);
}
}