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.ambari.server.security.authorization;
  
  import java.util.Collection;
  import java.util.EnumSet;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  import org.apache.ambari.server.orm.dao.PrivilegeDAO;
  import org.apache.ambari.server.orm.dao.ViewInstanceDAO;
  import org.apache.ambari.server.orm.entities.PermissionEntity;
  import org.apache.ambari.server.orm.entities.PrivilegeEntity;
  import org.apache.ambari.server.orm.entities.ResourceEntity;
  import org.apache.ambari.server.orm.entities.RoleAuthorizationEntity;
  import org.apache.ambari.server.security.authentication.AmbariProxiedUserDetailsImpl;
  import org.apache.ambari.server.security.authentication.AmbariUserDetails;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.security.core.Authentication;
  import org.springframework.security.core.GrantedAuthority;
  import org.springframework.security.core.context.SecurityContext;
  import org.springframework.security.core.context.SecurityContextHolder;
  import org.springframework.web.context.request.RequestAttributes;
  import org.springframework.web.context.request.RequestContextHolder;
  import org.springframework.web.context.request.ServletRequestAttributes;
  
  import com.google.common.collect.Lists;
  import com.google.inject.Inject;
  import com.google.inject.Provider;
  import com.google.inject.Singleton;
  
  /**
   * Provides utility methods for authentication functionality
   */
  @Singleton
  public class AuthorizationHelper {
    private final static Logger LOG = LoggerFactory.getLogger(AuthorizationHelper.class);
  
    @Inject
    static Provider<PrivilegeDAO> privilegeDAOProvider;
  
    @Inject
    static Provider<ViewInstanceDAO> viewInstanceDAOProvider;
  
    /**
     * Gets the name of the logged-in proxy user, if any.
     *
     * @param authentication
     * @return the name of the logged-in proxy user
     */
    public static String getProxyUserName(Authentication authentication) {
      if (authentication==null){
        return null;
      }
      Object userDetails = authentication.getPrincipal();
      if (userDetails instanceof AmbariProxiedUserDetailsImpl) {
        AmbariProxiedUserDetailsImpl ambariProxiedUserDetails = (AmbariProxiedUserDetailsImpl) userDetails;
        return ambariProxiedUserDetails.getProxyUserDetails().getUsername();
      }
      return null;
    }
  
    /**
     * Gets the name of the logged-in proxy user, if any.
     *
     * @return the name of the logged-in proxy user
     */
    public static String getProxyUserName() {
      SecurityContext securityContext = SecurityContextHolder.getContext();
      Authentication auth = securityContext.getAuthentication();
      return getProxyUserName(auth);
    }
  
    /**
     * Converts collection of RoleEntities to collection of GrantedAuthorities
     */
    public Collection<GrantedAuthority> convertPrivilegesToAuthorities(Collection<PrivilegeEntity> privilegeEntities) {
      Set<GrantedAuthority> authorities = new HashSet<>(privilegeEntities.size());
  
      for (PrivilegeEntity privilegeEntity : privilegeEntities) {
        authorities.add(new AmbariGrantedAuthority(privilegeEntity));
      }
  
      return authorities;
    }
  
    /**
     * Gets the name of the logged in user.  Thread-safe due to use of thread-local.
     *
     * @return the name of the logged in user, or <code>null</code> if none set.
     */
    public static String getAuthenticatedName() {
      return getAuthenticatedName(null);
    }
  
    /**
     * Gets the name of the logged-in user, if any.  Thread-safe due to use of
     * thread-local.
     *
     * @param defaultUsername the value if there is no logged-in user
     * @return the name of the logged-in user, or the default
     */
    public static String getAuthenticatedName(String defaultUsername) {
      SecurityContext securityContext = SecurityContextHolder.getContext();
  
      Authentication auth = securityContext.getAuthentication();
  
      return (null == auth) ? defaultUsername : auth.getName();
    }
  
    /**
     * Gets the ID of the logged-in user.  Thread-safe due to use of
     * thread-local.
     *
     * @return the ID of the logged-in user
     */
    public static int getAuthenticatedId() {
      SecurityContext securityContext = SecurityContextHolder.getContext();
  
      Authentication authentication = securityContext.getAuthentication();
      Object principal = (authentication == null) ? null : authentication.getPrincipal();
  
      if (principal instanceof AmbariUserDetails) {
        return ((AmbariUserDetails) principal).getUserId();
      }
  
      return -1;
    }
  
    /**
     * Determines if the authenticated user (from application's security context) is authorized to
     * perform an operation on the specific resource by matching the authenticated user's
     * authorizations with the one indicated.
     *
     * @param resourceType          a resource type being acted upon
     * @param resourceId            the privilege resource id (or adminresource.id) of the relevant resource
     * @param requiredAuthorization the required authorization
     * @return true if authorized; otherwise false
     * @see #isAuthorized(Authentication, ResourceType, Long, Set)
     */
    public static boolean isAuthorized(ResourceType resourceType, Long resourceId,
                                       RoleAuthorization requiredAuthorization) {
      return isAuthorized(getAuthentication(), resourceType, resourceId, EnumSet.of(requiredAuthorization));
    }
  
    /**
     * Determines if the authenticated user (from application's security context) is authorized to
     * perform an operation on the specific resource by matching the authenticated user's
     * authorizations with one from the provided set of authorizations.
     *
     * @param resourceType           a resource type being acted upon
     * @param resourceId             the privilege resource id (or adminresource.id) of the relevant resource
     * @param requiredAuthorizations a set of requirements for which one match will allow authorization
     * @return true if authorized; otherwise false
     * @see #isAuthorized(Authentication, ResourceType, Long, Set)
     */
    public static boolean isAuthorized(ResourceType resourceType, Long resourceId,
                                       Set<RoleAuthorization> requiredAuthorizations) {
      return isAuthorized(getAuthentication(), resourceType, resourceId, requiredAuthorizations);
    }
  
    /**
     * Determines if the specified authenticated user is authorized to perform an operation on the
     * specific resource by matching the authenticated user's authorizations with the one indicated.
     *
     * @param authentication        the authenticated user and associated access privileges
     * @param resourceType          a resource type being acted upon
     * @param resourceId            the privilege resource id (or adminresource.id) of the relevant resource
     * @param requiredAuthorization the required authorization
     * @return true if authorized; otherwise false
     * @see #isAuthorized(Authentication, ResourceType, Long, Set)
     */
    public static boolean isAuthorized(Authentication authentication, ResourceType resourceType,
                                       Long resourceId, RoleAuthorization requiredAuthorization) {
      return isAuthorized(authentication, resourceType, resourceId, EnumSet.of(requiredAuthorization));
    }
  
    /**
     * Determines if the specified authenticated user is authorized to perform an operation on the
     * the specific resource by matching the authenticated user's authorizations with one from the
     * provided set of authorizations.
     * <p/>
     * The specified resource type is a high-level resource such as {@link ResourceType#AMBARI Ambari},
     * a {@link ResourceType#CLUSTER cluster}, or a {@link ResourceType#VIEW view}.
     * <p/>
     * The specified resource id is the (admin)resource id referenced by a specific resource instance
     * such as a cluster or view.
     *
     * @param authentication         the authenticated user and associated access privileges
     * @param resourceType           a resource type being acted upon
     * @param resourceId             the privilege resource id (or adminresource.id) of the relevant resource
     * @param requiredAuthorizations a set of requirements for which one match will allow authorization
     * @return true if authorized; otherwise false
     */
    public static boolean isAuthorized(Authentication authentication, ResourceType resourceType,
                                       Long resourceId, Set<RoleAuthorization> requiredAuthorizations) {
      if ((requiredAuthorizations == null) || requiredAuthorizations.isEmpty()) {
        return true;
      } else if (authentication == null) {
        return false;
      } else {
        // Iterate through the set of required authorizations to see if at least one match is found.
        // If the user has at least one authorization that exists in the set of required authorizations,
        // that user is authorized to perform the operation.
        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
          if (grantedAuthority instanceof AmbariGrantedAuthority) {
            AmbariGrantedAuthority ambariGrantedAuthority = (AmbariGrantedAuthority) grantedAuthority;
            PrivilegeEntity privilegeEntity = ambariGrantedAuthority.getPrivilegeEntity();
            ResourceEntity privilegeResource = privilegeEntity.getResource();
            ResourceType privilegeResourceType = ResourceType.translate(privilegeResource.getResourceType().getName());
            boolean resourceOK;
  
            if (ResourceType.AMBARI == privilegeResourceType) {
              // This resource type indicates administrative access
              resourceOK = true;
            } else if ((resourceType == null) || (resourceType == privilegeResourceType)) {
              resourceOK = (resourceId == null) || resourceId.equals(privilegeResource.getId());
            } else {
              resourceOK = false;
            }
  
            // The the authority is for the relevant resource, see if one of the authorizations matches
            // one of the required authorizations...
            if (resourceOK) {
              PermissionEntity permission = privilegeEntity.getPermission();
              Collection<RoleAuthorizationEntity> userAuthorizations = (permission == null)
                  ? null
                  : permission.getAuthorizations();
  
              if (userAuthorizations != null) {
                for (RoleAuthorizationEntity userAuthorization : userAuthorizations) {
                  try {
                    if (requiredAuthorizations.contains(RoleAuthorization.translate(userAuthorization.getAuthorizationId()))) {
                      return true;
                    }
                  } catch (IllegalArgumentException e) {
                    LOG.warn("Invalid authorization name, '{}'... ignoring.", userAuthorization.getAuthorizationId());
                  }
                }
              }
            }
          }else {
              LOG.warn("GrantedAuthority is not an instance of AmbariGrantedAuthority. Ignoring.");
          }
        }
  
        return false;
      }
    }
  
    /**
     * Determines if the authenticated user (from application's security context) is authorized to
     * perform an operation on the the specific resource by matching the authenticated user's
     * authorizations with one from the provided set of authorizations.
     * <p/>
     * If not authorized, an {@link AuthorizationException} will be thrown.
     *
     * @param resourceType           a resource type being acted upon
     * @param resourceId             the privilege resource id (or adminresource.id) of the relevant resource
     * @param requiredAuthorizations a set of requirements for which one match will allow authorization
     * @throws AuthorizationException if authorization is not granted
     * @see #isAuthorized(ResourceType, Long, Set)
     */
    public static void verifyAuthorization(ResourceType resourceType,
                                           Long resourceId,
                                           Set<RoleAuthorization> requiredAuthorizations)
        throws AuthorizationException {
      if (!isAuthorized(resourceType, resourceId, requiredAuthorizations)) {
        throw new AuthorizationException();
      }
    }
  
    /**
     * Determines if the specified authenticated user is authorized to perform an operation on the
     * the specific resource by matching the authenticated user's authorizations with one from the
     * provided set of authorizations.
     * <p/>
     * If not authorized, an {@link AuthorizationException} will be thrown.
     *
     * @param authentication         the authenticated user and associated access privileges
     * @param resourceType           a resource type being acted upon
     * @param resourceId             the privilege resource id (or adminresource.id) of the relevant resource
     * @param requiredAuthorizations a set of requirements for which one match will allow authorization
     * @throws AuthorizationException if authorization is not granted
     * @see #isAuthorized(Authentication, ResourceType, Long, Set)
     */
    public static void verifyAuthorization(Authentication authentication,
                                           ResourceType resourceType,
                                           Long resourceId,
                                           Set<RoleAuthorization> requiredAuthorizations)
        throws AuthorizationException {
      if (!isAuthorized(authentication, resourceType, resourceId, requiredAuthorizations)) {
        throw new AuthorizationException();
      }
    }
  
    /**
     * Retrieves the authenticated user and authorization details from the application's security context.
     *
     * @return the authenticated user and associated access privileges; or null if not available
     */
    public static Authentication getAuthentication() {
      SecurityContext context = SecurityContextHolder.getContext();
      return (context == null) ? null : context.getAuthentication();
    }
  
    /**
     * There are cases when users log-in with a login name that is
     * define in LDAP and which do not correspond to the user name stored
     * locally in ambari. These external login names act as an alias to
     * ambari users name. This method stores in the current http session a mapping
     * of alias user name to local ambari user name to make possible resolving
     * login alias to ambari user name.
     * @param ambariUserName ambari user name for which the alias is to be stored in the session
     * @param loginAlias The Name with which user logged in Ambari UI.
     */
    public static void addLoginNameAlias(String ambariUserName, String loginAlias) {
      ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      if (attr != null) {
        LOG.info("Adding login alias '{}' for user name '{}'", loginAlias, ambariUserName);
        attr.setAttribute(loginAlias, ambariUserName, RequestAttributes.SCOPE_SESSION);
        //save Vice Versa Too
        attr.setAttribute(ambariUserName, loginAlias, RequestAttributes.SCOPE_SESSION);
      }
    }
  
    /**
     * Looks up the provided loginAlias in the current http session and return the ambari
     * user name that the alias is defined for.
     * @param loginAlias the login alias to resolve to ambari user name
     * @return the ambari user name if the alias is found otherwise returns the passed in loginAlias
     */
    public static String resolveLoginAliasToUserName(String loginAlias) {
      ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      if (attr != null && attr.getAttribute(loginAlias, RequestAttributes.SCOPE_SESSION) != null) {
        return (String)attr.getAttribute(loginAlias, RequestAttributes.SCOPE_SESSION);
      }
  
      return loginAlias;
    }
  
    /**
     * Retrieve authorization names based on the details of the authenticated user
     * @param authentication the authenticated user and associated access privileges
     * @return human readable role authorizations
     */
    public static List<String> getAuthorizationNames(Authentication authentication) {
      List<String> authorizationNames = Lists.newArrayList();
      if (authentication.getAuthorities() != null) {
        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
          AmbariGrantedAuthority ambariGrantedAuthority = (AmbariGrantedAuthority) grantedAuthority;
  
          PrivilegeEntity privilegeEntity = ambariGrantedAuthority.getPrivilegeEntity();
          Collection<RoleAuthorizationEntity> roleAuthorizationEntities =
            privilegeEntity.getPermission().getAuthorizations();
          for (RoleAuthorizationEntity entity : roleAuthorizationEntities) {
            authorizationNames.add(entity.getAuthorizationName());
          }
        }
      }
      return authorizationNames;
    }
  
  }
  