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

com.netflix.spinnaker.fiat.roles.BaseUserRolesProvider Maven / Gradle / Ivy

There is a newer version: 1.54.1
Show newest version
/*
 * Copyright 2021 Salesforce.com, 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.roles;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.netflix.spinnaker.fiat.config.UserRolesProviderCacheConfig;
import com.netflix.spinnaker.fiat.model.resources.Role;
import com.netflix.spinnaker.fiat.permissions.ExternalUser;
import com.netflix.spinnaker.fiat.providers.ProviderException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** UserRolesProvider base implementation class providing caching for user roles. */
public abstract class BaseUserRolesProvider implements UserRolesProvider {

  /** Cache loader implementation consistent with UserRolesProvider. */
  class BaseUserRolesProviderCacheLoader extends CacheLoader> {
    @Override
    public Collection load(final @NotNull String userId) {
      return loadRolesForUser(new ExternalUser().setId(userId));
    }

    @Override
    public Map> loadAll(@NotNull Iterable userIds) {
      final List externalUsers =
          ImmutableList.copyOf(userIds).stream()
              .map(userId -> new ExternalUser().setId(userId))
              .collect(Collectors.toList());
      return loadRolesForUsers(externalUsers);
    }
  }

  private final Logger log = LoggerFactory.getLogger(getClass());

  protected boolean cacheEnabled;
  protected LoadingCache> loadingCache;

  public final void setProviderCacheConfig(final UserRolesProviderCacheConfig cacheConfig) {
    final boolean enableCache = cacheConfig != null && cacheConfig.isEnabled();
    final long expireAfterWriteSeconds =
        enableCache ? cacheConfig.getExpireAfterWriteSeconds() : 0L;

    // Build the cache.
    this.loadingCache = enableCache ? buildCache(expireAfterWriteSeconds) : null;
    // Expose the cache for usage.
    this.cacheEnabled = enableCache;

    log.info(
        "Caching status: enabled = {}, expireAfterWriteSeconds = {}",
        enableCache,
        expireAfterWriteSeconds);
  }

  private LoadingCache> buildCache(final long expireAfterWriteSeconds) {
    return CacheBuilder.newBuilder()
        .expireAfterWrite(expireAfterWriteSeconds, TimeUnit.SECONDS)
        .build(new BaseUserRolesProviderCacheLoader());
  }

  @Override
  public List loadRoles(final ExternalUser user) {
    try {
      if (cacheEnabled) {
        // Responses by default stored as a collection in the cache to support the UserRolesProvider
        // interface.
        // If already a list, avoid duplicate list creation, else, return a new list.
        final Collection roles = loadingCache.get(user.getId());
        return roles instanceof List ? (List) roles : new ArrayList<>(roles);
      } else {
        return loadRolesForUser(user);
      }
    } catch (ExecutionException | UncheckedExecutionException e) {
      if (e.getCause() instanceof ProviderException) {
        throw (ProviderException) e.getCause();
      }
      throw new ProviderException(this.getClass(), e.getCause());
    }
  }

  /**
   * Helper method to encapsulate shared logic for converting list of users to ids for interacting
   * with the cache.
   *
   * @param users Users to convert to ids.
   * @return List of ids, one per user.
   */
  private List convertExternalUsersToUserIds(final Iterable users) {
    return StreamSupport.stream(users.spliterator(), false)
        .map(ExternalUser::getId)
        .collect(Collectors.toList());
  }

  @Override
  public Map> multiLoadRoles(final Collection users) {
    try {
      if (cacheEnabled) {
        // Convert the cached response to a mutable map object.
        // By default, Guava cache getAll() responses are returned as an immutable map.
        return new HashMap<>(loadingCache.getAll(convertExternalUsersToUserIds(users)));
      } else {
        return loadRolesForUsers(users);
      }
    } catch (ExecutionException | UncheckedExecutionException e) {
      if (e.getCause() instanceof ProviderException) {
        throw (ProviderException) e.getCause();
      }
      throw new ProviderException(this.getClass(), e.getCause());
    }
  }

  /**
   * Loads roles for a single user to the cache. This method must be implemented in order to support
   * cache loading functionality.
   *
   * @param user User for which to load roles.
   * @return List of roles for the user.
   * @throws ProviderException if there is an exception loading roles.
   */
  protected abstract List loadRolesForUser(final ExternalUser user) throws ProviderException;

  /**
   * Loads roles for multiple users to the cache. This method provides a default bulk cache load
   * implementation which simply loads the cache one user entry at a time using {@link
   * #loadRolesForUser}. This method should be overridden when bulk retrieval is significantly more
   * efficient than many individual lookups.
   *
   * @param users Users for which to load roles.
   * @return Mapping of user id to roles.
   * @throws ProviderException if there is an exception loading roles.
   */
  protected Map> loadRolesForUsers(final Collection users)
      throws ProviderException {
    return users.stream().collect(Collectors.toMap(ExternalUser::getId, this::loadRolesForUser));
  }

  /** Invalidates a single user entry in the cache. */
  public void invalidate(final ExternalUser user) {
    if (cacheEnabled) {
      loadingCache.invalidate(user.getId());
    }
  }

  /** Invalidates multiple user entries in the cache. */
  public void invalidate(final Iterable users) {
    if (cacheEnabled) {
      loadingCache.invalidateAll(convertExternalUsersToUserIds(users));
    }
  }

  /** Invalidates all user entries in the cache. */
  public void invalidateAll() {
    if (cacheEnabled) {
      loadingCache.invalidateAll();
    }
  }

  /** Returns {@code true} if caching is enabled. */
  protected boolean checkCacheEnabled() {
    return cacheEnabled;
  }

  /**
   * Returns the number of active entries in the cache. This method is intended for testing purposes
   * only as a cache clean up is manually invoked prior to the size calculation. If caching is not
   * enabled then returns -1.
   *
   * @return Size of the cache or -1 if caching is not enabled.
   */
  protected long size() {
    if (cacheEnabled) {
      loadingCache.cleanUp();
      return loadingCache.size();
    } else {
      return -1L;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy