content: /*
   * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
   * (the "License"). You may not use this work except in compliance with the License, which is
   * available at www.apache.org/licenses/LICENSE-2.0
   *
   * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
   * either express or implied, as more fully set forth in the License.
   *
   * See the NOTICE file distributed with this work for information regarding copyright ownership.
   */
  
  package alluxio.server.auth;
  
  import alluxio.AlluxioURI;
  import alluxio.ClientContext;
  import alluxio.client.file.FileSystemMasterClient;
  import alluxio.conf.Configuration;
  import alluxio.conf.PropertyKey;
  import alluxio.exception.status.UnauthenticatedException;
  import alluxio.master.MasterClientContext;
  import alluxio.security.authentication.AuthType;
  import alluxio.security.authentication.AuthenticationProvider;
  import alluxio.security.group.GroupMappingService;
  import alluxio.security.user.TestUserState;
  import alluxio.security.user.UserState;
  import alluxio.testutils.BaseIntegrationTest;
  import alluxio.testutils.LocalAlluxioClusterResource;
  import alluxio.util.FileSystemOptionsUtils;
  
  import org.junit.Assert;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.rules.ExpectedException;
  
  import java.io.IOException;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.util.Collections;
  import java.util.List;
  import javax.security.sasl.AuthenticationException;
  
  /**
   * Though its name indicates that it provides the tests for Alluxio authentication. This class is
   * likely to test four authentication modes: NOSASL, SIMPLE, CUSTOM, KERBEROS.
   */
  // TODO(bin): add tests for {@link MultiMasterLocalAlluxioCluster} in fault tolerant mode
  // TODO(bin): improve the way to set and isolate MasterContext/WorkerContext across test cases
  public final class MasterClientAuthenticationIntegrationTest extends BaseIntegrationTest {
    private static final String SUPERGROUP = "supergroup";
    private static final String NONSUPER = "nonsuper";
    private static final String SUPERUSER = "alluxio";
  
    @Rule
    public LocalAlluxioClusterResource mLocalAlluxioClusterResource =
        new LocalAlluxioClusterResource.Builder()
            .setProperty(PropertyKey.SECURITY_GROUP_MAPPING_CLASS, UserGroupsMapping.class.getName())
            .setProperty(PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_SUPERGROUP, SUPERGROUP)
            .build();
  
    @Rule
    public ExpectedException mThrown = ExpectedException.none();
  
    @Test
    @LocalAlluxioClusterResource.Config(
        confParams = {PropertyKey.Name.SECURITY_AUTHENTICATION_TYPE, "NOSASL",
          PropertyKey.Name.SECURITY_AUTHORIZATION_PERMISSION_ENABLED, "false"})
    public void noAuthenticationOpenClose() throws Exception {
      authenticationOperationTest("/file-nosasl");
    }
  
    @Test
    @LocalAlluxioClusterResource.Config(
        confParams = {PropertyKey.Name.SECURITY_AUTHENTICATION_TYPE, "SIMPLE"})
    public void simpleAuthenticationOpenClose() throws Exception {
      authenticationOperationTest("/file-simple");
    }
  
    @Test
    @LocalAlluxioClusterResource.Config(
        confParams = {PropertyKey.Name.SECURITY_AUTHENTICATION_TYPE, "CUSTOM",
            PropertyKey.Name.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER_CLASS,
            NameMatchAuthenticationProvider.FULL_CLASS_NAME,
            PropertyKey.Name.SECURITY_LOGIN_USERNAME, "alluxio"})
    public void customAuthenticationOpenClose() throws Exception {
      authenticationOperationTest("/file-custom");
    }
  
    @Test
    @LocalAlluxioClusterResource.Config(
        confParams = {PropertyKey.Name.SECURITY_AUTHENTICATION_TYPE, "CUSTOM",
            PropertyKey.Name.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER_CLASS,
            NameMatchAuthenticationProvider.FULL_CLASS_NAME,
            PropertyKey.Name.SECURITY_LOGIN_USERNAME, "alluxio"})
    public void customAuthenticationDenyConnect() throws Exception {
      UserState s = new TestUserState("no-alluxio", Configuration.global());
      try (FileSystemMasterClient masterClient = FileSystemMasterClient.Factory.create(
          MasterClientContext
              .newBuilder(ClientContext.create(s.getSubject(), Configuration.global()))
              .build())) {
        Assert.assertFalse(masterClient.isConnected());
        // Using no-alluxio as loginUser to connect to Master, the IOException will be thrown
        mThrown.expect(UnauthenticatedException.class);
        masterClient.connect();
      }
    }
  
    @Test
    @LocalAlluxioClusterResource.Config(
        confParams = {PropertyKey.Name.SECURITY_AUTHENTICATION_TYPE, "SIMPLE"})
    public void simpleAuthenticationIsolatedClassLoader() throws Exception {
      FileSystemMasterClient masterClient =
          FileSystemMasterClient.Factory.create(MasterClientContext
              .newBuilder(ClientContext.create(Configuration.global())).build());
      Assert.assertFalse(masterClient.isConnected());
  
      // Get the current context class loader to retrieve the classpath URLs.
      ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
      // Set the context class loader to an isolated class loader.
      ClassLoader isolatedClassLoader;
      if (contextClassLoader instanceof URLClassLoader) {
        isolatedClassLoader = new URLClassLoader(((URLClassLoader) contextClassLoader).getURLs(),
            null);
      } else {
        isolatedClassLoader = new URLClassLoader(new URL[0], contextClassLoader);
      }
      Thread.currentThread().setContextClassLoader(isolatedClassLoader);
      try {
        masterClient.connect();
      } finally {
        Thread.currentThread().setContextClassLoader(contextClassLoader);
      }
      Assert.assertTrue(masterClient.isConnected());
      masterClient.close();
    }
  
    /**
     * Tests Alluxio client connects or disconnects to the Master. When the client connects
     * successfully to the Master, it can successfully create file or not.
     *
     * @param filename the name of the file
     */
    private void authenticationOperationTest(String filename) throws Exception {
      UserState s = new TestUserState(SUPERUSER, Configuration.global());
      FileSystemMasterClient masterClient = FileSystemMasterClient.Factory.create(MasterClientContext
          .newBuilder(ClientContext.create(s.getSubject(), Configuration.global())).build());
      Assert.assertFalse(masterClient.isConnected());
      masterClient.connect();
      Assert.assertTrue(masterClient.isConnected());
      masterClient.createFile(new AlluxioURI(filename),
          FileSystemOptionsUtils.createFileDefaults(Configuration.global()));
      Assert.assertNotNull(
          masterClient.getStatus(new AlluxioURI(filename),
              FileSystemOptionsUtils.getStatusDefaults(Configuration.global())));
      masterClient.disconnect();
      masterClient.close();
    }
  
    /**
     * An authentication provider for {@link AuthType#CUSTOM}.
     */
    public static class NameMatchAuthenticationProvider implements AuthenticationProvider {
      // The fullly qualified class name of this authentication provider. This is needed to configure
      // the alluxio cluster
      public static final String FULL_CLASS_NAME =
          "alluxio.server.auth.MasterClientAuthenticationIntegrationTest$"
              + "NameMatchAuthenticationProvider";
  
      @Override
      public void authenticate(String user, String password) throws AuthenticationException {
        if (!user.equals("alluxio")) {
          throw new AuthenticationException("Only allow the user alluxio to connect");
        }
      }
    }
  
    public static class UserGroupsMapping implements GroupMappingService {
      public UserGroupsMapping() {}
  
      @Override
      public List<String> getGroups(String user) throws IOException {
        if (user.equals(SUPERUSER)) {
          return Collections.singletonList(SUPERGROUP);
        }
        return Collections.singletonList(NONSUPER);
      }
    }
  }
  