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.security.authentication;
  
  import alluxio.RuntimeConstants;
  import alluxio.conf.AlluxioConfiguration;
  import alluxio.conf.PropertyKey;
  import alluxio.util.CommonUtils;
  
  import com.google.common.base.Splitter;
  import com.google.common.collect.Sets;
  
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;
  import java.util.regex.Matcher;
  import javax.annotation.concurrent.ThreadSafe;
  import javax.security.sasl.AuthenticationException;
  
  /**
   * An authenticator for impersonation users. This determines if a connection user (the user
   * making the connection) is configured to impersonate as a separate impersonation user. To
   * enable impersonation for a particular connection user, the configuration parameter templates
   * {@link PropertyKey.Template#MASTER_IMPERSONATION_USERS_OPTION} and/or
   * {@link PropertyKey.Template#MASTER_IMPERSONATION_GROUPS_OPTION} must be used to specify the
   * users and groups the connection user is allowed to impersonate.
   */
  @ThreadSafe
  public final class ImpersonationAuthenticator {
    public static final String WILDCARD = "*";
    private static final Splitter SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
    // Maps users configured for impersonation to the set of groups which they can impersonate.
    private final Map<String, Set<String>> mImpersonationGroups;
    // Maps users configured for impersonation to the set of users which they can impersonate.
    private final Map<String, Set<String>> mImpersonationUsers;
  
    private AlluxioConfiguration mConfiguration;
  
    /**
     * Constructs a new {@link ImpersonationAuthenticator}.
     *
     * Note the constructor for this object is expensive. Take care with how many of these are
     * instantiated.
     *
     * @param conf conf Alluxio configuration
     */
    public ImpersonationAuthenticator(AlluxioConfiguration conf) {
      mImpersonationGroups = new HashMap<>();
      mImpersonationUsers = new HashMap<>();
      mConfiguration = conf;
  
      for (PropertyKey key : conf.keySet()) {
        // Process impersonation groups
        Matcher matcher =
            PropertyKey.Template.MASTER_IMPERSONATION_GROUPS_OPTION.match(key.getName());
        if (matcher.matches()) {
          String connectionUser = matcher.group(1);
          String value = conf.getOrDefault(key, null);
          if (connectionUser != null) {
            mImpersonationGroups.put(connectionUser, Sets.newHashSet(SPLITTER.split(value)));
          }
        }
  
        // Process impersonation users
        matcher = PropertyKey.Template.MASTER_IMPERSONATION_USERS_OPTION.match(key.getName());
        if (matcher.matches()) {
          String connectionUser = matcher.group(1);
          String value = conf.getOrDefault(key, null);
          if (connectionUser != null) {
            mImpersonationUsers.put(connectionUser, Sets.newHashSet(SPLITTER.split(value)));
          }
        }
      }
    }
  
    /**
     * @param connectionUser the user of the connection
     * @param impersonationUser the user to impersonate
     * @throws AuthenticationException if the connectionUser is not allowed to impersonate the
     *         impersonationUser
     */
    public void authenticate(String connectionUser, String impersonationUser)
        throws AuthenticationException {
      if (impersonationUser == null || connectionUser.equals(impersonationUser)) {
        // Impersonation is not being used.
        return;
      }
  
      Set<String> allowedUsers = mImpersonationUsers.get(connectionUser);
      Set<String> allowedGroups = mImpersonationGroups.get(connectionUser);
  
      if (allowedUsers == null && allowedGroups == null) {
        throw new AuthenticationException(String.format(
            "Failed to authenticate client user=\"%s\" connecting to Alluxio server and "
                + "impersonating as impersonationUser=\"%s\" to access Alluxio file system. "
                + "User \"%s\" is not configured to allow any impersonation. "
                + "Please read the guide to configure impersonation at %s",
            connectionUser, impersonationUser,
            connectionUser, RuntimeConstants.ALLUXIO_SECURITY_DOCS_URL));
      }
  
      // Check the impersonation users configs
      if (allowedUsers != null) {
        if (allowedUsers.contains(WILDCARD) || allowedUsers.contains(impersonationUser)) {
          // Impersonation is allowed
          return;
        }
      }
  
      // Check the impersonation groups configs
      if (allowedGroups != null) {
        if (allowedGroups.contains(WILDCARD)) {
          // Impersonation is allowed for all groups
          return;
        }
        try {
          for (String impersonationGroup : CommonUtils.getGroups(impersonationUser, mConfiguration)) {
            if (allowedGroups.contains(impersonationGroup)) {
              // Impersonation is allowed for this group
              return;
            }
          }
        } catch (IOException e) {
          throw new AuthenticationException(String.format(
              "Failed to authenticate client user=\"%s\" connecting to Alluxio master and "
                  + "impersonating as impersonationUser=\"%s\" to access Alluxio file system: "
                  + "Failed to get groups that impersonationUser=\"%s\" belongs to.",
              connectionUser, impersonationUser, impersonationUser), e);
        }
      }
      throw new AuthenticationException(String.format(
          "Failed to authenticate client user=\"%s\" connecting to Alluxio master and "
              + "impersonating as impersonationUser=\"%s\" to access Alluxio file system. "
              + "user=\"%s\" is not configured to impersonate as impersonationUser=\"%s\"."
              + "Please read the guide to configure impersonation at %s",
          connectionUser, impersonationUser, connectionUser, impersonationUser,
          RuntimeConstants.ALLUXIO_SECURITY_DOCS_URL));
    }
  }
  