/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.auth.euare; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.text.IsEmptyString.isEmptyOrNullString; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.AuthenticationProperties; import com.eucalyptus.auth.principal.UserPrincipal; import com.eucalyptus.util.Pair; import com.eucalyptus.util.Parameters; import com.eucalyptus.util.async.AsyncExceptions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilderSpec; /** * */ public class CachingPrincipalProvider extends RegionDelegatingPrincipalProvider { private final static AtomicReference<Pair<String,Cache<PrincipalCacheKey,PrincipalCacheValue>>> cacheReference = new AtomicReference<>( ); @Override public UserPrincipal lookupCachedPrincipalByUserId( final UserPrincipal cached, final String userId, final String nonce ) throws AuthException { return cache( new UserIdPrincipalCacheKey( userId, nonce ), new PrincipalLoader( ) { @Override public UserPrincipal load( final UserPrincipal cached ) throws AuthException { return CachingPrincipalProvider.super.lookupCachedPrincipalByUserId( cached, userId, nonce ); } } ); } @Override public UserPrincipal lookupCachedPrincipalByRoleId( final UserPrincipal cached, final String roleId, final String nonce ) throws AuthException { return cache( new RoleIdPrincipalCacheKey( roleId, nonce ), new PrincipalLoader( ) { @Override public UserPrincipal load( final UserPrincipal cached ) throws AuthException { return CachingPrincipalProvider.super.lookupCachedPrincipalByRoleId( cached, roleId, nonce ); } } ); } @Override public UserPrincipal lookupCachedPrincipalByAccessKeyId( final UserPrincipal cached, final String keyId, final String nonce ) throws AuthException { return cache( new AccessKeyIdPrincipalCacheKey( keyId, nonce ), new PrincipalLoader( ) { @Override public UserPrincipal load( final UserPrincipal cached ) throws AuthException { return CachingPrincipalProvider.super.lookupCachedPrincipalByAccessKeyId( cached, keyId, nonce ); } } ); } @Override public UserPrincipal lookupCachedPrincipalByCertificateId( final UserPrincipal cached, final String certificateId ) throws AuthException { return cache( new CertificateIdPrincipalCacheKey( certificateId ), new PrincipalLoader( ) { @Override public UserPrincipal load( final UserPrincipal cached ) throws AuthException { return CachingPrincipalProvider.super.lookupCachedPrincipalByCertificateId( cached, certificateId ); } } ); } public UserPrincipal lookupCachedPrincipalByAccountNumber( final UserPrincipal cached, final String accountNumber ) throws AuthException { return cache( new AccountNumberPrincipalCacheKey( accountNumber ), new PrincipalLoader( ) { @Override public UserPrincipal load( final UserPrincipal cached ) throws AuthException { return CachingPrincipalProvider.super.lookupCachedPrincipalByAccountNumber( cached, accountNumber ); } } ); } private UserPrincipal cache( final PrincipalCacheKey key, final PrincipalLoader loader ) throws AuthException { PrincipalCacheValue principalValue = null; final Cache<PrincipalCacheKey,PrincipalCacheValue> cache = cache( ); try { principalValue = cache.get( key, loader.callable( null ) ); if ( principalValue.updated + AuthenticationProperties.getAuthorizationExpiry( ) < System.currentTimeMillis( ) ) { cache.invalidate( key ); // invalidate expired and refresh principalValue = cache.get( key, loader.callable( principalValue.principal ) ); } return principalValue.principal; } catch ( final ExecutionException e ) { // reuse cached value on failure within configured limit, but not for web service error responses if ( !AsyncExceptions.asWebServiceError( e ).isPresent( ) && principalValue != null && principalValue.created + AuthenticationProperties.getAuthorizationReuseExpiry( ) > System.currentTimeMillis( ) ) { cache.put( key, new PrincipalCacheValue( principalValue ) ); return principalValue.principal; } if ( e.getCause( ) instanceof AuthException ) { throw (AuthException) e.getCause( ); } else { throw new AuthException( e ); } } } private static Cache<PrincipalCacheKey,PrincipalCacheValue> cache( ) { Cache<PrincipalCacheKey,PrincipalCacheValue> cache; final Pair<String,Cache<PrincipalCacheKey,PrincipalCacheValue>> cachePair = cacheReference.get( ); final String cacheSpec = AuthenticationProperties.AUTHORIZATION_CACHE; if ( cachePair == null || !cacheSpec.equals( cachePair.getLeft( ) ) ) { final Pair<String,Cache<PrincipalCacheKey,PrincipalCacheValue>> newCachePair = Pair.pair( cacheSpec, cache( cacheSpec ) ); if ( cacheReference.compareAndSet( cachePair, newCachePair ) || cachePair == null ) { cache = newCachePair.getRight( ); } else { cache = cachePair.getRight( ); } } else { cache = cachePair.getRight( ); } return cache; } private static Cache<PrincipalCacheKey,PrincipalCacheValue> cache( final String cacheSpec ) { return CacheBuilder .from( CacheBuilderSpec.parse( cacheSpec ) ) .build( ); } private static abstract class PrincipalLoader { abstract UserPrincipal load( UserPrincipal cached ) throws AuthException; Callable<PrincipalCacheValue> callable( final UserPrincipal cached ) { return new Callable<PrincipalCacheValue>( ) { @Override public PrincipalCacheValue call( ) throws AuthException { return new PrincipalCacheValue( load( cached ) ); } }; } } private static final class PrincipalCacheValue { private final long created; private final long updated; @Nonnull private final UserPrincipal principal; public PrincipalCacheValue( @Nonnull final UserPrincipal principal ) { Parameters.checkParam( "principal", principal, notNullValue( ) ); this.created = System.currentTimeMillis( ); this.updated = created; this.principal = principal; } public PrincipalCacheValue( @Nonnull final PrincipalCacheValue value ) { Parameters.checkParam( "value", value, notNullValue( ) ); this.created = value.created; this.updated = System.currentTimeMillis( ); this.principal = value.principal; } } private static abstract class PrincipalCacheKey { @Nonnull private final String identifier; @Nullable private final String nonce; protected PrincipalCacheKey( @Nonnull final String identifier, @Nullable final String nonce ) { Parameters.checkParam( "identifier", identifier, not( isEmptyOrNullString( ) ) ); this.identifier = identifier; this.nonce = nonce; } @SuppressWarnings( "RedundantIfStatement" ) @Override public boolean equals( final Object o ) { if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; final PrincipalCacheKey that = (PrincipalCacheKey) o; if ( !identifier.equals( that.identifier ) ) return false; if ( nonce != null ? !nonce.equals( that.nonce ) : that.nonce != null ) return false; return true; } @Override public int hashCode() { int result = identifier.hashCode(); result = 31 * result + ( nonce != null ? nonce.hashCode() : 0 ); return result; } } private static final class UserIdPrincipalCacheKey extends PrincipalCacheKey { protected UserIdPrincipalCacheKey( @Nonnull final String identifier, @Nullable final String nonce ) { super( identifier, nonce ); } } private static final class RoleIdPrincipalCacheKey extends PrincipalCacheKey { protected RoleIdPrincipalCacheKey( @Nonnull final String identifier, @Nullable final String nonce ) { super( identifier, nonce ); } } private static final class AccessKeyIdPrincipalCacheKey extends PrincipalCacheKey { protected AccessKeyIdPrincipalCacheKey( @Nonnull final String identifier, @Nullable final String nonce ) { super( identifier, nonce ); } } private static final class CertificateIdPrincipalCacheKey extends PrincipalCacheKey { protected CertificateIdPrincipalCacheKey( @Nonnull final String identifier ) { super( identifier, null ); } } private static final class AccountNumberPrincipalCacheKey extends PrincipalCacheKey { protected AccountNumberPrincipalCacheKey( @Nonnull final String identifier ) { super( identifier, null ); } } }