package io.dropwizard.auth;

import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.caffeine.MetricsStatsCounter;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import com.github.benmanes.caffeine.cache.stats.StatsCounter;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.util.Sets;

import javax.annotation.Nullable;
import javax.ws.rs.container.ContainerRequestContext;
import java.security.Principal;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.codahale.metrics.MetricRegistry.name;

An Authorizer decorator which uses a Caffeine cache to temporarily cache principals' role associations.

Cache entries include both inclusion and exclusion of a principal within a given role.

Type parameters:
  • <P> – the type of principals on which the authorizer operates
/** * An {@link Authorizer} decorator which uses a {@link Caffeine} cache to * temporarily cache principals' role associations. * <p> * Cache entries include both inclusion and exclusion of a principal * within a given role. * * @param <P> the type of principals on which the authorizer operates */
public class CachingAuthorizer<P extends Principal> implements Authorizer<P> { private final Authorizer<P> underlying; private final Meter cacheMisses; private final Timer getsTimer; // A cache which maps (principal, role, uriInfo) to boolean // authorization states. // // A cached value of `true` indicates that the key's principal is // authorized to assume the given role. False values indicate the // principal is not authorized to assume the role. // // `null` cache values are interpreted as cache misses, and will // thus result in read through to the underlying `Authorizer`. // // Field is package-private to be visible for unit tests @VisibleForTesting final LoadingCache<AuthorizationContext<P>, Boolean> cache;
Creates a new cached authorizer.
Params:
  • metricRegistry – the application's registry of metrics
  • authorizer – the underlying authorizer
  • cacheSpec – CaffeineSpec
/** * Creates a new cached authorizer. * * @param metricRegistry the application's registry of metrics * @param authorizer the underlying authorizer * @param cacheSpec {@link CaffeineSpec} */
public CachingAuthorizer( final MetricRegistry metricRegistry, final Authorizer<P> authorizer, final CaffeineSpec cacheSpec) { this(metricRegistry, authorizer, Caffeine.from(cacheSpec)); }
Creates a new cached authorizer.
Params:
  • metricRegistry – the application's registry of metrics
  • authorizer – the underlying authorizer
  • builder – a CaffeineSpec
/** * Creates a new cached authorizer. * * @param metricRegistry the application's registry of metrics * @param authorizer the underlying authorizer * @param builder a {@link CaffeineSpec} */
public CachingAuthorizer( final MetricRegistry metricRegistry, final Authorizer<P> authorizer, final Caffeine<Object, Object> builder) { this(metricRegistry, authorizer, builder, () -> new MetricsStatsCounter(metricRegistry, name(CachingAuthorizer.class))); }
Creates a new cached authorizer.
Params:
  • metricRegistry – the application's registry of metrics
  • authorizer – the underlying authorizer
  • builder – a Caffeine spec
  • supplier – a Supplier<StatsCounter>
/** * Creates a new cached authorizer. * * @param metricRegistry the application's registry of metrics * @param authorizer the underlying authorizer * @param builder a {@link Caffeine} spec * @param supplier a {@link Supplier<StatsCounter>} */
public CachingAuthorizer( final MetricRegistry metricRegistry, final Authorizer<P> authorizer, final Caffeine<Object, Object> builder, final Supplier<StatsCounter> supplier) { this.underlying = authorizer; this.cacheMisses = metricRegistry.meter(name(authorizer.getClass(), "cache-misses")); this.getsTimer = metricRegistry.timer(name(authorizer.getClass(), "gets")); this.cache = builder .recordStats(supplier) .build(key -> { cacheMisses.mark(); return underlying.authorize(key.getPrincipal(), key.getRole(), key.getRequestContext()); }); } @Override public boolean authorize(P principal, String role) { return authorize(principal, role, null); } @Override public boolean authorize(P principal, String role, @Nullable ContainerRequestContext requestContext) { try (Timer.Context context = getsTimer.time()) { final AuthorizationContext<P> cacheKey = new AuthorizationContext<>(principal, role, requestContext); final Boolean result = cache.get(cacheKey); return result == null ? false : result; } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw e; } }
Discards any cached role associations for the given principal and role.
Params:
  • principal –
  • role –
  • requestContext –
/** * Discards any cached role associations for the given principal and role. * * @param principal * @param role * @param requestContext */
public void invalidate(P principal, String role, ContainerRequestContext requestContext) { cache.invalidate(new AuthorizationContext<>(principal, role, requestContext)); }
Discards any cached role associations for the given principal.
Params:
  • principal –
/** * Discards any cached role associations for the given principal. * * @param principal */
public void invalidate(P principal) { final Set<AuthorizationContext<P>> keys = cache.asMap().keySet().stream() .filter(cacheKey -> cacheKey.getPrincipal().equals(principal)) .collect(Collectors.toSet()); cache.invalidateAll(keys); }
Discards any cached role associations for the given collection of principals.
Params:
  • principals – a list of principals
/** * Discards any cached role associations for the given collection * of principals. * * @param principals a list of principals */
public void invalidateAll(Iterable<P> principals) { final Set<P> principalSet = Sets.of(principals); final Set<AuthorizationContext<P>> keys = cache.asMap().keySet().stream() .filter(cacheKey -> principalSet.contains(cacheKey.getPrincipal())) .collect(Collectors.toSet()); cache.invalidateAll(keys); }
Discards any cached role associations for principals satisfying the given predicate.
Params:
  • predicate – a predicate to filter credentials
/** * Discards any cached role associations for principals satisfying * the given predicate. * * @param predicate a predicate to filter credentials */
public void invalidateAll(Predicate<? super P> predicate) { final Set<AuthorizationContext<P>> keys = cache.asMap().keySet().stream() .filter(cacheKey -> predicate.test(cacheKey.getPrincipal())) .collect(Collectors.toSet()); cache.invalidateAll(keys); }
Discards all cached role associations.
/** * Discards all cached role associations. */
public void invalidateAll() { cache.invalidateAll(); }
Returns the number of principals for which there are cached role associations.
Returns:the number of cached principals
/** * Returns the number of principals for which there are cached * role associations. * * @return the number of cached principals */
public long size() { return cache.estimatedSize(); }
Returns a set of statistics about the cache contents and usage.
Returns:a set of statistics about the cache contents and usage
/** * Returns a set of statistics about the cache contents and usage. * * @return a set of statistics about the cache contents and usage */
public CacheStats stats() { return cache.stats(); } }