/** * Copyright 2016 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.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.main.RuntimeModule; import org.apache.directory.ldap.client.api.LdapConnection; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.junit.Assert; import org.junit.ClassRule; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.testcontainers.containers.GenericContainer; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Response; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @Ignore public class LdapAuthenticationIT extends LdapAuthenticationBaseIT { // Connection to Ldap Server private static LdapConnection connection; @ClassRule public static GenericContainer server = new GenericContainer("osixia/openldap:1.1.6").withExposedPorts(LDAP_PORT); @BeforeClass public static void setUpClass() throws Exception { // create conf dir new File(confDir).mkdirs(); connection = setupLdapServer(server, "ldap-server2-entries.ldif"); } @AfterClass public static void cleanUpClass() throws IOException { connection.close(); if (server != null) { server.stop(); } System.getProperties().remove(RuntimeModule.SDC_PROPERTY_PREFIX + RuntimeInfo.CONFIG_DIR); } /** * Test the default behavior using existing configuration. Default behavior is searching * role by full DN. User needs to provide roleMemberAttribute and roleObjectClass, * and generate a search filter by (&(roleObjectClass=roleObjectClass)(roleMemberAttribute='user's full DN')) * @throws Exception */ @Test public void testDefaultBehavior() throws Exception { String originalConf = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"false\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleMemberAttribute=\"member\"\n" + " roleObjectClass=\"groupOfNames\"\n" + " roleFilter=\"\";\n" + // roleSearchFilter is empty "};"; assertAuthenticationSuccess(originalConf, "user4", "user4"); } /** * Test using search filter. If roleSearchFilter is provided and has {user}, then * we apply the filter by replacing {user} with UID. * @throws Exception */ @Test public void testRoleFilterMemberUidForceBindingFalse() throws Exception { String memberUidFilter = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"false\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleMemberAttribute=\"memberUid\"\n" + " roleObjectClass=\"posixGroup\"\n" + " roleFilter=\"memberUid={user}\";\n" + "};"; assertAuthenticationSuccess(memberUidFilter, "user6", "user6"); } /** * Test using search filter. If roleSearchFilter is provided and has {user}, then * we apply the filter by replacing {user} with UID. * @throws Exception */ @Test public void testRoleFilterMemberUidForceBindingTrue() throws Exception { String memberUidFilter = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"true\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleMemberAttribute=\"memberUid\"\n" + " roleObjectClass=\"posixGroup\"\n" + " roleFilter=\"memberUid={user}\";\n" + "};"; assertAuthenticationSuccess(memberUidFilter, "user6", "user6"); } /** * Test using search filter. If roleSearchFilter is provided and has {dn}, then * we apply the filter by replacing {dn} with user's full DN. * @throws Exception */ @Test public void testRoleFilterMember() throws Exception { String memberFilter = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"false\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleObjectClass=\"groupOfNames\"\n" + " roleFilter=\"member={dn}\";\n" + "};"; assertAuthenticationSuccess(memberFilter, "user4", "user4"); } /** * Test using search filter. If roleSearchFilter is provided and has {dn}, then * we apply the filter by replacing {dn} with user's full DN. * @throws Exception */ @Test public void testWrongFilter() throws Exception { String memberFilter = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"false\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleMemberAttribute=\"member\"\n" + " roleObjectClass=\"groupOfNames\"\n" + " roleFilter=\"member={}\";\n" + // {} is invalid. Default(search by full DN) should be applied "};"; assertAuthenticationSuccess(memberFilter, "user4", "user4"); } /** * Test using search filter. If roleSearchFilter is provided and has {dn}, then * we apply the filter by replacing {dn} with user's full DN. * @throws Exception */ @Test public void testEmptyPassword() throws Exception { String memberFilter = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"false\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleMemberAttribute=\"member\"\n" + " roleObjectClass=\"groupOfNames\"\n" + " roleFilter=\"member={dn}\";\n" + // {} is invalid. Default(search by full DN) should be applied "};"; startSDCServer(memberFilter); String userInfoURI = sdcURL + "/rest/v1/system/info/currentUser"; HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("user4", ""); Response response = ClientBuilder .newClient() .register(feature) .target(userInfoURI) .request() .get(); Assert.assertEquals(401, response.getStatus()); } /** * Test that we can authenticate users that are more than one level below the baseUserDN in the tree. * @throws Exception */ @Test public void testSubtreeUserAuthentication() throws Exception { String originalConf = "ldap {\n" + // information for server 1 " com.streamsets.datacollector.http.LdapLoginModule required\n" + " debug=\"false\"\n" + " useLdaps=\"false\"\n" + " contextFactory=\"com.sun.jndi.ldap.LdapCtxFactory\"\n" + " hostname=\"" + server.getContainerIpAddress()+ "\"\n" + " port=\"" + server.getMappedPort(LDAP_PORT) + "\"\n" + " bindDn=\"" + BIND_DN + "\"\n" + " bindPassword=\"" + BIND_PWD + "\"\n" + " authenticationMethod=\"simple\"\n" + " forceBindingLogin=\"false\"\n" + " userBaseDn=\"ou=employees,dc=example,dc=org\"\n" + " userRdnAttribute=\"uid\"\n" + " userIdAttribute=\"uid\"\n" + " userPasswordAttribute=\"userPassword\"\n" + " userObjectClass=\"inetOrgPerson\"\n" + " roleBaseDn=\"ou=departments,dc=example,dc=org\"\n" + " roleNameAttribute=\"cn\"\n" + " roleMemberAttribute=\"member\"\n" + " roleObjectClass=\"groupOfNames\"\n" + " roleFilter=\"\";\n" + // roleSearchFilter is empty "};"; assertAuthenticationSuccess(originalConf, "internalUser1", "internalUser1Password"); } /** * Assert authentication result using ldap-login.conf with given username and password. * All user for this test is configured so that role "admin" will be found and successfully authenticated. * @param ldapConf The configuration for ldap-login.conf * @param username username to login * @param password password to login * @throws Exception */ public static void assertAuthenticationSuccess(String ldapConf, String username, String password) throws Exception{ startSDCServer(ldapConf); String userInfoURI = sdcURL + "/rest/v1/system/info/currentUser"; HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(username, password); 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")); Assert.assertEquals(1, ((List)userInfo.get("roles")).size()); Assert.assertEquals("admin", ((List)userInfo.get("roles")).get(0)); stopSDCServer(); } }