/************************************************************************* * Copyright 2009-2016 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. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.PersistenceException; import org.apache.log4j.Logger; import com.eucalyptus.auth.AuthContext; import com.eucalyptus.auth.AuthEvaluationContext; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.AuthQuotaException; import com.eucalyptus.auth.Permissions; import com.eucalyptus.auth.PolicyEvaluationContext; import com.eucalyptus.auth.PolicyEvaluationWriteContextKey; import com.eucalyptus.auth.PolicyResourceContext; import com.eucalyptus.auth.policy.annotation.PolicyAction; import com.eucalyptus.auth.policy.annotation.PolicyResourceType; import com.eucalyptus.auth.policy.PolicySpec; import com.eucalyptus.auth.principal.AccountFullName; import com.eucalyptus.auth.principal.OwnerFullName; import com.eucalyptus.auth.principal.PolicyVersion; import com.eucalyptus.auth.principal.Principal.PrincipalType; import com.eucalyptus.auth.principal.Principals; import com.eucalyptus.auth.principal.TypedPrincipal; import com.eucalyptus.auth.principal.UserFullName; import com.eucalyptus.auth.principal.UserPrincipal; import com.eucalyptus.auth.type.LimitedType; import com.eucalyptus.auth.type.RestrictedType; import com.eucalyptus.bootstrap.ServiceJarDiscovery; import com.eucalyptus.component.ComponentId; import com.eucalyptus.component.annotation.ComponentMessage; import com.eucalyptus.auth.policy.annotation.PolicyVendor; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.context.IllegalContextAccessException; import com.eucalyptus.records.Logs; import com.eucalyptus.system.Ats; import com.eucalyptus.system.Threads; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Interner; import com.google.common.collect.Interners; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import edu.ucsb.eucalyptus.msgs.BaseMessage; import com.eucalyptus.auth.AuthContextSupplier; import static com.eucalyptus.auth.PolicyResourceContext.PolicyResourceInfo; import static com.eucalyptus.util.Parameters.checkParam; import static com.eucalyptus.auth.type.RestrictedType.AccountRestrictedType; import static com.eucalyptus.auth.type.RestrictedType.PolicyRestrictedType; import static com.eucalyptus.auth.type.RestrictedType.UserRestrictedType; import static org.hamcrest.Matchers.notNullValue; public class RestrictedTypes { static Logger LOG = Logger.getLogger( RestrictedTypes.class ); private static final Interner<AllocationScope> allocationInterner = Interners.newWeakInterner( ); private static final TypedKey<PrincipalType> principalTypeKey = TypedKey.create( "PrincipalType" ); private static final TypedKey<String> principalNameKey = TypedKey.create( "PrincipalName" ); public static final PolicyEvaluationWriteContextKey<PrincipalType> principalTypeContextKey = PolicyEvaluationWriteContextKey.create( principalTypeKey ); public static final PolicyEvaluationWriteContextKey<String> principalNameContextKey = PolicyEvaluationWriteContextKey.create( principalNameKey ); /** * Map request to policy language's action string. * * @param request The request message * @return The IAM ARN action string. */ public static String requestToAction( BaseMessage request ) { if ( request != null ) { PolicyAction action = Ats.from( request ).get( PolicyAction.class ); if ( action != null ) { return action.action( ); } } return null; } /** * Annotation for use on a Class implementing Function<String,T extends RestrictedType>, * that is, one which converts a string reference into a type reference for the object T * referenced by {@code identifier}. * <p> * {@code public abstract T apply( String identifier );} * </p> * * <p> * The method should: * <ul> * <li>return T the object referenced by the given {@code identifier}</li> * <li>throw PersistenceException if an error occurred in the underlying retrieval mechanism</li> * <li>throw NoSuchElementException if the requested {@code identifier} does not exist and the * user is authorized.</li> * </ul> * </p> */ @Target( { ElementType.TYPE } ) @Retention( RetentionPolicy.RUNTIME ) public @interface Resolver { Class<?> value( ); } private static final Map<Class, Function<?, ?>> resourceResolvers = Maps.newHashMap(); @SuppressWarnings( "unchecked" ) public static <T extends RestrictedType> Function<String, T> resolver( Class<T> type ) { return ( Function<String, T> ) checkMapByType( type, resourceResolvers ); } /** * Implementations <strong>measure</strong> the quantity of {@code T}, the <i>resource type</i>, * currently ascribed to a user, via {@link com.eucalyptus.auth.principal.OwnerFullName}. In other words, types annotated with * this encapsulate a service and resource-specific method for computing the current * {@code quantity} of {@code resource type} ascribed to {@code ownerFullName}. */ @Target( { ElementType.TYPE } ) @Retention( RetentionPolicy.RUNTIME ) public @interface UsageMetricFunction { Class<?> value( ); } private static final Map<Class, Function<?, ?>> usageMetricFunctions = Maps.newHashMap( ); @SuppressWarnings( "unchecked" ) public static Function<OwnerFullName, Long> usageMetricFunction( Class type ) { return ( Function<OwnerFullName, Long> ) checkMapByType( type, usageMetricFunctions ); } @Target( { ElementType.TYPE } ) @Retention( RetentionPolicy.RUNTIME ) public @interface QuantityMetricFunction { Class<?> value( ); } private static final Map<Class, Function<?, ?>> quantityMetricFunctions = Maps.newHashMap( ); @SuppressWarnings( "unchecked" ) public static Function<OwnerFullName, Long> quantityMetricFunction( Class type ) { return ( Function<OwnerFullName, Long> ) checkMapByType( type, quantityMetricFunctions ); } private static Function<?, ?> checkMapByType( Class type, Map<Class, Function<?, ?>> map ) { for ( Class subType : Classes.ancestors( type ) ) { if ( map.containsKey( subType ) ) { return map.get( subType ); } } throw new NoSuchElementException( "Failed to lookup function (@" + Threads.currentStackFrame( 1 ).getMethodName( ) + ") for type: " + type ); } /** * Allocate {@code quantity} unitless resources, correctly rolling their allocation back in the * case of partial failures. */ private static <T> List<T> runAllocator( int quantity, Supplier<T> allocator, Predicate<T> rollback ) { List<T> res = Lists.newArrayList( ); try { for ( int i = 0; i < quantity; i++ ) { T rsc = allocator.get( ); if ( rsc == null ) { throw new NoSuchElementException( "Attempt to allocate " + quantity + " resources failed." ); } res.add( rsc ); } } catch ( Exception ex ) { for ( T rsc : res ) { try { rollback.apply( rsc ); } catch ( Exception ex1 ) { LOG.trace( ex1, ex1 ); } } if ( ex.getCause( ) != null ) { throw Exceptions.toUndeclared( ex.getCause( ) ); } else { throw Exceptions.toUndeclared( ex ); } } return res; } /** * Special case of allocating a single countable resource. * * {@inheritDoc RestrictedTypes#allocateCountableResources(Integer, Supplier)} * * @see RestrictedTypes#allocateUnitlessResources(Integer, Supplier) */ @SuppressWarnings( { "cast", "unchecked" } ) public static <T extends LimitedType> T allocateUnitlessResource( Supplier<T> allocator ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return allocateUnitlessResources( 1, allocator ).get( 0 ); } /** * Allocation of a dimension-less type; i.e. only the quantity matters and the characteristics of * the allocated resource cannot be parameterized in any way. * * @param <T> type to be allocated * @param quantity quantity to be allocated * @param allocator Supplier which performs allocation of a single unit. * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ @SuppressWarnings( { "cast", "unchecked" } ) public static <T extends LimitedType> List<T> allocateUnitlessResources( Integer quantity, Supplier<T> allocator ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return allocateUnitlessResources( findResourceClass( allocator ), quantity, allocator ); } /** * Allocation of a dimension-less type; i.e. only the quantity matters and the characteristics of * the allocated resource cannot be parameterized in any way. * * @param <T> type to be allocated * @param rscType class for type to be allocated * @param quantity quantity to be allocated * @param allocator Supplier which performs allocation of a single unit. * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ @SuppressWarnings( { "cast", "unchecked" } ) public static <T extends LimitedType> List<T> allocateUnitlessResources( final Class<?> rscType, final Integer quantity, final Supplier<T> allocator ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return doAllocate( false, rscType, quantity, null, new Supplier<List<T>>() { @Override public List<T> get( ) { return runAllocator( quantity, allocator, ( Predicate ) Predicates.alwaysTrue( ) ); } } ); } /** * Allocation of a dimension-less type; i.e. only the quantity matters and the characteristics of * the allocated resource cannot be parameterized in any way. * * @param <T> type to be allocated * @param rscType class for type to be allocated * @param min minimum quantity to be allocated * @param max maximum quantity to be allocated * @param allocator Supplier which performs allocation of a single unit. * @param example Example resource * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ @SuppressWarnings( { "cast", "unchecked" } ) public static <T extends LimitedType> List<T> allocateUnitlessResources( final Class<?> rscType, final int min, final int max, final BatchAllocator<T> allocator, final RestrictedType example ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return doAllocate( false, rscType, max, example, new Supplier<List<T>>( ){ @Override public List<T> get() { return allocator.allocate( min, max ); } }); } /** * Reallocation of a dimension-less type; i.e. only the quantity matters and the characteristics of * the allocated resource cannot be parameterized in any way. * * Assumes permission check is elsewhere, only handles quotas * * @param <T> type to be allocated * @param rscType class for type to be allocated * @param allocator Supplier which performs allocation of a single unit. * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ @SuppressWarnings( { "cast", "unchecked" } ) public static <T extends LimitedType> List<T> reallocateUnitlessResource( final Class<?> rscType, final BatchAllocator<T> allocator ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return doAllocate( true, rscType, 1, null, new Supplier<List<T>>( ){ @Override public List<T> get() { return allocator.allocate( 1, 1 ); } }); } private static <A> A doAllocate( final boolean skipAuth, final Class<?> rscType, final Integer quantity, final Object example, final Supplier<A> allocator ) throws AuthException, IllegalContextAccessException { String identifier = ""; Context ctx = Contexts.lookup( ); if ( !ctx.hasAdministrativePrivileges( ) ) { Ats ats = findPolicyAnnotations( rscType ); PolicyVendor vendor = ats.get( PolicyVendor.class ); PolicyResourceType type = ats.get( PolicyResourceType.class ); String action = getIamActionByMessageType(); String qualifiedAction = PolicySpec.qualifiedName( vendor.value( ), action ); AuthContextSupplier userContext = ctx.getAuthContext( ); try ( final PolicyResourceContext context = example == null ? PolicyResourceContext.of( ctx.getAccountNumber( ), rscType, qualifiedAction ) : PolicyResourceContext.of( example, qualifiedAction ) ) { if ( !skipAuth && !Permissions.isAuthorized( vendor.value( ), type.value( ), identifier, null, action, userContext )){ throw new AuthException( "Not authorized to create: " + type.value( ) + " by user: " + ctx.getUserFullName( ) ); } final Lock lock = allocationInterner.intern( new AllocationScope( vendor.value( ), type.value( ), userContext.get( ).getAccountNumber( ) ) ).lock( ); lock.lock( ); try { if ( !Permissions.canAllocate( vendor.value( ), type.value( ), identifier, action, userContext, (long) quantity ) ) { throw new AuthQuotaException( type.value( ), "Quota exceeded while trying to create: " + type.value( ) + " by user: " + ctx.getUserFullName( ) ); } return allocator.get( ); } finally { lock.unlock( ); } } } else { return allocator.get( ); } } /** * Allocate a resource and subsequently verify naming restrictions. * * @see RestrictedTypes#allocateUnitlessResources(Integer, Supplier) */ @SuppressWarnings( "ConstantConditions" ) public static <T extends RestrictedType> List<T> allocateNamedUnitlessResources( Integer quantity, Supplier<T> allocator, Predicate<T> rollback ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { Context ctx = Contexts.lookup( ); Class<?> rscType = findResourceClass( allocator ); if ( ctx.hasAdministrativePrivileges( ) ) { return runAllocator( quantity, allocator, rollback ); // may throw RuntimeException } else { Ats ats = findPolicyAnnotations( rscType ); PolicyVendor vendor = ats.get( PolicyVendor.class ); PolicyResourceType type = ats.get( PolicyResourceType.class ); String action = getIamActionByMessageType(); AuthContextSupplier userContext = ctx.getAuthContext( ); List<T> res = Lists.newArrayList( ); for ( int i = 0; i < quantity; i++ ) { T rsc = null; try { rsc = allocator.get( ); } catch ( RuntimeException ex1 ) { if ( rsc != null ) { rollback.apply( rsc ); } throw ex1; } if ( rsc == null ) { throw new NoSuchElementException( "Attempt to allocate " + quantity + " " + type + " failed." ); } try ( final PolicyResourceContext context = PolicyResourceContext.of( ctx.getAccountNumber( ), rscType, PolicySpec.qualifiedName( vendor.value( ), action ) ) ) { String identifier = rsc.getDisplayName( ); if ( !Permissions.isAuthorized( vendor.value( ), type.value( ), identifier, null, action, userContext ) ) { throw new AuthException( "Not authorized to create: " + type.value() + " by user: " + ctx.getUserFullName( ) ); } else if ( !Permissions.canAllocate( vendor.value( ), type.value( ), identifier, action, userContext, ( long ) quantity ) ) { throw new AuthQuotaException( type.value( ), "Quota exceeded while trying to create: " + type.value() + " by user: " + ctx.getUserFullName( ) ); } } catch ( AuthException ex ) { if ( rsc != null ) { rollback.apply( rsc ); } throw ex; } res.add( rsc ); } return res; } } /** * Allocation of a type which requires dimensional parameters (e.g., size of a volume) where * {@code amount} indicates the desired value for the measured dimensional parameter. * * @param <T> type to be allocated * @param amount amount to be allocated * @param allocator Supplier which performs allocation of a single unit. * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ public static <T extends LimitedType> T allocateMeasurableResource( final Long amount, final Function<Long, T> allocator ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return allocateMeasurableResource( amount, allocator, null ); } /** * Allocation of a type which requires dimensional parameters (e.g., size of a volume) where * {@code amount} indicates the desired value for the measured dimensional parameter. * * @param <T> type to be allocated * @param amount amount to be allocated * @param allocator Supplier which performs allocation of a single unit. * @param example Example resource * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ public static <T extends LimitedType> T allocateMeasurableResource( final Long amount, final Function<Long, T> allocator, final T example ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { Context ctx = Contexts.lookup( ); if ( !ctx.hasAdministrativePrivileges( ) ) { String action = getIamActionByMessageType( ctx.getRequest( ) ); return allocateMeasurableResource( ctx.getAuthContext( ), ctx.getUserFullName( ), action, amount, allocator, example ); } else { return allocator.apply( amount ); } } /** * Allocation of a type which requires dimensional parameters (e.g., size of a volume) where * {@code amount} indicates the desired value for the measured dimensional parameter. * * @param <T> type to be allocated * @param userContext The authorization context to use, * @param userDescription Description of the principal related to the allocation * @param action The unqualified API action related to the allocation * @param amount amount to be allocated * @param allocator Supplier which performs allocation of a single unit. * @param example Example resource * @return List<T> of size {@code quantity} of new allocations of {@code <T>} */ public static <T extends LimitedType> T allocateMeasurableResource( final AuthContextSupplier userContext, final UserFullName userDescription, final String action, final Long amount, final Function<Long, T> allocator, final T example ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { String identifier = ""; final AuthContext authContext = userContext.get( ); if ( !authContext.isSystemAdmin( ) ) { final Class<?> rscType = findResourceClass( allocator ); Ats ats = findPolicyAnnotations( rscType ); PolicyVendor vendor = ats.get( PolicyVendor.class ); PolicyResourceType type = ats.get( PolicyResourceType.class ); final String qualifiedAction = PolicySpec.qualifiedName( vendor.value( ), action ); try ( final PolicyResourceContext context = example == null ? PolicyResourceContext.of( authContext.getAccountNumber( ), rscType, qualifiedAction ) : PolicyResourceContext.of( example, qualifiedAction ) ) { if ( RestrictedType.class.isAssignableFrom( rscType ) && !Permissions.isAuthorized( vendor.value( ), type.value( ), identifier, null, action, userContext ) ) { throw new AuthException( "Not authorized to create: " + type.value( ) + " by user: " + userDescription ); } else if ( !Permissions.canAllocate( vendor.value( ), type.value( ), identifier, action, userContext, amount ) ) { throw new AuthQuotaException( type.value( ), "Quota exceeded while trying to create: " + type.value( ) + " by user: " + userDescription ); } } } return allocator.apply( amount ); } @SuppressWarnings( { "cast", "unchecked" } ) public static <T extends RestrictedType> T doPrivileged( String identifier, Class<T> type ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return doPrivileged( identifier, ( Function<String, T> ) checkMapByType( type, resourceResolvers ) ); } @SuppressWarnings( "rawtypes" ) public static <T extends RestrictedType> T doPrivileged( String identifier, Function<String, T> resolverFunction ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return doPrivileged( identifier, resolverFunction, false ); } /** * Check access permission without regard for resource ownership. * * This check should only be used for resources that are public or that have * an additional permission check applied (for example EC2 images can be * shared between accounts) * * @see #doPrivileged(String, Class) */ @SuppressWarnings( "rawtypes" ) public static <T extends RestrictedType> T doPrivilegedWithoutOwner( String identifier, Function<? super String, T> resolverFunction ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { return doPrivileged( identifier, resolverFunction, true ); } /** * Uses the provided {@code lookupFunction} to resolve the {@code identifier} to the underlying * object {@code T} with privileges determined by the current messaging context. * * @param <T> type of object which needs looking up * @param identifier identifier of the desired object * @param resolverFunction class which resolves string identifiers to the underlying object * @return the object corresponding with the given {@code identifier} * @throws AuthException if the user is not authorized * @throws PersistenceException if an error occurred in the underlying retrieval mechanism * @throws NoSuchElementException if the requested {@code identifier} does not exist and the user * is authorized. * @throws IllegalContextAccessException if the current request context cannot be determined. */ @SuppressWarnings( "rawtypes" ) private static <T extends RestrictedType> T doPrivileged( final String identifier, final Function<? super String, T> resolverFunction, final boolean ignoreOwningAccount ) throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { checkParam( "Resolver function must be not null: " + identifier, resolverFunction, notNullValue() ); Context ctx = Contexts.lookup( ); if ( ctx.hasAdministrativePrivileges( ) ) { return resolverFunction.apply( identifier ); } else { Class<? extends BaseMessage> msgType = ctx.getRequest( ).getClass( ); LOG.debug( "Attempting to lookup " + identifier + " using lookup: " + resolverFunction.getClass( ) + " typed as " + Classes.genericsToClasses( resolverFunction ) ); Class<?> rscType = findResourceClass( resolverFunction ); Ats ats = findPolicyAnnotations( rscType ); PolicyVendor vendor = ats.get( PolicyVendor.class ); PolicyResourceType type = ats.get( PolicyResourceType.class ); String action = getIamActionByMessageType( ); String actionVendor = findPolicyVendor( msgType ); AuthContextSupplier authContextSupplier = ctx.getAuthContext( ); UserPrincipal requestUser = ctx.getUser( ); Map<String,String> evaluatedKeys = ctx.evaluateKeys( ); T requestedObject; try { requestedObject = resolverFunction.apply( identifier ); if ( requestedObject == null ) { throw new NoSuchElementException( "Failed to lookup requested " + rscType.getCanonicalName( ) + " with id " + identifier + " using " + resolverFunction.getClass( ) ); } } catch ( NoSuchElementException ex ) { throw ex; } catch ( PersistenceException ex ) { Logs.extreme( ).error( ex, ex ); LOG.error( ex ); throw ex; } catch ( Exception ex ) { Logs.extreme( ).error( ex, ex ); LOG.error( ex ); throw new PersistenceException( "Error occurred while attempting to lookup " + identifier + " using lookup: " + resolverFunction.getClass( ) + " typed as " + rscType, ex ); } final PolicyEvaluationContext policyEvaluationContext = PolicyEvaluationContext.get( ); final Set<TypedPrincipal> principals; if ( policyEvaluationContext.hasAttribute( principalTypeKey ) && policyEvaluationContext.hasAttribute( principalNameKey ) ) { final PrincipalType principalType = policyEvaluationContext.getAttribute( principalTypeKey ); final String principalName = policyEvaluationContext.getAttribute( principalNameKey ); principals = ImmutableSet.of( TypedPrincipal.of( principalType, principalName ) ); } else { principals = Principals.typedSet( requestUser ); } final AccountFullName objectOwnerAccount = AccountFullName.getInstance( requestedObject.getOwner( ).getAccountNumber( ) ); AccountFullName owningAccount = null; if ( !ignoreOwningAccount ) { owningAccount = Principals.nobodyFullName( ).getAccountNumber( ).equals( requestedObject.getOwner( ).getAccountNumber( ) ) ? null : objectOwnerAccount; } final String qualifiedAction = PolicySpec.qualifiedName( actionVendor, action ); //noinspection unused try ( final PolicyResourceContext policyResourceContext = PolicyResourceContext.of( requestedObject, qualifiedAction ) ) { if ( !Permissions.isAuthorized( principals, findPolicy( requestedObject, actionVendor, action ), objectOwnerAccount, PolicySpec.qualifiedName( vendor.value( ), type.value( ) ), identifier, owningAccount, qualifiedAction, requestUser, authContextSupplier.get( ).getPolicies( ), evaluatedKeys ) ) { throw new AuthException( "Not authorized to use " + type.value( ) + " identified by " + identifier + " as the user " + UserFullName.getInstance( requestUser ) ); } } return requestedObject; } } public static <T extends RestrictedType> CompatPredicate<T> filterPrivileged( ) { return filterPrivileged( false, ContextSupplier.INSTANCE ); } /** * Check access permission without regard for resource ownership. * * This check should only be used for resources that are public or that have * an additional permission check applied (for example EC2 images can be * shared between accounts) * * @see #filterPrivileged */ public static <T extends RestrictedType> CompatPredicate<T> filterPrivilegedWithoutOwner( ) { return filterPrivileged( true, ContextSupplier.INSTANCE ); } public static <T extends RestrictedType> CompatFunction<T, String> toDisplayName( ) { return new CompatFunction<T, String>( ) { @Override public String apply( T arg0 ) { return arg0 == null ? null : arg0.getDisplayName( ); } }; } public static <T extends RestrictedType> CompatPredicate<T> filterById( final Collection<String> requestedIdentifiers ) { return filterByProperty( requestedIdentifiers, toDisplayName() ); } public static <T extends RestrictedType> CompatPredicate<T> filterByProperty( final String requestedValue, final Function<? super T,String> extractor ) { return filterByProperty( CollectionUtils.<String>listUnit().apply( requestedValue ), extractor ); } public static <T extends RestrictedType> CompatPredicate<T> filterByProperty( final Collection<String> values, final Function<? super T,String> extractor ) { return new CompatPredicate<T>( ) { final ImmutableList<String> requestedValues = values == null ? null : ImmutableList.copyOf( values ); @Override public boolean apply( T input ) { return requestedValues == null || requestedValues.isEmpty( ) || requestedValues.contains( extractor.apply( input ) ); } }; } public static <T extends RestrictedType> CompatPredicate<T> filterByOwningAccount( final Collection<String> identifiers ) { return new CompatPredicate<T>( ) { final ImmutableList<String> requestedIdentifiers = identifiers == null ? null : ImmutableList.copyOf( identifiers ); @Override public boolean apply( T input ) { return requestedIdentifiers == null || requestedIdentifiers.isEmpty( ) || requestedIdentifiers.contains( input.getOwner( ).getAccountNumber( ) ); } }; } public static <T extends RestrictedType> FilterBuilder<T> filteringFor( final Class<T> metadataClass ) { return new FilterBuilder<T>(metadataClass ); } /* * Please, ignoreOwningAccount here is necessary. Consult me first before making any changes. * -- Ye Wen (wenye@eucalyptus.com) */ private static <T extends RestrictedType> CompatPredicate<T> filterPrivileged( final boolean ignoreOwningAccount, final Function<? super Class<?>, AuthEvaluationContext> contextFunction ) { return new CompatPredicate<T>( ) { @SuppressWarnings( { "ConstantConditions", "unused" } ) @Override public boolean apply( T arg0 ) { Context ctx = Contexts.lookup( ); if ( !ctx.hasAdministrativePrivileges( ) ) { try { String owningAccountNumber = null; if ( !ignoreOwningAccount ) { owningAccountNumber = Principals.nobodyFullName( ).getAccountNumber( ).equals( arg0.getOwner( ).getAccountNumber( ) ) ? null : arg0.getOwner( ).getAccountNumber( ); } final AuthEvaluationContext evaluationContext = contextFunction.apply( arg0.getClass( ) ); try ( final PolicyResourceContext policyResourceContext = PolicyResourceContext.of( arg0, evaluationContext.getAction( ) ) ) { return Permissions.isAuthorized( evaluationContext, owningAccountNumber, arg0.getDisplayName() ); } } catch ( Exception ex ) { return false; } } return true; } }; } private enum ContextSupplier implements Function<Class<?>, AuthEvaluationContext> { INSTANCE; @Override public AuthEvaluationContext apply( final Class<?> rscType ) { try { final Context ctx = Contexts.lookup(); final Ats ats = findPolicyAnnotations( rscType ); final PolicyVendor vendor = ats.get( PolicyVendor.class ); final PolicyResourceType type = ats.get( PolicyResourceType.class ); final String action = getIamActionByMessageType( ); return ctx.getAuthContext( ).get( ).evaluationContext( vendor.value( ), type.value( ), action ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } /** * Filter by account and possibly user. * * <p>If the given owner is null the returned predicate will always match.</p> */ @Nonnull public static Predicate<AccountRestrictedType> filterByOwner( @Nullable final OwnerFullName owner ) { return owner == null ? Predicates.<AccountRestrictedType>alwaysTrue() : Predicates.<AccountRestrictedType>and( filterByAccount( owner.getAccountNumber() ), typeSafeFilterByUser( owner.getUserId() ) ); } @Nonnull public static Predicate<AccountRestrictedType> filterByAccount( @Nonnull final String accountNumber ) { return new Predicate<AccountRestrictedType>() { @Override public boolean apply( @Nullable final AccountRestrictedType restricted ) { return restricted == null || accountNumber.equals( restricted.getOwnerAccountNumber() ); } }; } @Nonnull public static Predicate<UserRestrictedType> filterByUser( @Nonnull final String userId ) { return new Predicate<UserRestrictedType>() { @Override public boolean apply( @Nullable final UserRestrictedType restricted ) { return restricted == null || userId.equals( restricted.getOwnerUserId() ); } }; } @Nonnull private static Predicate<AccountRestrictedType> typeSafeFilterByUser( @Nullable final String userId ) { final Predicate<UserRestrictedType> userFilter = userId == null ? Predicates.<UserRestrictedType>alwaysTrue() : filterByUser( userId ); return new Predicate<AccountRestrictedType>() { @Override public boolean apply( @Nullable final AccountRestrictedType restricted ) { return !(restricted instanceof UserRestrictedType) || userFilter.apply( (UserRestrictedType) restricted ); } }; } @TypeMapper public enum RestrictedTypeToPolicyResourceInfo implements Function<RestrictedType,PolicyResourceInfo> { INSTANCE; @Nullable @Override public PolicyResourceInfo apply( final RestrictedType restrictedType ) { final String accountNumber; if ( restrictedType instanceof UserRestrictedType ) { accountNumber = ( (UserRestrictedType) restrictedType ).getOwnerAccountNumber( ); } else if ( restrictedType instanceof AccountRestrictedType ) { accountNumber = ( (AccountRestrictedType) restrictedType ).getOwnerAccountNumber( ); } else { accountNumber = restrictedType.getOwner( ).getAccountNumber( ); } return PolicyResourceContext.resourceInfo( accountNumber, restrictedType ); } } public static class FilterBuilder<T extends RestrictedType> { private final Class<T> metadataClass; private final List<Predicate<? super T>> predicates = Lists.newArrayList(); private FilterBuilder( final Class<T> metadataClass ) { this.metadataClass = metadataClass; } public FilterBuilder<T> byId( final Collection<String> requestedIdentifiers ) { predicates.add( filterById( requestedIdentifiers ) ); return this; } public <T extends RestrictedType> Predicate<T> filterByProperty( final Collection<String> requestedValues, final Function<? super T,String> extractor ) { return new Predicate<T>( ) { @Override public boolean apply( T input ) { return requestedValues == null || requestedValues.isEmpty() || requestedValues.contains( extractor.apply(input) ); } }; } public FilterBuilder<T> byProperty(final Collection<String> requestedValues, final Function<? super T, String> extractor) { predicates.add(filterByProperty(requestedValues, extractor)); return this; } public FilterBuilder<T> byPrivileges() { predicates.add( RestrictedTypes.filterPrivileged( false, Functions.constant( ContextSupplier.INSTANCE.apply( metadataClass ) ) ) ); return this; } public FilterBuilder<T> byPrivilegesWithoutOwner() { predicates.add( RestrictedTypes.filterPrivileged( true, Functions.constant( ContextSupplier.INSTANCE.apply( metadataClass ) ) ) ); return this; } public FilterBuilder<T> byOwningAccount( final Collection<String> requestedIdentifiers ) { predicates.add( filterByOwningAccount( requestedIdentifiers ) ); return this; } public FilterBuilder<T> byPredicate( final Predicate<? super T> predicate ) { predicates.add( predicate ); return this; } public Predicate<? super T> buildPredicate() { return Predicates.and( predicates ); } } public interface BatchAllocator<T> { List<T> allocate( int min, int max ); } public static class ResourceMetricFunctionDiscovery extends ServiceJarDiscovery { public ResourceMetricFunctionDiscovery( ) { super( ); } @SuppressWarnings( { "synthetic-access", "unchecked" } ) @Override public boolean processClass( Class candidate ) throws Exception { if ( Ats.from( candidate ).has( UsageMetricFunction.class ) && Function.class.isAssignableFrom( candidate ) ) { UsageMetricFunction measures = Ats.from( candidate ).get( UsageMetricFunction.class ); Class<?> measuredType = measures.value( ); LOG.info( "Registered @UsageMetricFunction: " + measuredType.getSimpleName( ) + " => " + candidate ); RestrictedTypes.usageMetricFunctions.put( measuredType, ( Function<OwnerFullName, Long> ) Classes.newInstance( candidate ) ); return true; } else if ( Ats.from( candidate ).has( QuantityMetricFunction.class ) && Function.class.isAssignableFrom( candidate ) ) { QuantityMetricFunction measures = Ats.from( candidate ).get( QuantityMetricFunction.class ); Class<?> measuredType = measures.value( ); LOG.info( "Registered @QuantityMetricFunction: " + measuredType.getSimpleName( ) + " => " + candidate ); RestrictedTypes.quantityMetricFunctions.put( measuredType, ( Function<OwnerFullName, Long> ) Classes.newInstance( candidate ) ); return true; } else if ( Ats.from( candidate ).has( Resolver.class ) && Function.class.isAssignableFrom( candidate ) ) { Resolver resolver = Ats.from( candidate ).get( Resolver.class ); Class<?> resolverFunctionType = resolver.value( ); LOG.info( "Registered @Resolver: " + resolverFunctionType.getSimpleName( ) + " => " + candidate ); RestrictedTypes.resourceResolvers.put( resolverFunctionType, ( Function<String, RestrictedType> ) Classes.newInstance( candidate ) ); return true; } else { return false; } } @Override public Double getPriority( ) { return 0.3d; } } public static String getIamActionByMessageType( ) { return getIamActionByMessageType( Contexts.lookup( ).getRequest( ) ); } public static String getIamActionByMessageType( final BaseMessage request ) { String action = requestToAction( request ); if ( action == null ) { if ( request != null ) { return request.getClass( ).getSimpleName( ).replaceAll( "(ResponseType|Type)$", "" ).toLowerCase( ); } else { return null; } } else { return action; } } private static Class<?> findResourceClass( Object allocator ) throws IllegalArgumentException, NoSuchElementException { List<Class> lookupTypes = Classes.genericsToClasses( allocator ); if ( lookupTypes.isEmpty( ) ) { throw new IllegalArgumentException( "Failed to find required generic type for lookup " + allocator.getClass( ) + " so the policy type for looking up " + allocator + " cannot be determined." ); } Class<?> rscType; try { rscType = Iterables.find( lookupTypes, new Predicate<Class>( ) { @Override public boolean apply( Class arg0 ) { return LimitedType.class.isAssignableFrom( arg0 ); } } ); } catch ( NoSuchElementException ex1 ) { LOG.error( ex1, ex1 ); throw ex1; } return rscType; } private static Ats findPolicyAnnotations( Class<?> rscType ) throws IllegalArgumentException { Ats ats = Ats.inClassHierarchy( rscType ); if ( !ats.has( PolicyVendor.class ) ) { throw new IllegalArgumentException( "Failed to determine policy for allocating type instance " + rscType.getCanonicalName( ) + ": required @PolicyVendor missing in resource type hierarchy" ); } else if ( !ats.has( PolicyResourceType.class ) ) { throw new IllegalArgumentException( "Failed to determine policy for looking up type instance " + rscType.getCanonicalName( ) + ": required @PolicyResourceType missing in resource type hierarchy" ); } return ats; } public static String findPolicyVendor( Class<? extends BaseMessage > msgType ) throws IllegalArgumentException { final Ats ats = Ats.inClassHierarchy( msgType ); if ( ats.has( PolicyVendor.class ) ) { return ats.get( PolicyVendor.class ).value(); } if ( ats.has( PolicyAction.class ) ) { return ats.get( PolicyAction.class ).vendor(); } if ( ats.has( ComponentMessage.class ) ) { final Class<? extends ComponentId> componentIdClass = ats.get( ComponentMessage.class ).value(); final Ats componentAts = Ats.inClassHierarchy( componentIdClass ); if ( componentAts.has( PolicyVendor.class ) ) { return componentAts.get( PolicyVendor.class ).value(); } } throw new IllegalArgumentException( "Failed to determine policy" + ": require @PolicyVendor, @PolicyAction or @ComponentMessage in request type hierarchy " + msgType.getCanonicalName( ) ); } private static PolicyVersion findPolicy( final RestrictedType object, final String vendor, final String action ) throws AuthException { PolicyVersion policy = null; if ( object instanceof PolicyRestrictedType ) { final Ats ats = Ats.inClassHierarchy( object.getClass() ); final PolicyResourceType policyResourceType = ats.get( PolicyResourceType.class ); if ( policyResourceType == null || Lists.newArrayList( policyResourceType.resourcePolicyActions() ).contains( PolicySpec.qualifiedName( vendor, action ).toLowerCase() ) ) { try { policy = ((PolicyRestrictedType) object).getPolicy(); if ( policy == null ) { throw new AuthException( "Policy not found for resource" ); } } catch ( Exception e ) { throw new AuthException( "Error finding policy", e ); } } } return policy; } private static final class AllocationScope { private final String resourceVendor; private final String resourceType; private final String accountNumber; private final Lock lock = new ReentrantLock( ); private AllocationScope( final String resourceVendor, final String resourceType, final String accountNumber ) { Parameters.checkParam( "resourceVendor", resourceVendor, notNullValue( ) ); Parameters.checkParam( "resourceType", resourceType, notNullValue( ) ); Parameters.checkParam( "accountNumber", accountNumber, notNullValue( ) ); this.resourceVendor = resourceVendor; this.resourceType = resourceType; this.accountNumber = accountNumber; } public Lock lock( ) { return lock; } @Override public boolean equals( final Object o ) { if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; final AllocationScope that = (AllocationScope) o; if ( !accountNumber.equals( that.accountNumber ) ) return false; if ( !resourceType.equals( that.resourceType ) ) return false; if ( !resourceVendor.equals( that.resourceVendor ) ) return false; return true; } @Override public int hashCode() { int result = resourceVendor.hashCode(); result = 31 * result + resourceType.hashCode(); result = 31 * result + accountNumber.hashCode(); return result; } public String getResourceVendor( ) { return resourceVendor; } public String getResourceType( ) { return resourceType; } public String getAccountNumber( ) { return accountNumber; } } }