io.apiman.gateway.engine.policies.BasicAuthLDAPTest.java Source code

Java tutorial

Introduction

Here is the source code for io.apiman.gateway.engine.policies.BasicAuthLDAPTest.java

Source

/*
 * Copyright 2015 JBoss Inc
 *
 * Licensed 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 io.apiman.gateway.engine.policies;

import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.PolicyFailureType;
import io.apiman.gateway.engine.components.ILdapComponent;
import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent;
import io.apiman.gateway.engine.impl.DefaultLdapComponent;
import io.apiman.gateway.engine.policies.config.BasicAuthenticationConfig;
import io.apiman.gateway.engine.policy.IPolicyChain;
import io.apiman.gateway.engine.policy.IPolicyContext;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
import org.apache.directory.shared.ldap.entry.ServerEntry;
import org.apache.directory.shared.ldap.ldif.LdifEntry;
import org.apache.directory.shared.ldap.ldif.LdifReader;
import org.apache.directory.shared.ldap.name.DN;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

/**
 * Fires up an ldap server to do a better job testing the basic auth
 * policy when configured for ldap.
 *
 * @author eric.wittmann@redhat.com
 */
@SuppressWarnings({ "nls", "javadoc" })
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = { @CreateTransport(protocol = "LDAP", port = 7654) })
public class BasicAuthLDAPTest extends AbstractLdapTestUnit {

    private static final String LDAP_SERVER = "localhost";

    private static JdbmPartition partition;

    @Before
    public void setUp() throws Exception {

        if (partition != null) {
            return;
        }
        File targetDir = new File("target");
        if (!targetDir.isDirectory()) {
            throw new Exception("Couldn't find maven target directory: " + targetDir);
        }
        File partitionDir = new File(targetDir, "_ldap-partition");
        if (partitionDir.exists()) {
            FileUtils.deleteDirectory(partitionDir);
        }
        partitionDir.mkdirs();

        final File partitionDirectory = partitionDir;
        partition = new JdbmPartition();
        partition.setId("apiman");
        partition.setPartitionDir(partitionDirectory);
        partition.setSchemaManager(service.getSchemaManager());
        partition.setSuffix("o=apiman");
        service.addPartition(partition);

        // Inject the foo root entry if it does not already exist
        try {
            service.getAdminSession().lookup(partition.getSuffixDn());
        } catch (Exception lnnfe) {
            DN dn = new DN("o=apiman");
            ServerEntry entry = service.newEntry(dn);
            entry.add("objectClass", "top", "domain", "extensibleObject");
            entry.add("dc", "apiman");
            entry.add("cn", "o=apiman");
            service.getAdminSession().add(entry);
        }

        try {
            injectLdifFiles("io/apiman/gateway/engine/policies/users.ldif");
        } catch (Exception e) {
            throw e;
        }
    }

    // This will help determine whether all the right data is present in ldap
    @Test
    @Ignore
    public void testLdap() throws Exception {
        DirContext ctx = createContext();
        Assert.assertNotNull(ctx);

        SearchControls controls = new SearchControls();
        controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        NamingEnumeration<SearchResult> result = ctx.search("o=apiman", "(ObjectClass=*)", controls);

        System.out.println(" ==== Search Results ====");
        while (result.hasMore()) {
            SearchResult entry = result.next();
            System.out.println(" ===> " + entry.getName());
        }

    }

    /**
     * Test method for {@link io.apiman.gateway.engine.policies.BasicAuthenticationPolicy#apply(ServiceRequest, IPolicyContext, Object, IPolicyChain)}.
     */
    @Test
    public void testApply() throws Exception {
        // Test using a direct bind to the user account
        //////////////////////////////////////////////////
        String json = "{\r\n" + "    \"realm\" : \"TestRealm\",\r\n"
                + "    \"forwardIdentityHttpHeader\" : \"X-Authenticated-Identity\",\r\n"
                + "    \"ldapIdentity\" : {\r\n" + "        \"url\" : \"ldap://" + LDAP_SERVER + ":"
                + ldapServer.getPort() + "\",\r\n" + "        \"dnPattern\" : \"uid=${username},ou=system\"\r\n"
                + "    }\r\n" + "}";

        doTest(json, null, null, PolicyFailureCodes.BASIC_AUTH_REQUIRED);
        doTest(json, "admin", "invalid_password", PolicyFailureCodes.BASIC_AUTH_FAILED);
        doTest(json, "admin", "secret", null);

        // Test using a service account with user search
        //////////////////////////////////////////////////
        json = "{\r\n" + "  \"realm\" : \"TestRealm\",\r\n" + "  \"ldapIdentity\" : {\r\n"
                + "    \"url\" : \"ldap://" + LDAP_SERVER + ":" + ldapServer.getPort() + "\",\r\n"
                + "    \"dnPattern\" : \"uid=${username},ou=system\",\r\n"
                + "    \"bindAs\" : \"ServiceAccount\",\r\n" + "    \"credentials\" : {\r\n"
                + "      \"username\" : \"admin\",\r\n" + "      \"password\" : \"secret\"\r\n" + "    },\r\n"
                + "    \"userSearch\" : {\r\n" + "      \"baseDn\" : \"ou=people,o=apiman\",\r\n"
                + "      \"expression\" : \"(uid=${username})\"\r\n" + "    }\r\n" + "  }\r\n" + "}";

        doTest(json, null, null, PolicyFailureCodes.BASIC_AUTH_REQUIRED);
        doTest(json, "ewittman", "invalid_password", PolicyFailureCodes.BASIC_AUTH_FAILED);
        doTest(json, "unknown_user", "password", PolicyFailureCodes.BASIC_AUTH_FAILED);
        doTest(json, "ewittman", "ewittman", null);

        // Test using a service account with user search
        //////////////////////////////////////////////////
        json = "{\r\n" + "  \"realm\" : \"TestRealm\",\r\n" + "  \"ldapIdentity\" : {\r\n"
                + "    \"url\" : \"ldap://" + LDAP_SERVER + ":" + ldapServer.getPort() + "\",\r\n"
                + "    \"dnPattern\" : \"uid=${username},ou=system\",\r\n"
                + "    \"bindAs\" : \"ServiceAccount\",\r\n" + "    \"credentials\" : {\r\n"
                + "      \"username\" : \"admin\",\r\n" + "      \"password\" : \"secret\"\r\n" + "    },\r\n"
                + "    \"userSearch\" : {\r\n" + "      \"baseDn\" : \"ou=people,o=apiman\",\r\n"
                + "      \"expression\" : \"(uid=${username})\"\r\n" + "    }\r\n" + "  }\r\n" + "}";
        doTest(json, null, null, PolicyFailureCodes.BASIC_AUTH_REQUIRED);
        doTest(json, "ewittman", "invalid_password", PolicyFailureCodes.BASIC_AUTH_FAILED);
        doTest(json, "unknown_user", "password", PolicyFailureCodes.BASIC_AUTH_FAILED);
        doTest(json, "ewittman", "ewittman", null);

        // Test with the extraction of user roles
        //////////////////////////////////////////////////
        json = "{\r\n" + "  \"realm\" : \"TestRealm\",\r\n" + "  \"ldapIdentity\" : {\r\n"
                + "    \"url\" : \"ldap://localhost:7654\",\r\n"
                + "    \"dnPattern\" : \"uid=${username},ou=system\",\r\n"
                + "    \"bindAs\" : \"ServiceAccount\",\r\n" + "    \"credentials\" : {\r\n"
                + "      \"username\" : \"admin\",\r\n" + "      \"password\" : \"secret\"\r\n" + "    },\r\n"
                + "    \"userSearch\" : {\r\n" + "      \"baseDn\" : \"ou=people,o=apiman\",\r\n"
                + "      \"expression\" : \"(uid=${username})\"\r\n" + "    },\r\n"
                + "    \"extractRoles\" : true,\r\n" + "    \"membershipAttribute\" : \"title\",\r\n"
                + "    \"rolenameAttribute\" : \"cn\"\r\n" + "  }\r\n" + "}";
        Set<String> expectedRoles = new HashSet<>();
        expectedRoles.add("user");
        expectedRoles.add("admin");
        doTest(json, "ewittman", "ewittman", null, expectedRoles);
        doTest(json, "ewittman", "ewittmanx", PolicyFailureCodes.BASIC_AUTH_FAILED, expectedRoles);
    }

    private void doTest(String json, String username, String password, Integer expectedFailureCode)
            throws Exception {
        doTest(json, username, password, expectedFailureCode, null);
    }

    // pass null if you expect success
    private void doTest(String json, String username, String password, Integer expectedFailureCode,
            Set<String> expectedRoles) throws Exception {
        BasicAuthenticationPolicy policy = new BasicAuthenticationPolicy();
        BasicAuthenticationConfig config = policy.parseConfiguration(json);
        ApiRequest request = new ApiRequest();
        request.setType("GET");
        request.setApiKey("12345");
        request.setRemoteAddr("1.2.3.4");
        request.setDestination("/");
        IPolicyContext context = Mockito.mock(IPolicyContext.class);
        final PolicyFailure failure = new PolicyFailure();
        Mockito.when(context.getComponent(IPolicyFailureFactoryComponent.class))
                .thenReturn(new IPolicyFailureFactoryComponent() {
                    @Override
                    public PolicyFailure createFailure(PolicyFailureType type, int failureCode, String message) {
                        failure.setType(type);
                        failure.setFailureCode(failureCode);
                        failure.setMessage(message);
                        return failure;
                    }
                });

        // The LDAP stuff we're testing!
        Mockito.when(context.getComponent(ILdapComponent.class)).thenReturn(new DefaultLdapComponent());

        IPolicyChain<ApiRequest> chain = Mockito.mock(IPolicyChain.class);

        if (username != null) {
            request.getHeaders().put("Authorization", createBasicAuthorization(username, password));
        }

        if (expectedFailureCode == null) {
            policy.apply(request, context, config, chain);
            Mockito.verify(chain).doApply(request);
        } else {
            policy.apply(request, context, config, chain);
            Mockito.verify(chain).doFailure(failure);
            Assert.assertEquals(expectedFailureCode.intValue(), failure.getFailureCode());
        }

        if (expectedRoles != null && expectedFailureCode == null) {
            Mockito.verify(context).setAttribute(AuthorizationPolicy.AUTHENTICATED_USER_ROLES, expectedRoles);
        }
    }

    /**
     * Creates the http Authorization string for the given credentials.
     * @param username
     * @param password
     */
    private String createBasicAuthorization(String username, String password) {
        String creds = username + ":" + password;
        StringBuilder builder = new StringBuilder();
        builder.append("Basic ");
        builder.append(Base64.encodeBase64String(creds.getBytes()));
        return builder.toString();
    }

    private DirContext createContext() throws NamingException {
        // Create a environment container
        Hashtable<Object, Object> env = new Hashtable<>();

        String url = "ldap://" + LDAP_SERVER + ":" + ldapServer.getPort();

        // Create a new context pointing to the partition
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
        env.put(Context.SECURITY_CREDENTIALS, "secret");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        // Let's open a connection on this partition
        InitialContext initialContext = new InitialContext(env);

        // We should be able to read it
        DirContext appRoot = (DirContext) initialContext.lookup("");
        Assert.assertNotNull(appRoot);

        return appRoot;
    }

    public static void injectLdifFiles(String... ldifFiles) throws Exception {
        if (ldifFiles != null && ldifFiles.length > 0) {
            for (String ldifFile : ldifFiles) {
                InputStream is = null;
                try {
                    is = BasicAuthLDAPTest.class.getClassLoader().getResourceAsStream(ldifFile);
                    if (is == null) {
                        throw new FileNotFoundException("LDIF file '" + ldifFile + "' not found.");
                    } else {
                        try {
                            LdifReader ldifReader = new LdifReader(is);
                            for (LdifEntry entry : ldifReader) {
                                injectEntry(entry);
                            }
                            ldifReader.close();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                } finally {
                    IOUtils.closeQuietly(is);
                }
            }
        }
    }

    private static void injectEntry(LdifEntry entry) throws Exception {
        if (entry.isChangeAdd()) {
            service.getAdminSession().add(new DefaultServerEntry(service.getSchemaManager(), entry.getEntry()));
        } else if (entry.isChangeModify()) {
            service.getAdminSession().modify(entry.getDn(), entry.getModificationItems());
        } else {
            String message = I18n.err(I18n.ERR_117, entry.getChangeType());
            throw new NamingException(message);
        }
    }

}