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

be.objectify.deadbolt.java.actions.AbstractDeadboltAction Maven / Gradle / Ivy

There is a newer version: 2.8.1
Show newest version
/*
 * Copyright 2010-2016 Steve Chaloner
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package be.objectify.deadbolt.java.actions;

import be.objectify.deadbolt.java.ConfigKeys;
import be.objectify.deadbolt.java.DeadboltExecutionContextProvider;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.ExecutionContextProvider;
import be.objectify.deadbolt.java.cache.HandlerCache;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.libs.concurrent.HttpExecution;
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;
import scala.concurrent.ExecutionContext;
import scala.concurrent.ExecutionContextExecutor;

import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Provides some convenience methods for concrete Deadbolt actions, such as getting the correct {@link DeadboltHandler},
 * etc.  Extend this if you want to save some time if you create your own action.
 *
 * @author Steve Chaloner ([email protected])
 */
public abstract class AbstractDeadboltAction extends Action
{
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDeadboltAction.class);

    private static final String ACTION_AUTHORISED = "deadbolt.action-authorised";

    private static final String ACTION_UNAUTHORISED = "deadbolt.action-unauthorised";

    private static final String ACTION_DEFERRED = "deadbolt.action-deferred";
    private static final String IGNORE_DEFERRED_FLAG = "deadbolt.ignore-deferred-flag";

    final HandlerCache handlerCache;

    final Config config;

    final DeadboltExecutionContextProvider executionContextProvider;

    public final boolean blocking;
    public final long blockingTimeout;

    protected AbstractDeadboltAction(final HandlerCache handlerCache,
                                     final Config config,
                                     final ExecutionContextProvider ecProvider)
    {
        this.handlerCache = handlerCache;
        this.config = config;

        this.executionContextProvider = ecProvider.get();

        final HashMap defaults = new HashMap<>();
        defaults.put(ConfigKeys.BLOCKING_DEFAULT._1,
                     ConfigKeys.BLOCKING_DEFAULT._2);
        defaults.put(ConfigKeys.DEFAULT_BLOCKING_TIMEOUT_DEFAULT._1,
                     ConfigKeys.DEFAULT_BLOCKING_TIMEOUT_DEFAULT._2);
        final Config configWithFallback = config.withFallback(ConfigFactory.parseMap(defaults));
        this.blocking = configWithFallback.getBoolean(ConfigKeys.BLOCKING_DEFAULT._1);
        this.blockingTimeout = configWithFallback.getLong(ConfigKeys.DEFAULT_BLOCKING_TIMEOUT_DEFAULT._1);
    }

    /**
     * Gets the current {@link DeadboltHandler}.  This can come from one of two places:
     * - a handler key is provided in the annotation.  A cached instance of that class will be used. This has the highest priority.
     * - the global handler defined in the application.conf by deadbolt.handler.  This has the lowest priority.
     *
     * @param handlerKey the DeadboltHandler key, if any, coming from the annotation.
     * @param         the actual class of the DeadboltHandler
     * @return an option for the DeadboltHandler.
     */
    protected  DeadboltHandler getDeadboltHandler(final String handlerKey)
    {
        LOGGER.debug("Getting Deadbolt handler with key [{}]",
                     handlerKey);
        return handlerKey == null || ConfigKeys.DEFAULT_HANDLER_KEY.equals(handlerKey) ? handlerCache.get()
                                                                                       : handlerCache.apply(handlerKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CompletionStage call(final Http.Context ctx)
    {
        CompletionStage result;

        Class annClass = configuration.getClass();
        try
        {
            if (isDeferred(ctx))
            {
                result = getDeferredAction(ctx).call(ctx);
            }
            else if (!ctx.args.containsKey(IGNORE_DEFERRED_FLAG)
                    && annClass.isAnnotationPresent(Deferrable.class)
                    && (Boolean) annClass.getMethod("deferred").invoke(configuration))
            {
                defer(ctx,
                      this);
                result = delegate.call(ctx);
            }
            else
            {
                result = execute(ctx);
            }
            return result;
        }
        catch (Exception e)
        {
            LOGGER.info("Something bad happened while checking authorization",
                        e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Execute the action.
     *
     * @param ctx the request context
     * @return the result
     * @throws Exception if something bad happens
     */
    public abstract CompletionStage execute(final Http.Context ctx) throws Exception;

    /**
     * Wrapper for {@link DeadboltHandler#onAuthFailure} to ensure the access failure is logged.
     *
     * @param deadboltHandler the Deadbolt handler
     * @param content         the content type hint
     * @param ctx             th request context
     * @return the result of {@link DeadboltHandler#onAuthFailure}
     */
    protected CompletionStage onAuthFailure(final DeadboltHandler deadboltHandler,
                                                    final Optional content,
                                                    final Http.Context ctx)
    {
        LOGGER.info("Deadbolt: Access failure on [{}]",
                    ctx.request().uri());

        CompletionStage result;
        try
        {
            result = deadboltHandler.onAuthFailure(ctx,
                                                   content);
        }
        catch (Exception e)
        {
            LOGGER.warn("Deadbolt: Exception when invoking onAuthFailure",
                        e);
            result = CompletableFuture.completedFuture(Results.internalServerError());
        }
        return result;
    }

    /**
     * Marks the current action as authorised.  This allows method-level annotations to override controller-level annotations.
     *
     * @param ctx the request context
     */
    protected void markActionAsAuthorised(final Http.Context ctx)
    {
        ctx.args.put(ACTION_AUTHORISED,
                     true);
    }

    /**
     * Marks the current action as unauthorised.  This allows method-level annotations to override controller-level annotations.
     *
     * @param ctx the request context
     */
    protected void markActionAsUnauthorised(final Http.Context ctx)
    {
        ctx.args.put(ACTION_UNAUTHORISED,
                     true);
    }

    /**
     * Checks if an action is authorised.  This allows controller-level annotations to cede control to method-level annotations.
     *
     * @param ctx the request context
     * @return true if a more-specific annotation has authorised access, otherwise false
     */
    protected boolean isActionAuthorised(final Http.Context ctx)
    {
        final Object o = ctx.args.get(ACTION_AUTHORISED);
        return o != null && (Boolean) o;
    }

    /**
     * Checks if an action is unauthorised.  This allows controller-level annotations to cede control to method-level annotations.
     *
     * @param ctx the request context
     * @return true if a more-specific annotation has blocked access, otherwise false
     */
    protected boolean isActionUnauthorised(final Http.Context ctx)
    {
        final Object o = ctx.args.get(ACTION_UNAUTHORISED);
        return o != null && (Boolean) o;
    }

    /**
     * Defer execution until a later point.
     *
     * @param ctx    the request context
     * @param action the action to defer
     */
    protected void defer(final Http.Context ctx,
                         final AbstractDeadboltAction action)
    {
        if (action != null)
        {
            LOGGER.info("Deferring action [{}]",
                        this.getClass().getName());
            ctx.args.put(ACTION_DEFERRED,
                         action);
        }
    }

    /**
     * Check if there is a deferred action in the context.
     *
     * @param ctx the request context
     * @return true iff there is a deferred action in the context
     */
    public boolean isDeferred(final Http.Context ctx)
    {
        return ctx.args.containsKey(ACTION_DEFERRED);
    }

    /**
     * Get the deferred action from the context.
     *
     * @param ctx the request context
     * @return the deferred action, or null if it doesn't exist
     */
    public AbstractDeadboltAction getDeferredAction(final Http.Context ctx)
    {
        AbstractDeadboltAction action = null;
        final Object o = ctx.args.get(ACTION_DEFERRED);
        if (o != null)
        {
            action = (AbstractDeadboltAction) o;

            ctx.args.remove(ACTION_DEFERRED);
            ctx.args.put(IGNORE_DEFERRED_FLAG,
                         true);
        }
        return action;
    }

    public CompletionStage> preAuth(final boolean forcePreAuthCheck,
                                                     final Http.Context ctx,
                                                     final DeadboltHandler deadboltHandler)
    {
        return forcePreAuthCheck ? deadboltHandler.beforeAuthCheck(ctx)
                                 : CompletableFuture.completedFuture(Optional.empty());
    }

    protected ExecutionContextExecutor executor()
    {
        final ExecutionContext executionContext = executionContextProvider.get();
        return HttpExecution.fromThread(executionContext);
    }

    /**
     * Add a flag to the context to indicate the action has passed the constraint
     * and call the delegate.
     *
     * @param context the context
     * @return the result
     */
    protected CompletionStage authorizeAndExecute(final Http.Context context)
    {
        markActionAsAuthorised(context);
        return delegate.call(context);
    }

    /**
     * Add a flag to the context to indicate the action has been blocked by the
     * constraint and call {@link DeadboltHandler#onAuthFailure(Http.Context, Optional)}.
     *
     * @param context the context
     * @param handler the relevant handler
     * @param content the content type
     * @return the result
     */
    protected CompletionStage unauthorizeAndFail(final Http.Context context,
                                                         final DeadboltHandler handler,
                                                         final Optional content)
    {
        markActionAsUnauthorised(context);
        return onAuthFailure(handler,
                             content,
                             context);
    }

    public static CompletionStage sneakyCall(final Action action,
                                                     final Http.Context context)
    {
        try
        {
            return action.call(context);
        }
        catch (Throwable t)
        {
            throw sneakyThrow(t);
        }
    }

    CompletionStage maybeBlock(CompletionStage eventualResult) throws InterruptedException,
                                                                                      ExecutionException,
                                                                                      TimeoutException
    {
        return blocking ? CompletableFuture.completedFuture(eventualResult.toCompletableFuture().get(blockingTimeout,
                                                                                                     TimeUnit.MILLISECONDS))
                        : eventualResult;
    }

    private static RuntimeException sneakyThrow(final Throwable t)
    {
        if (t == null)
        {
            throw new NullPointerException("Can't use sneakyThrow without a throwable");
        }
        sneakyThrow0(t);
        return null;
    }

    @SuppressWarnings("unchecked")
    private static  void sneakyThrow0(final Throwable t) throws T
    {
        throw (T) t;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy