/*
 * Licensed to 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 org.apache.atlas.web.filters;

import org.apache.atlas.RequestContext;
import org.apache.atlas.web.security.BaseSecurityTest;
import org.apache.atlas.web.service.EmbeddedServer;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hdfs.web.URLConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.testng.annotations.Test;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.PrivilegedExceptionAction;

import static org.testng.Assert.assertEquals;

public class AtlasAuthenticationKerberosFilterTest extends BaseSecurityTest {
    public static final String TEST_USER_JAAS_SECTION = "TestUser";
    public static final String TESTUSER               = "testuser";
    public static final String TESTPASS               = "testpass";

    private File userKeytabFile;
    private File httpKeytabFile;

    @Test(enabled = false)
    public void testKerberosBasedLogin() throws Exception {
        String originalConf = System.getProperty("atlas.conf");

        setupKDCAndPrincipals();
        TestEmbeddedServer server = null;

        try {
            // setup the atlas-application.properties file
            String confDirectory = generateKerberosTestProperties();
            System.setProperty("atlas.conf", confDirectory);

            // need to create the web application programmatically in order to control the injection of the test
            // application properties
            server = new TestEmbeddedServer(23000, "webapp/target/apache-atlas");

            startEmbeddedServer(server.getServer());

            final URLConnectionFactory connectionFactory = URLConnectionFactory.DEFAULT_SYSTEM_CONNECTION_FACTORY;
            // attempt to hit server and get rejected
            URL               url        = new URL("http://localhost:23000/");
            HttpURLConnection connection = (HttpURLConnection) connectionFactory.openConnection(url, false);
            connection.setRequestMethod("GET");
            connection.connect();

            assertEquals(connection.getResponseCode(), 401);

            // need to populate the ticket cache with a local user, so logging in...
            Subject subject = loginTestUser();

            Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
                @Override
                public Object run() throws Exception {
                    // attempt to hit server and get rejected
                    URL               url        = new URL("http://localhost:23000/");
                    HttpURLConnection connection = (HttpURLConnection) connectionFactory.openConnection(url, true);
                    connection.setRequestMethod("GET");
                    connection.connect();

                    assertEquals(connection.getResponseCode(), 200);
                    assertEquals(RequestContext.get().getUser(), TESTUSER);
                    return null;
                }
            });
        } finally {
            server.getServer().stop();
            kdc.stop();

            if (originalConf != null) {
                System.setProperty("atlas.conf", originalConf);
            } else {
                System.clearProperty("atlas.conf");
            }
        }
    }

    public void setupKDCAndPrincipals() throws Exception {
        // set up the KDC
        File kdcWorkDir = startKDC();

        userKeytabFile = createKeytab(kdc, kdcWorkDir, "dgi", "dgi.keytab");
        httpKeytabFile = createKeytab(kdc, kdcWorkDir, "HTTP", "spnego.service.keytab");

        // create a test user principal
        kdc.createPrincipal(TESTUSER, TESTPASS);

        String jaas = "TestUser {\n" +
                "    com.sun.security.auth.module.Krb5LoginModule required\nuseTicketCache=true;\n" +
                "};\n" +
                createJAASEntry("Client", "dgi", userKeytabFile) +
                createJAASEntry("Server", "HTTP", httpKeytabFile);

        File jaasFile = new File(kdcWorkDir, "jaas.txt");
        FileUtils.write(jaasFile, jaas);
        bindJVMtoJAASFile(jaasFile);
    }

    protected Subject loginTestUser() throws LoginException, IOException {
        LoginContext lc = new LoginContext(TEST_USER_JAAS_SECTION, new CallbackHandler() {
            @Override
            public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
                for (Callback callback : callbacks) {
                    if (callback instanceof PasswordCallback) {
                        PasswordCallback passwordCallback = (PasswordCallback) callback;
                        passwordCallback.setPassword(TESTPASS.toCharArray());
                    }
                    if (callback instanceof NameCallback) {
                        NameCallback nameCallback = (NameCallback) callback;
                        nameCallback.setName(TESTUSER);
                    }
                }
            }
        });

        // attempt authentication
        lc.login();

        return lc.getSubject();
    }

    protected String generateKerberosTestProperties() throws Exception {
        PropertiesConfiguration props = new PropertiesConfiguration();
        props.setProperty("atlas.http.authentication.enabled", "true");
        props.setProperty("atlas.http.authentication.type", "kerberos");
        props.setProperty("atlas.http.authentication.kerberos.principal", "HTTP/localhost@" + kdc.getRealm());
        props.setProperty("atlas.http.authentication.kerberos.keytab", httpKeytabFile.getAbsolutePath());
        props.setProperty("atlas.http.authentication.kerberos.name.rules",
                "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\nDEFAULT");

        return writeConfiguration(props);
    }

    class TestEmbeddedServer extends EmbeddedServer {
        public TestEmbeddedServer(int port, String path) throws IOException {
            super(ATLAS_DEFAULT_BIND_ADDRESS, port, path);
        }

        @Override
        protected WebAppContext getWebAppContext(String path) {
            WebAppContext application = new WebAppContext(path, "/");
            application.setDescriptor(System.getProperty("projectBaseDir") + "/webapp/src/test/webapp/WEB-INF/web.xml");
            application.setClassLoader(Thread.currentThread().getContextClassLoader());
            return application;
        }

        Server getServer() {
            return server;
        }
    }
}
