<commit_before>/*
 * Copyright 2010-2016 <NAME>
 *
 * 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.ConstraintMode;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.cache.BeforeAuthCheckCache;
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.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;

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 <NAME> (<EMAIL>)
 */
public abstract class AbstractDeadboltAction<T> extends Action<T>
{
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDeadboltAction.class);

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

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

    private static final String CONSTRAINT_COUNT = "deadbolt.constraints-count-in-action-chain";

    final HandlerCache handlerCache;

    final BeforeAuthCheckCache beforeAuthCheckCache;

    final Config config;

    public final boolean blocking;
    public final long blockingTimeout;
    public final ConstraintMode constraintMode;

    protected AbstractDeadboltAction(final HandlerCache handlerCache,
                                     final BeforeAuthCheckCache beforeAuthCheckCache,
                                     final Config config)
    {
        this.handlerCache = handlerCache;
        this.beforeAuthCheckCache = beforeAuthCheckCache;
        this.config = config;

        final HashMap<String, Object> 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);
        defaults.put(ConfigKeys.CONSTRAINT_MODE_DEFAULT._1,
                     ConfigKeys.CONSTRAINT_MODE_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);
        this.constraintMode = ConstraintMode.valueOf(configWithFallback.getString(ConfigKeys.CONSTRAINT_MODE_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 <C>        the actual class of the DeadboltHandler
     * @return an option for the DeadboltHandler.
     */
    protected <C extends DeadboltHandler> 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<Result> call(final Http.Context ctx)
    {
        CompletionStage<Result> result;

        // The first deadbolt-annotations that runs saves how many constraints in the action chain are, so we know that for later
        // Be aware: Not every deadbolt-annotation is a constraint, but every constraint is a deadbolt-annotation ;)
        // @BeforeAccess and @DeferredDeadboltAction do NOT count as constraint, because they just pass trough (=they do NOT call markActionAsAuthorised() in success case)
        ctx.args.computeIfAbsent(CONSTRAINT_COUNT, key -> constraintsCountInActionChain(this, 0));

        try
        {
            if (isDeferred(ctx))
            {
                final AbstractDeadboltAction<?> deferredAction = getDeferredAction(ctx);
                LOGGER.debug("Executing deferred action [{}]", deferredAction.getClass().getName());
                result = deferredAction.call(ctx);
            }
            else if (!ctx.args.containsKey(IGNORE_DEFERRED_FLAG)
                    && deferred())
            {
                defer(ctx,
                      this);
                result = delegate.call(ctx);
            }
            else
            {
                if (isActionAuthorised(ctx) && !alwaysExecute())
                {
                    result = delegate.call(ctx);
                }
                else
                {
                    result = maybeBlock(execute(ctx));
                }
            }
            if(constraintMode == ConstraintMode.OR && !deadboltActionLeftInActionChain(this) && !isActionAuthorised(ctx) && ((Integer)ctx.args.get(CONSTRAINT_COUNT)) > 0) {
                // We are in OR mode and "this" is the last deadbolt-action that runs and no constraint marked the targeted action-method as authorised yet -> we finally have to fail now.
                // If there was no "real" constraint, we don't come here, e.g. just calling @BeforeAccess or/and @DeferredDeadboltAction doesn't count as constraint
                result = onAuthFailure(getDeadboltHandler(getHandlerKey()),
                        getContent(),
                        ctx);
            }
            return result;
        }
        catch (Exception e)
        {
            LOGGER.info("Something bad happened while checking authorization",
                        e);
            throw new RuntimeException(e);
        }
    }<commit_msg>In OR mode isActionAuthorised(ctx) check has to happen in thenCompose...<commit_after>/*
 * Copyright 2010-2016 <NAME>
 *
 * 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.ConstraintMode;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.cache.BeforeAuthCheckCache;
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.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;

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 <NAME> (<EMAIL>)
 */
public abstract class AbstractDeadboltAction<T> extends Action<T>
{
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDeadboltAction.class);

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

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

    private static final String CONSTRAINT_COUNT = "deadbolt.constraints-count-in-action-chain";

    final HandlerCache handlerCache;

    final BeforeAuthCheckCache beforeAuthCheckCache;

    final Config config;

    public final boolean blocking;
    public final long blockingTimeout;
    public final ConstraintMode constraintMode;

    protected AbstractDeadboltAction(final HandlerCache handlerCache,
                                     final BeforeAuthCheckCache beforeAuthCheckCache,
                                     final Config config)
    {
        this.handlerCache = handlerCache;
        this.beforeAuthCheckCache = beforeAuthCheckCache;
        this.config = config;

        final HashMap<String, Object> 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);
        defaults.put(ConfigKeys.CONSTRAINT_MODE_DEFAULT._1,
                     ConfigKeys.CONSTRAINT_MODE_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);
        this.constraintMode = ConstraintMode.valueOf(configWithFallback.getString(ConfigKeys.CONSTRAINT_MODE_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 <C>        the actual class of the DeadboltHandler
     * @return an option for the DeadboltHandler.
     */
    protected <C extends DeadboltHandler> 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<Result> call(final Http.Context ctx)
    {
        CompletionStage<Result> result;

        // The first deadbolt-annotations that runs saves how many constraints in the action chain are, so we know that for later
        // Be aware: Not every deadbolt-annotation is a constraint, but every constraint is a deadbolt-annotation ;)
        // @BeforeAccess and @DeferredDeadboltAction do NOT count as constraint, because they just pass trough (=they do NOT call markActionAsAuthorised() in success case)
        ctx.args.computeIfAbsent(CONSTRAINT_COUNT, key -> constraintsCountInActionChain(this, 0));

        try
        {
            if (isDeferred(ctx))
            {
                final AbstractDeadboltAction<?> deferredAction = getDeferredAction(ctx);
                LOGGER.debug("Executing deferred action [{}]", deferredAction.getClass().getName());
                result = deferredAction.call(ctx);
            }
            else if (!ctx.args.containsKey(IGNORE_DEFERRED_FLAG)
                    && deferred())
            {
                defer(ctx,
                      this);
                result = delegate.call(ctx);
            }
            else
            {
                if (isActionAuthorised(ctx) && !alwaysExecute())
                {
                    result = delegate.call(ctx);
                }
                else
                {
                    result = maybeBlock(execute(ctx));
                }
            }
            return result.thenCompose(r -> {
                if(constraintMode == ConstraintMode.OR && !deadboltActionLeftInActionChain(this) && !isActionAuthorised(ctx) && ((Integer)ctx.args.get(CONSTRAINT_COUNT)) > 0) {
                    // We are in OR mode and "this" was the last deadbolt-action that ran and no constraint marked the targeted action-method as authorised yet -> we finally have to fail now.
                    // If there was no "real" constraint, we don't come here, e.g. just calling @BeforeAccess or/and @DeferredDeadboltAction doesn't count as constraint
                    return onAuthFailure(getDeadboltHandler(getHandlerKey()),
                            getContent(),
                            ctx);
                }
                return CompletableFuture.completedFuture(r);
            });
        }
        catch (Exception e)
        {
            LOGGER.info("Something bad happened while checking authorization",
                        e);
            throw new RuntimeException(e);
        }
    }
{'fraction_non_alphanumeric': 0.08016965651508831, 'fraction_numerical': 0.003476567932137394, 'mean_word_length': 2.5707547169811322, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1090123', 'n_tokens_mistral': 3765, 'n_tokens_neox': 3551, 'n_words': 1049}