All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.spinnaker.fiat.permissions.DefaultPermissionsResolver Maven / Gradle / Ivy

/*
 * Copyright 2016 Google, Inc.
 *
 * Licensed 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 com.netflix.spinnaker.fiat.permissions;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.netflix.spinnaker.fiat.config.AccountManagerConfig;
import com.netflix.spinnaker.fiat.config.FiatAdminConfig;
import com.netflix.spinnaker.fiat.config.UnrestrictedResourceConfig;
import com.netflix.spinnaker.fiat.model.UserPermission;
import com.netflix.spinnaker.fiat.model.resources.Resource;
import com.netflix.spinnaker.fiat.model.resources.Role;
import com.netflix.spinnaker.fiat.model.resources.ServiceAccount;
import com.netflix.spinnaker.fiat.providers.ProviderException;
import com.netflix.spinnaker.fiat.providers.ResourceProvider;
import com.netflix.spinnaker.fiat.roles.UserRolesProvider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class DefaultPermissionsResolver implements PermissionsResolver {

  private final UserRolesProvider userRolesProvider;
  private final ResourceProvider serviceAccountProvider;
  private final ImmutableList> resourceProviders;
  private final FiatAdminConfig fiatAdminConfig;
  private final AccountManagerConfig accountManagerConfig;
  private final ObjectMapper mapper;

  @Autowired
  public DefaultPermissionsResolver(
      UserRolesProvider userRolesProvider,
      ResourceProvider serviceAccountProvider,
      List> resourceProviders,
      FiatAdminConfig fiatAdminConfig,
      AccountManagerConfig accountManagerConfig,
      @Qualifier("objectMapper") ObjectMapper mapper) {
    this.userRolesProvider = userRolesProvider;
    this.serviceAccountProvider = serviceAccountProvider;
    this.resourceProviders = ImmutableList.copyOf(resourceProviders);
    this.fiatAdminConfig = fiatAdminConfig;
    this.accountManagerConfig = accountManagerConfig;
    this.mapper = mapper;
  }

  @Override
  public UserPermission resolveUnrestrictedUser() {
    return getUserPermission(
        UnrestrictedResourceConfig.UNRESTRICTED_USERNAME,
        new HashSet<>(userRolesProvider.loadUnrestrictedRoles()));
  }

  @Override
  public UserPermission resolve(@NonNull String userId) {
    return resolveAndMerge(new ExternalUser().setId(userId));
  }

  @Override
  public UserPermission resolveAndMerge(@NonNull ExternalUser user) {
    List roles;
    try {
      log.debug("Loading roles for user " + user);
      roles = userRolesProvider.loadRoles(user);
      log.debug("Got roles " + roles + " for user " + user);
    } catch (ProviderException pe) {
      throw new PermissionResolutionException(
          "Failed to resolve user permission for user " + user.getId(), pe);
    }
    Set combo =
        Stream.concat(roles.stream(), user.getExternalRoles().stream()).collect(Collectors.toSet());

    return getUserPermission(user.getId(), combo);
  }

  @Override
  public void clearCache() {
    for (ResourceProvider provider : resourceProviders) {
      provider.clearCache();
    }
  }

  private boolean hasAdminRole(Set roles) {
    Set adminRoles = Set.copyOf(fiatAdminConfig.getAdmin().getRoles());
    Set userRoles = roles.stream().map(Role::getName).collect(Collectors.toSet());
    return !Collections.disjoint(adminRoles, userRoles);
  }

  private boolean hasAccountManagerRole(Set roles) {
    Set accountManagerRoles = Set.copyOf(accountManagerConfig.getRoles());
    Set userRoles = roles.stream().map(Role::getName).collect(Collectors.toSet());
    return !Collections.disjoint(accountManagerRoles, userRoles);
  }

  @SuppressWarnings("unchecked")
  private UserPermission getUserPermission(String userId, Set roles) {
    UserPermission permission =
        new UserPermission()
            .setId(userId)
            .setRoles(roles)
            .setAdmin(hasAdminRole(roles))
            .setAccountManager(hasAccountManagerRole(roles));

    for (ResourceProvider provider : resourceProviders) {
      try {
        if (UnrestrictedResourceConfig.UNRESTRICTED_USERNAME.equalsIgnoreCase(userId)) {
          permission.addResources(provider.getAllUnrestricted());
        }

        if (!roles.isEmpty()) {
          permission.addResources(provider.getAllRestricted(userId, roles, permission.isAdmin()));
        }
      } catch (ProviderException pe) {
        throw new PermissionResolutionException(
            String.format(
                "permission resolution failed from provider %s",
                provider.getClass().getSimpleName()),
            pe);
      }
    }
    return permission;
  }

  @Override
  public Map resolve(@NonNull Collection users) {
    Map> allServiceAccountRoles = getServiceAccountRoles();

    Collection serviceAccounts =
        users.stream()
            .filter(user -> allServiceAccountRoles.containsKey(user.getId()))
            .collect(Collectors.toList());

    // Service accounts should already have external roles set. Remove them from the list so they
    // are not sent to the RoleProvider for fetching roles.
    users.removeAll(serviceAccounts);

    Map> userToRoles = new HashMap<>();

    if (!users.isEmpty()) {
      userToRoles.putAll(getAndMergeUserRoles(users));
    }

    userToRoles.putAll(
        serviceAccounts.stream()
            .collect(Collectors.toMap(ExternalUser::getId, ExternalUser::getExternalRoles)));

    return resolveResources(userToRoles);
  }

  private Map> getServiceAccountRoles() {
    return serviceAccountProvider.getAll().stream()
        .map(ServiceAccount::toUserPermission)
        .collect(Collectors.toMap(UserPermission::getId, UserPermission::getRoles));
  }

  private Map> getAndMergeUserRoles(
      @NonNull Collection users) {
    Map> userToRoles = userRolesProvider.multiLoadRoles(users);

    users.forEach(
        user ->
            userToRoles
                .computeIfAbsent(user.getId(), ignored -> new ArrayList<>())
                .addAll(user.getExternalRoles()));

    if (log.isDebugEnabled()) {
      try {
        log.debug(
            "Multi-loaded roles: \n"
                + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(userToRoles));
      } catch (Exception e) {
        log.debug("Exception writing roles", e);
      }
    }
    return userToRoles;
  }

  @Override
  public Map resolveResources(
      @NonNull Map> userToRoles) {
    return userToRoles.entrySet().stream()
        .map(
            entry -> {
              final String userId = entry.getKey();
              final Set userRoles = new HashSet<>(entry.getValue());
              final boolean isAdmin = hasAdminRole(userRoles);

              return new UserPermission()
                  .setId(userId)
                  .setRoles(userRoles)
                  .setAdmin(isAdmin)
                  .setAccountManager(hasAccountManagerRole(userRoles))
                  .addResources(getResources(userId, userRoles, isAdmin));
            })
        .collect(Collectors.toMap(UserPermission::getId, Function.identity()));
  }

  private Set getResources(String userId, Set userRoles, boolean isAdmin) {
    return resourceProviders.stream()
        .flatMap(
            provider -> {
              try {
                return provider.getAllRestricted(userId, userRoles, isAdmin).stream();
              } catch (ProviderException pe) {
                throw new PermissionResolutionException(
                    String.format(
                        "resource lookup failed from provider %s",
                        provider.getClass().getSimpleName()),
                    pe);
              }
            })
        .collect(Collectors.toSet());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy