content: /*
   * 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;
          }
      }
  }
  