com.eucalyptus.util.RestrictedTypes.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.util.RestrictedTypes.java

Source

/*************************************************************************
 * Copyright 2009-2014 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.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.PersistenceException;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.AuthEvaluationContext;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.AuthQuotaException;
import com.eucalyptus.auth.Permissions;
import com.eucalyptus.auth.policy.PolicyAction;
import com.eucalyptus.auth.policy.PolicyResourceType;
import com.eucalyptus.auth.policy.PolicySpec;
import com.eucalyptus.auth.principal.Account;
import com.eucalyptus.auth.principal.Policy;
import com.eucalyptus.auth.principal.Principal;
import com.eucalyptus.auth.principal.Principals;
import com.eucalyptus.auth.principal.User;
import com.eucalyptus.auth.principal.UserFullName;
import com.eucalyptus.bootstrap.ServiceJarDiscovery;
import com.eucalyptus.component.ComponentId;
import com.eucalyptus.component.annotation.ComponentMessage;
import com.eucalyptus.component.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.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.util.Parameters.checkParam;
import static com.eucalyptus.util.RestrictedType.AccountRestrictedType;
import static com.eucalyptus.util.RestrictedType.PolicyRestrictedType;
import static com.eucalyptus.util.RestrictedType.UserRestrictedType;
import static org.hamcrest.Matchers.notNullValue;

public class RestrictedTypes {
    static Logger LOG = Logger.getLogger(RestrictedTypes.class);

    /**
     * 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();
    private static final List<PolicyResourceInterceptor> resourceInterceptors = Lists.newCopyOnWriteArrayList();

    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 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 RestrictedType> 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 RestrictedType> List<T> allocateUnitlessResources(Integer quantity,
            Supplier<T> allocator)
            throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException {
        String identifier = "";
        Context ctx = Contexts.lookup();
        Class<?> rscType = findResourceClass(allocator);
        if (!ctx.hasAdministrativePrivileges()) {
            Class<? extends BaseMessage> msgType = ctx.getRequest().getClass();
            Ats ats = findPolicyAnnotations(rscType, msgType);
            PolicyVendor vendor = ats.get(PolicyVendor.class);
            PolicyResourceType type = ats.get(PolicyResourceType.class);
            String action = getIamActionByMessageType();
            AuthContextSupplier userContext = ctx.getAuthContext();
            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, ctx.getUser(),
                    (long) quantity)) {
                throw new AuthQuotaException(type.value(), "Quota exceeded while trying to create: " + type.value()
                        + " by user: " + ctx.getUserFullName());
            }
        }
        return runAllocator(quantity, allocator, (Predicate) Predicates.alwaysTrue());
    }

    /**
     * Allocate a resource and subsequently verify naming restrictions.
     * 
     * @see RestrictedTypes#allocateUnitlessResources(Integer, Supplier)
     */
    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 {
            Class<? extends BaseMessage> msgType = ctx.getRequest().getClass();
            Ats ats = findPolicyAnnotations(rscType, msgType);
            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 {
                    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,
                            ctx.getUser(), (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>}
     */
    @SuppressWarnings({ "cast", "unchecked" })
    public static <T extends RestrictedType> T allocateMeasurableResource(Long amount, Function<Long, T> allocator)
            throws AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException {
        String identifier = "";
        Context ctx = Contexts.lookup();
        if (!ctx.hasAdministrativePrivileges()) {
            Class<? extends BaseMessage> msgType = ctx.getRequest().getClass();
            Class<?> rscType = findResourceClass(allocator);
            Ats ats = findPolicyAnnotations(rscType, msgType);
            PolicyVendor vendor = ats.get(PolicyVendor.class);
            PolicyResourceType type = ats.get(PolicyResourceType.class);
            String action = getIamActionByMessageType();
            AuthContextSupplier userContext = ctx.getAuthContext();
            try {
                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, ctx.getUser(),
                        amount)) {
                    throw new AuthQuotaException(type.value(), "Quota exceeded while trying to create: "
                            + type.value() + " by user: " + ctx.getUserFullName());
                }
            } catch (AuthException ex) {
                throw ex;
            }
        }
        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, msgType);
            PolicyVendor vendor = ats.get(PolicyVendor.class);
            PolicyResourceType type = ats.get(PolicyResourceType.class);
            String action = getIamActionByMessageType();
            String actionVendor = findPolicyVendor(msgType);
            User 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 Principal.PrincipalType principalType;
            final String principalName;
            if (Principals.isSameUser(requestUser, Principals.systemUser())) {
                principalType = Principal.PrincipalType.Service;
                principalName = "ec2.amazon.com";
            } else {
                principalType = Principal.PrincipalType.AWS;
                principalName = Accounts.getUserArn(requestUser);
            }

            Account owningAccount = null;
            if (!ignoreOwningAccount) {
                owningAccount = Principals.nobodyFullName().getAccountNumber()
                        .equals(requestedObject.getOwner().getAccountNumber()) ? null
                                : Accounts.lookupAccountById(requestedObject.getOwner().getAccountNumber());
            }

            final String qualifiedAction = PolicySpec.qualifiedName(actionVendor, action);
            notifyResourceInterceptors(requestedObject, qualifiedAction);
            try {
                if (!Permissions.isAuthorized(principalType, principalName,
                        findPolicy(requestedObject, actionVendor, action),
                        PolicySpec.qualifiedName(vendor.value(), type.value()), identifier, owningAccount,
                        qualifiedAction, requestUser, evaluatedKeys)) {
                    throw new AuthException("Not authorized to use " + type.value() + " identified by " + identifier
                            + " as the user " + UserFullName.getInstance(requestUser));
                }
            } finally {
                notifyResourceInterceptors(null, null);
            }
            return requestedObject;
        }
    }

    private static <T extends RestrictedType> void notifyResourceInterceptors(final T requestedObject,
            final String action) {
        for (final PolicyResourceInterceptor interceptor : resourceInterceptors) {
            interceptor.onResource(requestedObject, action);
        }
    }

    public static <T extends RestrictedType> Predicate<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> Predicate<T> filterPrivilegedWithoutOwner() {
        return filterPrivileged(true, ContextSupplier.INSTANCE);
    }

    public static <T extends RestrictedType> Function<T, String> toDisplayName() {
        return new Function<T, String>() {
            @Override
            public String apply(T arg0) {
                return arg0 == null ? null : arg0.getDisplayName();
            }
        };
    }

    public static <T extends RestrictedType> Predicate<T> filterById(
            final Collection<String> requestedIdentifiers) {
        return filterByProperty(requestedIdentifiers, toDisplayName());
    }

    public static <T extends RestrictedType> Predicate<T> filterByProperty(final String requestedValue,
            final Function<? super T, String> extractor) {
        return filterByProperty(CollectionUtils.<String>listUnit().apply(requestedValue), extractor);
    }

    public static <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 static <T extends RestrictedType> Predicate<T> filterByOwningAccount(
            final Collection<String> requestedIdentifiers) {
        return new Predicate<T>() {
            @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> Predicate<T> filterPrivileged(final boolean ignoreOwningAccount,
            final Function<? super Class<?>, AuthEvaluationContext> contextFunction) {
        return new Predicate<T>() {

            @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());
                        notifyResourceInterceptors(arg0, evaluationContext.getAction());
                        return Permissions.isAuthorized(evaluationContext, owningAccountNumber,
                                arg0.getDisplayName());
                    } catch (Exception ex) {
                        return false;
                    } finally {
                        notifyResourceInterceptors(null, null);
                    }
                }
                return true;
            }

        };
    }

    private enum ContextSupplier implements Function<Class<?>, AuthEvaluationContext> {
        INSTANCE;

        @Override
        public AuthEvaluationContext apply(final Class<?> rscType) {
            try {
                final Context ctx = Contexts.lookup();
                final Class<? extends BaseMessage> msgType = ctx.getRequest().getClass();
                final Ats ats = findPolicyAnnotations(rscType, msgType);
                final PolicyVendor vendor = ats.get(PolicyVendor.class);
                final PolicyResourceType type = ats.get(PolicyResourceType.class);
                final String action = getIamActionByMessageType();
                final User requestUser = ctx.getUser();
                final Map<String, String> evaluatedKeys = ctx.evaluateKeys();
                return Permissions.createEvaluationContext(vendor.value(), type.value(), action, requestUser,
                        evaluatedKeys);
            } 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);
            }
        };
    }

    public interface PolicyResourceInterceptor {
        void onResource(RestrictedType resource, String action);
    }

    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 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 class PolicyResourceInterceptorDiscovery extends ServiceJarDiscovery {

        public PolicyResourceInterceptorDiscovery() {
            super();
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean processClass(final Class candidate) throws Exception {
            if (PolicyResourceInterceptor.class.isAssignableFrom(candidate)
                    && !Modifier.isAbstract(candidate.getModifiers())) {
                LOG.info("Registered PolicyResourceInterceptor:    " + candidate);
                RestrictedTypes.resourceInterceptors
                        .add((PolicyResourceInterceptor) Classes.newInstance(candidate));
                return true;
            }

            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 = PolicySpec.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 RestrictedType.class.isAssignableFrom(arg0);
                }
            });
        } catch (NoSuchElementException ex1) {
            LOG.error(ex1, ex1);
            throw ex1;
        }
        return rscType;
    }

    private static Ats findPolicyAnnotations(Class<?> rscType, Class<? extends BaseMessage> msgType)
            throws IllegalArgumentException {
        Ats ats = Ats.inClassHierarchy(rscType);
        Ats msgAts = Ats.inClassHierarchy(msgType);
        if (!ats.has(PolicyVendor.class) && !msgAts.has(PolicyVendor.class)) {
            throw new IllegalArgumentException("Failed to determine policy for allocating type instance "
                    + rscType.getCanonicalName() + ": required @PolicyVendor missing in resource type hierarchy "
                    + rscType.getCanonicalName() + " and request type hierarchy " + msgType.getCanonicalName());
        } else if (!ats.has(PolicyResourceType.class) && !msgAts.has(PolicyResourceType.class)) {
            throw new IllegalArgumentException("Failed to determine policy for looking up type instance "
                    + rscType.getCanonicalName()
                    + ": required @PolicyResourceType missing in resource type hierarchy "
                    + rscType.getCanonicalName() + " and request type hierarchy " + msgType.getCanonicalName());
        }
        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 Policy findPolicy(final RestrictedType object, final String vendor, final String action)
            throws AuthException {
        Policy 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;
    }
}