/*
 * Copyright 2010-2017 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;

import be.objectify.deadbolt.java.cache.PatternCache;
import be.objectify.deadbolt.java.cache.SubjectCache;
import be.objectify.deadbolt.java.models.PatternType;
import be.objectify.deadbolt.java.models.Subject;
import be.objectify.deadbolt.java.utils.TriFunction;
import play.libs.F;
import play.mvc.Http;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * The logic behind the constraints.
 *
 * @author Steve Chaloner (steve@objectify.be)
 */
@Singleton
public class ConstraintLogic
{
    private final DeadboltAnalyzer analyzer;

    private final SubjectCache subjectCache;

    private final PatternCache patternCache;

    @Inject
    public ConstraintLogic(final DeadboltAnalyzer analyzer,
                           final SubjectCache subjectCache,
                           final PatternCache patternCache)
    {
        this.analyzer = analyzer;
        this.subjectCache = subjectCache;
        this.patternCache = patternCache;
    }

    public <T> CompletionStage<T> subjectPresent(final Http.RequestHeader requestHeader,
                                                 final DeadboltHandler deadboltHandler,
                                                 final Optional<String> content,
                                                 final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> present,
                                                 final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> notPresent,
                                                 final ConstraintPoint constraintPoint)
    {
        return subjectTest(requestHeader,
                           deadboltHandler,
                           content,
                           (rh, handler, cnt) ->
                           {
                               handler.onAuthSuccess(rh,
                                                     "subjectPresent",
                                                     constraintPoint);
                               return present.apply(rh,
                                                    handler,
                                                    cnt);
                           },
                           notPresent);
    }

    public <T> CompletionStage<T> subjectNotPresent(final Http.RequestHeader requestHeader,
                                                    final DeadboltHandler deadboltHandler,
                                                    final Optional<String> content,
                                                    final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> present,
                                                    final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> notPresent,
                                                    final ConstraintPoint constraintPoint)
    {
        return subjectTest(requestHeader,
                           deadboltHandler,
                           content,
                           present,
                           (rh, handler, cnt) ->
                           {
                               handler.onAuthSuccess(rh,
                                                     "subjectNotPresent",
                                                     constraintPoint);
                               return notPresent.apply(rh,
                                                    handler,
                                                    cnt);
                           });
    }

    private <T> CompletionStage<T> subjectTest(final Http.RequestHeader requestHeader,
                                               final DeadboltHandler deadboltHandler,
                                               final Optional<String> content,
                                               final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> present,
                                               final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> notPresent)
    {
        return getSubject(requestHeader,
                          deadboltHandler)
                .thenCompose(maybeSubject -> maybeSubject._1.map(subject -> present.apply(maybeSubject._2,
                                                                                deadboltHandler,
                                                                                content))
                                                         .orElseGet(() -> notPresent.apply(maybeSubject._2,
                                                                                deadboltHandler,
                                                                                content)));
    }

    public <T> CompletionStage<T> restrict(final Http.RequestHeader requestHeader,
                                           final DeadboltHandler deadboltHandler,
                                           final Optional<String> content,
                                           final Supplier<List<String[]>> roleGroupSupplier,
                                           final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                           final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                           final ConstraintPoint constraintPoint)
    {
        return getSubject(requestHeader,
                          deadboltHandler)
                .thenCompose(subjectOption ->
                                  {
                                      boolean roleOk = false;
                                      if (subjectOption._1.isPresent())
                                      {
                                          final List<String[]> roleGroups = roleGroupSupplier.get();
                                          for (int i = 0; !roleOk && i < roleGroups.size(); i++)
                                          {
                                              roleOk = analyzer.checkRole(subjectOption._1,
                                                                          roleGroups.get(i));
                                          }
                                      }
                                      return roleOk ? pass(subjectOption._2,
                                                           deadboltHandler,
                                                           pass,
                                                           constraintPoint,
                                                           "restrict")
                                                    : fail.apply(subjectOption._2,
                                                                 deadboltHandler,
                                                                 content);
                                  });
    }

    public <T> CompletionStage<T> roleBasedPermissions(final Http.RequestHeader requestHeader,
                                                       final DeadboltHandler deadboltHandler,
                                                       final Optional<String> content,
                                                       final String roleName,
                                                       final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                                       final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                                       final ConstraintPoint constraintPoint)
    {
        return getSubject(requestHeader,
                          deadboltHandler)
                .thenCompose(maybeSubject -> maybeSubject._1.isPresent() ? deadboltHandler.getPermissionsForRole(roleName)
                                                                                .thenApply(permissions -> F.Tuple(permissions.stream()
                                                                                .map(permission -> Optional.ofNullable(patternCache.apply(permission.getValue())))
                                                                                .map(maybePattern -> analyzer.checkRegexPattern(maybeSubject._1,
                                                                                maybePattern))
                                                                                .filter(matches -> matches)
                                                                                .findFirst()
                                                                                .isPresent(), maybeSubject._2))

                                                                      : CompletableFuture.completedFuture(F.Tuple(false, maybeSubject._2)))
                .thenCompose(allowed -> allowed._1 ? pass(allowed._2,
                                                            deadboltHandler,
                                                            pass,
                                                            constraintPoint,
                                                            "roleBasedPermissions")
                                                     : fail.apply(allowed._2,
                                                                  deadboltHandler,
                                                                  content));

    }

    public <T> CompletionStage<T> pattern(final Http.RequestHeader requestHeader,
                                          final DeadboltHandler deadboltHandler,
                                          final Optional<String> content,
                                          final String value,
                                          final PatternType patternType,
                                          final Optional<String> meta,
                                          final boolean invert,
                                          final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                          final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                          final ConstraintPoint constraintPoint)
    {
        return pattern(requestHeader, deadboltHandler, content, new String[] {value}, null, patternType, meta, invert, pass, fail, constraintPoint);
    }

    public <T> CompletionStage<T> pattern(final Http.RequestHeader requestHeader,
                                          final DeadboltHandler deadboltHandler,
                                          final Optional<String> content,
                                          final String[] values,
                                          final ConstraintMode mode,
                                          final PatternType patternType,
                                          final Optional<String> meta,
                                          final boolean invert,
                                          final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                          final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                          final ConstraintPoint constraintPoint)
    {
        final CompletionStage<T> result;

        switch (patternType)
        {
            case EQUALITY:
                result = equality(requestHeader,
                                  deadboltHandler,
                                  content,
                                  values,
                                  0,
                                  mode,
                                  invert,
                                  pass,
                                  fail,
                                  constraintPoint);
                break;
            case REGEX:
                result = regex(requestHeader,
                               deadboltHandler,
                               content,
                               values,
                               0,
                               mode,
                               invert,
                               pass,
                               fail,
                               constraintPoint);
                break;
            case CUSTOM:
                result = custom(requestHeader,
                                deadboltHandler,
                                content,
                                values,
                                0,
                                mode,
                                meta,
                                invert,
                                pass,
                                fail,
                                constraintPoint);
                break;
            default:
                throw new RuntimeException("Unknown pattern type: " + patternType);
        }

        return result;
    }

    public <T> CompletionStage<T> dynamic(final Http.RequestHeader requestHeader,
                                          final DeadboltHandler deadboltHandler,
                                          final Optional<String> content,
                                          final String name,
                                          final Optional<String> meta,
                                          final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                          final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                          final ConstraintPoint constraintPoint)
    {
        return deadboltHandler.getDynamicResourceHandler(requestHeader)
                              .thenApply(option -> option.orElseGet(() -> ExceptionThrowingDynamicResourceHandler.INSTANCE))
                              .thenCompose(drh -> drh.isAllowed(name,
                                                                meta,
                                                                deadboltHandler,
                                                                requestHeader))
                              .thenCompose(allowed -> allowed ? pass(requestHeader,
                                                                          deadboltHandler,
                                                                          pass,
                                                                          constraintPoint,
                                                                          "dynamic")
                                                                   : fail.apply(requestHeader,
                                                                                deadboltHandler,
                                                                                content));
    }

    private <T> CompletionStage<T> custom(final Http.RequestHeader requestHeader,
                                          final DeadboltHandler deadboltHandler,
                                          final Optional<String> content,
                                          final String[] values,
                                          final int valueIndex,
                                          final ConstraintMode mode,
                                          final Optional<String> meta,
                                          final boolean invert,
                                          final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                          final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                          final ConstraintPoint constraintPoint)
    {
        final Http.RequestHeader requestHeaderWithAttr = requestHeader.addAttr(Constants.PATTERN_INVERT,
                     invert);
        return deadboltHandler.getDynamicResourceHandler(requestHeaderWithAttr)
                              .thenApply(option -> option.orElseGet(() -> ExceptionThrowingDynamicResourceHandler.INSTANCE))
                              .thenCompose(resourceHandler -> resourceHandler.checkPermission(values[valueIndex],
                                                                                meta,
                                                                                deadboltHandler,
                                                                                requestHeaderWithAttr))
                              .thenCompose(allowed -> (invert ? !allowed : allowed) ? (successCallAgain(mode, values, valueIndex) ? custom(requestHeaderWithAttr,
                                                                                deadboltHandler,
                                                                                content,
                                                                                values,
                                                                                valueIndex + 1,
                                                                                mode,
                                                                                meta,
                                                                                invert,
                                                                                pass,
                                                                                fail,
                                                                                constraintPoint)
                                                                                : pass(requestHeaderWithAttr,
                                                                                deadboltHandler,
                                                                                pass,
                                                                                constraintPoint,
                                                                                "pattern - custom"))
                                                                                : (failCallAgain(mode, values, valueIndex) ? custom(requestHeaderWithAttr,
                                                                                deadboltHandler,
                                                                                content,
                                                                                values,
                                                                                valueIndex + 1,
                                                                                mode,
                                                                                meta,
                                                                                invert,
                                                                                pass,
                                                                                fail,
                                                                                constraintPoint)
                                                                                : fail.apply(requestHeaderWithAttr,
                                                                                deadboltHandler,
                                                                                content)));
    }

    private <T> CompletionStage<T> equality(final Http.RequestHeader requestHeader,
                                            final DeadboltHandler deadboltHandler,
                                            final Optional<String> content,
                                            final String[] values,
                                            final int valueIndex,
                                            final ConstraintMode mode,
                                            final boolean invert,
                                            final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                            final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                            final ConstraintPoint constraintPoint)
    {
        return getSubject(requestHeader,
                          deadboltHandler)
                .thenCompose(subject -> {
                                      final boolean equal = subject._1.isPresent() ? analyzer.checkPatternEquality(subject._1,
                                                                                Optional.ofNullable(values[valueIndex]))
                                                                                : invert; // this is a little clumsy - it means no subject + invert is still denied
                                      return (invert ? !equal : equal) ? (successCallAgain(mode, values, valueIndex) ? equality(subject._2,
                                                                                deadboltHandler,
                                                                                content,
                                                                                values,
                                                                                valueIndex + 1,
                                                                                mode,
                                                                                invert,
                                                                                pass,
                                                                                fail,
                                                                                constraintPoint)
                                                                                : pass(subject._2,
                                                                                deadboltHandler,
                                                                                pass,
                                                                                constraintPoint,
                                                                                "pattern - equality"))
                                                                       : (failCallAgain(mode, values, valueIndex) ? equality(subject._2,
                                                                                deadboltHandler,
                                                                                content,
                                                                                values,
                                                                                valueIndex + 1,
                                                                                mode,
                                                                                invert,
                                                                                pass,
                                                                                fail,
                                                                                constraintPoint)
                                                                                : fail.apply(subject._2,
                                                                                deadboltHandler,
                                                                                content));
                });
    }

    protected CompletionStage<F.Tuple<Optional<? extends Subject>, Http.RequestHeader>> getSubject(final Http.RequestHeader requestHeader,
                                                                                final DeadboltHandler deadboltHandler)
    {
        return subjectCache.apply(deadboltHandler,
                                  requestHeader);
    }

    /**
     * Checks access to the resource based on the regex
     *
     * @param requestHeader             the HTTP request header
     * @param deadboltHandler the Deadbolt handler
     * @param invert          if true, invert the application of the constraint
     * @return the necessary result
     */
    private <T> CompletionStage<T> regex(final Http.RequestHeader requestHeader,
                                         final DeadboltHandler deadboltHandler,
                                         final Optional<String> content,
                                         final String[] values,
                                         final int valueIndex,
                                         final ConstraintMode mode,
                                         final boolean invert,
                                         final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                         final TriFunction<Http.RequestHeader, DeadboltHandler, Optional<String>, CompletionStage<T>> fail,
                                         final ConstraintPoint constraintPoint)
    {
        return CompletableFuture.completedFuture(patternCache.apply(values[valueIndex]))
                                .thenCombine(getSubject(requestHeader,
                                                             deadboltHandler),
                                             (patternValue, subject) ->
                                                     F.Tuple(subject._1.isPresent() ? analyzer.checkRegexPattern(subject._1,
                                                                                Optional.ofNullable(patternValue))
                                                                         : invert, subject._2)) // this is a little clumsy - it means no subject + invert is still denied

                                .thenCompose(hasPassed -> (invert ? !hasPassed._1 : hasPassed._1) ? (successCallAgain(mode, values, valueIndex) ? regex(hasPassed._2,
                                                                                deadboltHandler,
                                                                                content,
                                                                                values,
                                                                                valueIndex + 1,
                                                                                mode,
                                                                                invert,
                                                                                pass,
                                                                                fail,
                                                                                constraintPoint)
                                                                                : pass(hasPassed._2,
                                                                                deadboltHandler,
                                                                                pass,
                                                                                constraintPoint,
                                                                                "pattern - regex"))
                                                                                : (failCallAgain(mode, values, valueIndex) ? regex(hasPassed._2,
                                                                                deadboltHandler,
                                                                                content,
                                                                                values,
                                                                                valueIndex + 1,
                                                                                mode,
                                                                                invert,
                                                                                pass,
                                                                                fail,
                                                                                constraintPoint)
                                                                                : fail.apply(hasPassed._2,
                                                                                deadboltHandler,
                                                                                content)));
    }

    private static boolean successCallAgain(final ConstraintMode mode, final String values[], final int valueIndex) {
        if(mode == null || ConstraintMode.AND.equals(mode)) { // null check because AND is the default mode
            if(values.length > (valueIndex + 1)) {
                // there is at least another check left - that one has to pass too in AND mode
                return true;
            }
        }
        return false;
    }

    private static boolean failCallAgain(final ConstraintMode mode, final String values[], final int valueIndex) {
        if(ConstraintMode.OR.equals(mode)) {
            if(values.length > (valueIndex + 1)) {
                // there is at least another check left - give that one the chance to pass
                return true;
            }
        }
        return false;
    }

    private <T> CompletionStage<T> pass(final Http.RequestHeader requestHeader,
                                        final DeadboltHandler handler,
                                        final Function<Http.RequestHeader, CompletionStage<T>> pass,
                                        final ConstraintPoint constraintPoint,
                                        final String constraintType)
    {
        handler.onAuthSuccess(requestHeader,
                              constraintType,
                              constraintPoint);
        return pass.apply(requestHeader);
    }
}


/*
 * 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.Constants;
import be.objectify.deadbolt.java.ConstraintAnnotationMode;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.libs.F;
import play.libs.typedmap.TypedKey;
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;

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;
import java.util.function.Function;

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

    static final TypedKey<Boolean> ACTION_AUTHORISED = TypedKey.create("deadbolt.action-authorised");

    static final TypedKey<AbstractDeadboltAction<?>> ACTION_DEFERRED = TypedKey.create("deadbolt.action-deferred");
    static final TypedKey<Boolean> IGNORE_DEFERRED_FLAG = TypedKey.create("deadbolt.ignore-deferred-flag");

    final HandlerCache handlerCache;

    final BeforeAuthCheckCache beforeAuthCheckCache;

    public final boolean blocking;
    public final long blockingTimeout;
    public final ConstraintAnnotationMode constraintAnnotationMode;

    private boolean authorised = false;

    protected AbstractDeadboltAction(final HandlerCache handlerCache,
                                     final BeforeAuthCheckCache beforeAuthCheckCache,
                                     final Config config)
    {
        this.handlerCache = handlerCache;
        this.beforeAuthCheckCache = beforeAuthCheckCache;
        this.blocking = config.getBoolean("deadbolt.java.blocking");
        this.blockingTimeout = config.getLong("deadbolt.java.blocking-timeout");
        this.constraintAnnotationMode = ConstraintAnnotationMode.valueOf(config.getString("deadbolt.java.constraint-mode"));
    }

    /**
     * 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 || Constants.DEFAULT_HANDLER_KEY.equals(handlerKey) ? handlerCache.get()
                                                                                : handlerCache.apply(handlerKey);
    }

    @Override
    public CompletionStage<Result> call(final Http.Request request)
    {
        CompletionStage<Result> result;

        try
        {
            if (isDeferred(request))
            {
                final F.Tuple<AbstractDeadboltAction<?>, Http.RequestHeader> deferredAction = getDeferredAction(request);
                LOGGER.debug("Executing deferred action [{}]", deferredAction._1.getClass().getName());
                result = deferredAction._1.call((Http.Request)deferredAction._2);
            }
            else if (!request.attrs().containsKey(IGNORE_DEFERRED_FLAG)
                    && deferred())
            {
                result = delegate.call((Http.Request)defer(request,
                        this));
            }
            else
            {
                if (isAuthorised(request) && !alwaysExecute())
                {
                    result = delegate.call(request);
                }
                else
                {
                    result = maybeBlock(execute(request));
                }
            }
            return result.thenCompose(r -> {
                if(constraintAnnotationMode == ConstraintAnnotationMode.OR && !deadboltActionLeftInActionChain(this) && !this.isAuthorised() && !isAuthorised(request) && (isConstraintInActionChain(this, action -> action.precursor) || isConstraintInActionChain(this, action -> action.delegate))) {
                    // 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(),
                            request);
                }
                return CompletableFuture.completedFuture(r);
            });
        }
        catch (Exception e)
        {
            LOGGER.info("Something bad happened while checking authorization",
                        e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Execute the action.
     *
     * @param request the request header
     * @return the result
     * @throws Exception if something bad happens
     */
    public abstract CompletionStage<Result> execute(final Http.RequestHeader request) throws Exception;

    /**
     * If a constraint is deferrable, i.e. method-level constraints are not applied until controller-level annotations are applied.
     */
    protected abstract boolean deferred();

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

        CompletionStage<Result> result;
        try
        {
            result = deadboltHandler.onAuthFailure(request,
                                                   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 request the request
     */
    private Http.RequestHeader markAsAuthorised(final Http.RequestHeader request)
    {
        this.authorised = true;
        return request.addAttr(ACTION_AUTHORISED,
                     true);
    }

    boolean isAuthorised() {
        return this.authorised;
    }

    /**
     * Checks if an action is authorised.  This allows controller-level annotations to cede control to method-level annotations.
     *
     * @param request the request
     * @return true if a more-specific annotation has authorised access, otherwise false
     */
    protected static boolean isAuthorised(final Http.RequestHeader request)
    {
        return request.attrs().getOptional(ACTION_AUTHORISED).orElse(false);
    }

    /**
     * Defer execution until a later point.
     *
     * @param request    the request
     * @param action the action to defer
     */
    protected Http.RequestHeader defer(final Http.RequestHeader request,
                         final AbstractDeadboltAction<T> action)
    {
        if (action != null)
        {
            LOGGER.info("Deferring action [{}]",
                        this.getClass().getName());
            return request.addAttr(ACTION_DEFERRED,
                         action);
        }
        return request;
    }

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

    /**
     * Get the deferred action from the request.
     *
     * @param request the request
     * @return a tuple containing the deferred action (or null if it doesn't exist) and the cleaned up request you should pass on
     */
    @SuppressWarnings("unchecked")
    public F.Tuple<AbstractDeadboltAction<?>, Http.RequestHeader> getDeferredAction(final Http.RequestHeader request)
    {
        return request.attrs().getOptional(ACTION_DEFERRED).map(action -> {
            action.delegate = this;
            return F.<AbstractDeadboltAction<?>, Http.RequestHeader>Tuple(action, request.removeAttr(ACTION_DEFERRED).addAttr(IGNORE_DEFERRED_FLAG, true));
        }).orElseGet(() -> F.Tuple(null, request));
    }

    public CompletionStage<F.Tuple<Optional<Result>, Http.RequestHeader>> preAuth(final boolean forcePreAuthCheck,
                                                                                final Http.RequestHeader request,
                                                                                final Optional<String> content,
                                                                                final DeadboltHandler deadboltHandler)
    {
        return forcePreAuthCheck ? beforeAuthCheckCache.apply(deadboltHandler, request, content)
                                 : CompletableFuture.completedFuture(F.Tuple(Optional.empty(), request));
    }

    /**
     * Add a flag to the request to indicate the action has passed the constraint
     * and call the delegate.
     *
     * @param request the request
     * @return the result
     */
    protected CompletionStage<Result> authorizeAndExecute(final Http.RequestHeader request)
    {
        if(constraintAnnotationMode != ConstraintAnnotationMode.AND)
        {
            // In AND mode we don't mark an action as authorised because we want ALL (remaining) constraints to be evaluated as well!
            return delegate.call((Http.Request)markAsAuthorised(request));
        }
        return delegate.call((Http.Request)request);
    }

    /**
     * Add a flag to the request to indicate the action has been blocked by the
     * constraint and call {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional<String>)}.
     *
     * @param request the request
     * @param handler the relevant handler
     * @param content the content type
     * @return the result
     */
    protected CompletionStage<Result> unauthorizeAndFail(final Http.RequestHeader request,
                                                         final DeadboltHandler handler,
                                                         final Optional<String> content)
    {
        if(constraintAnnotationMode == ConstraintAnnotationMode.OR && deadboltActionLeftInActionChain(this))
        {
            // In OR mode we don't fail immediately but also check remaining constraints (it there is any left). Maybe one of these next ones authorizes...
            return delegate.call((Http.Request)request);
        }

        return onAuthFailure(handler,
                             content,
                             request);
    }

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

    /**
     * Recursive method to determine if there is another deadbolt action further down the action chain
     */
    private static boolean deadboltActionLeftInActionChain(final Action<?> action) {
        if(action != null) {
            if(action.delegate instanceof AbstractDeadboltAction) {
                return true; // yes, there is at least one deadbolt action remaining
            }
            // action.delegate wasn't a deadbolt action, let's check the next one in the chain
            return deadboltActionLeftInActionChain(action.delegate);
        }
        return false;
    }

    /**
     * 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 markAsAuthorised() in success case)
     */
    private static boolean isConstraintInActionChain(final Action<?> action, final Function<Action<?>, Action<?>> nextAction) {
        if(action != null) {
            if(action instanceof AbstractDeadboltAction &&
                    !(action instanceof BeforeAccessAction) &&
                    !(action instanceof DeferredDeadboltAction)) {
                return true;
            }
            // that wasn't a deadbolt constraint, let's go and check the next action in the chain
            return isConstraintInActionChain(nextAction.apply(action), nextAction);
        }
        return false;
    }

    public abstract Optional<String> getContent();

    public abstract String getHandlerKey();

    public boolean alwaysExecute() {
        return false;
    }

}


/*
 * 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.filters;

import akka.stream.Materializer;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.cache.HandlerCache;
import play.mvc.Http;
import play.mvc.Result;
import play.routing.Router;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;

/**
 * Applies constraints before the action is invoked, allowing constraints to be defined outside of controllers.
 *
 * @author Steve Chaloner (steve@objectify.be)
 * @since 2.5.1
 */
@Singleton
public class DeadboltRoutePathFilter extends AbstractDeadboltFilter
{
    private final DeadboltHandler handler;
    private final AuthorizedRoutes authorizedRoutes;

    @Inject
    public DeadboltRoutePathFilter(final Materializer mat,
                                   final HandlerCache handlerCache,
                                   final Provider<AuthorizedRoutes> authorizedRoutes)
    {
        super(mat);
        this.handler = handlerCache.get();
        this.authorizedRoutes = authorizedRoutes.get();
    }

    /**
     * If a constraint is defined for a given route, test that constraint before allowing the request to proceed.
     *
     * @param next          the next step in the filter chain
     * @param requestHeader the request header
     * @return a future for the result
     */
    @Override
    public CompletionStage<Result> apply(final Function<Http.RequestHeader, CompletionStage<Result>> next,
                                         final Http.RequestHeader requestHeader)
    {
        final Optional<AuthorizedRoute> maybeAuthRoute = authorizedRoutes.apply(requestHeader.method(),
                                                                                requestHeader.attrs().get(Router.Attrs.HANDLER_DEF).path());
        return maybeAuthRoute.map(authRoute -> authRoute.constraint().apply(requestHeader,
                                                                            authRoute.handler().orElse(handler),
                                                                            next)
        ).orElseGet(() -> next.apply(requestHeader));
    }
}


/*
 * 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;

import be.objectify.deadbolt.java.models.Permission;
import be.objectify.deadbolt.java.models.Role;
import be.objectify.deadbolt.java.models.Subject;

import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This carries out static (i.e. non-dynamic) checks.
 *
 * @author Steve Chaloner (steve@objectify.be)
 */
@Singleton
public class DeadboltAnalyzer
{
    /**
     * Checks if the subject has all the role names.  In other words, this gives AND support.
     *
     * @param subjectOption an option for the subject
     * @param roleNames     the role names.  Any role name starting with ! will be negated.
     * @return true if the subject meets the restrictions (so access will be allowed), otherwise false
     */
    public boolean checkRole(final Optional<? extends Subject> subjectOption,
                             final String[] roleNames)
    {
        // this is legacy code, and can be refactored out at some point
        return hasAllRoles(subjectOption,
                           roleNames);
    }

    /**
     * Gets the role name of each role held.
     *
     * @param subjectOption an option for the subject
     * @return a non-null list containing all role names
     */
    public List<String> getRoleNames(final Optional<? extends Subject> subjectOption)
    {
        final List<String> roleNames = new ArrayList<>();
        subjectOption.ifPresent(subject ->
                                {
                                    final List<? extends Role> roles = subject.getRoles();
                                    if (roles != null)
                                    {
                                        roleNames.addAll(roles.stream()
                                                              .filter(Objects::nonNull)
                                                              .map(Role::getName)
                                                              .collect(Collectors.toList()));
                                    }
                                });

        return roleNames;
    }

    /**
     * Check if the subject has the given role.
     *
     * @param subjectOption an option for the subject
     * @param roleName      the name of the role
     * @return true iff the subject has the role represented by the role name
     */
    public boolean hasRole(final Optional<? extends Subject> subjectOption,
                           final String roleName)
    {
        return getRoleNames(subjectOption).contains(roleName);
    }

    /**
     * Check if the {@link Subject} has all the roles given in the roleNames array.  Note that while a Subject must
     * have all the roles, it may also have other roles.
     *
     * @param subjectOption an option for the subject
     * @param roleNames     the names of the required roles
     * @return true iff the subject has all the roles
     */
    public boolean hasAllRoles(final Optional<? extends Subject> subjectOption,
                               final String[] roleNames)
    {
        final List<String> heldRoles = getRoleNames(subjectOption);

        boolean roleCheckResult = roleNames != null && roleNames.length > 0;
        for (int i = 0; roleCheckResult && i < roleNames.length; i++)
        {
            boolean invert = false;
            String roleName = roleNames[i];
            if (roleName.startsWith("!"))
            {
                invert = true;
                roleName = roleName.substring(1);
            }
            roleCheckResult = heldRoles.contains(roleName);

            if (invert)
            {
                roleCheckResult = !roleCheckResult;
            }
        }
        return roleCheckResult;
    }

    /**
     * Check the pattern for a match against the {@link Permission}s of the user.
     *
     * @param subjectOption an option for the subject
     * @param patternOption an option for the pattern
     * @return true iff the pattern matches at least one of the subject's permissions
     */
    public boolean checkRegexPattern(final Optional<? extends Subject> subjectOption,
                                     final Optional<Pattern> patternOption)
    {
        final boolean[] roleOk = {false};
        subjectOption.ifPresent(subject -> patternOption.ifPresent(pattern ->
                                                                   {
                                                                       final List<? extends Permission> permissions = subject.getPermissions();
                                                                       if (permissions != null)
                                                                       {
                                                                           for (Iterator<? extends Permission> iterator = permissions.iterator(); !roleOk[0] && iterator
                                                                                .hasNext(); )
                                                                           {
                                                                               final Permission permission = iterator.next();
                                                                               roleOk[0] = pattern.matcher(permission.getValue()).matches();
                                                                           }
                                                                       }
                                                                   }));

        return roleOk[0];
    }

    /**
     * Check the pattern for equality against the {@link Permission}s of the user.
     *
     * @param subjectOption      an option for the subject
     * @param patternValueOption an option for the pattern value
     * @return true iff the pattern is equal to at least one of the subject's permissions
     */
    public boolean checkPatternEquality(final Optional<? extends Subject> subjectOption,
                                        final Optional<String> patternValueOption)
    {
        final boolean[] roleOk = {false};
        subjectOption.ifPresent(subject -> patternValueOption.ifPresent(patternValue ->
                                                                        {
                                                                            final List<? extends Permission> permissions = subject.getPermissions();
                                                                            if (permissions != null)
                                                                            {
                                                                                for (Iterator<? extends Permission> iterator = permissions.iterator(); !roleOk[0] && iterator
                                                                                .hasNext(); )
                                                                                {
                                                                                final Permission permission = iterator.next();
                                                                                roleOk[0] = patternValue.equals(permission.getValue());
                                                                                }
                                                                            }
                                                                        }));

        return roleOk[0];
    }
}


/*
 * 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.filters;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import akka.stream.Materializer;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.cache.HandlerCache;
import be.objectify.deadbolt.java.models.PatternType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.api.routing.HandlerDef;
import play.libs.F;
import play.mvc.Http;
import play.mvc.Result;
import play.routing.Router;

/**
 * Filters all incoming HTTP requests and applies constraints based on the routes' modifier tags.  If deadbolt modifier tags are present, the constraints
 * for that route will be applied.  If access is allowed, the next filter in the chain is invoked; if access is not allowed,
 * {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} is invoked.
 *

 * The format of a modifier tag is deadbolt:constraintType:config.  Individual configurations have the form :label[value] - to omit an optional config,
 * remove :label[value],
 *

 *

 *
deadbolt:subjectPresent:handler[handler name]
 *

 *
handler - optional.  The name of a handler in the HandlerCache

 *

 *

 *
deadbolt:subjectNotPresent:handler[handler name]
 *

 *
name - required.  This is the name passed to DeadboltHandler#isAllowed

 *

 *

 *
deadbolt:dynamic:name[constraint name]:handler[handler name]
 *

 *
name - required.  This is the name passed to DeadboltHandler#isAllowed

 *
handler - optional.  The name of a handler in the HandlerCache

 *

 *

 *
deadbolt:pattern:value[constraint value]:type[EQUALITY|REGEX|CUSTOM]:invert[true|false]:handler[handler name]
 *

 *
value - required.  Used to test the permissions of a subject.

 *
type - required.  The pattern type, case sensitive.

 *
invert - optional.  Defines if the result should be flipped, i.e. a matching permission mean unauthorized.  Defaults to false.

 *
handler - optional.  The name of a handler in the HandlerCache

 *

 *

 *
deadbolt:composite:name[constraint name]:handler[handler name]
 *

 *
name - required.  The name of a constraint in CompositeCache.

 *
handler - optional.  The name of a handler in the HandlerCache

 *

 *

 *
deadbolt:restrict:name[constraint name]:handler[handler name]
 *

 *
name - required.  The name of a constraint in the CompositeCache

 *
handler - optional.  The name of a handler in the HandlerCache

 *

 *

 *
deadbolt:rbp:name[role name]:handler[handler name]
 *

 *
name - required.  The name of a role, which will be used to resolve permissions from {@link DeadboltHandler#getPermissionsForRole(String)}

 *
handler - optional.  The name of a handler in the HandlerCache

 *

 *

 *

 *

 * Restrict is a tricky one, because the possible combinations of roles leads to a nightmare to parse.  Instead, define your role constraints within the
 * composite cache and use the named constraint instead.  deadbolt:restrict is actually a synonym for deadbolt:composite.
 *
 * @author Steve Chaloner (steve@objectify.be)
 * @since 2.5.1
 */
@Singleton
public class DeadboltRouteModifierTagsFilter extends AbstractDeadboltFilter
{
    private static final Logger LOGGER = LoggerFactory.getLogger(DeadboltRouteModifierTagsFilter.class);

    final Pattern subjectPresentModifierTag = Pattern.compile("deadbolt\\:(subjectPresent)(?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");
    final Pattern subjectNotPresentModifierTag = Pattern.compile("deadbolt\\:(subjectNotPresent)(?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");
    final Pattern dynamicModifierTag = Pattern.compile("deadbolt\\:(dynamic)\\:name\\[(?<name>.+?)\\](?:\\:meta\\[(?<meta>.+?)\\]){0,1}(?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");
    final Pattern patternModifierTag = Pattern.compile("deadbolt\\:(pattern)\\:value\\[(?<value>.+?)\\]\\:type\\[(?<type>EQUALITY|REGEX|CUSTOM)\\](?:\\:meta\\[(?<meta>.+?)\\]){0,1}(?:\\:invert\\[(?<invert>true|false)\\]){0,1}(?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");
    final Pattern compositeModifierTag = Pattern.compile("deadbolt\\:(composite)\\:name\\[(?<name>.+?)\\](?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");
    final Pattern restrictModifierTag = Pattern.compile("deadbolt\\:(restrict)\\:name\\[(?<name>.+?)\\](?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");
    final Pattern roleBasedPermissionsModifierTag = Pattern.compile("deadbolt\\:(rbp)\\:name\\[(?<name>.+?)\\](?:\\:content\\[(?<content>.+?)\\]){0,1}(?:\\:handler\\[(?<handler>.+?)\\]){0,1}");

    private final HandlerCache handlerCache;
    private final DeadboltHandler handler;
    private final FilterConstraints filterConstraints;

    private final F.Tuple<FilterFunction, DeadboltHandler> unknownDeadboltModifierTag;

    @Inject
    public DeadboltRouteModifierTagsFilter(final Materializer mat,
                                           final HandlerCache handlerCache,
                                           final FilterConstraints filterConstraints)
    {
        super(mat);
        this.handlerCache = handlerCache;
        this.handler = handlerCache.get();
        this.filterConstraints = filterConstraints;

        this.unknownDeadboltModifierTag = new F.Tuple<>((requestHeader, dh, onSuccess) ->
                                                    {
                                                        LOGGER.error("Unknown Deadbolt route modifier tag in [{}], denying access with default handler",
                                                                     requestHeader.attrs().get(Router.Attrs.HANDLER_DEF)
                                                                             .getModifiers().stream().collect(Collectors.joining(" ")));
                                                        return dh.onAuthFailure(requestHeader, Optional.empty());
                                                    }, handler);
    }

    /**
     * If a constraint is defined for a given route, test that constraint before allowing the request to proceed.
     *
     * @param next          the next step in the filter chain
     * @param requestHeader the request header
     * @return a future for the result
     */
    @Override
    public CompletionStage<Result> apply(final Function<Http.RequestHeader, CompletionStage<Result>> next,
                                         final Http.RequestHeader requestHeader)
    {
        final HandlerDef handlerDef = requestHeader.attrs().get(Router.Attrs.HANDLER_DEF);
        final List<String> deadboltModifierTags = handlerDef.getModifiers().stream().filter(mt -> mt != null && mt.startsWith("deadbolt:")).collect(Collectors.toList());
        return processModifierTags(deadboltModifierTags, 0, requestHeader, next);
    }

    private CompletionStage<Result> processModifierTags(final List<String> deadboltModifierTags, int index, final Http.RequestHeader requestHeader, final Function<Http.RequestHeader, CompletionStage<Result>> lastNext)
    {
        if(index < deadboltModifierTags.size())
        {
            final String modifierTag = deadboltModifierTags.get(index);
            // this is horrible
            final F.Tuple<FilterFunction, DeadboltHandler> tuple = subjectPresent(modifierTag).orElseGet(() -> subjectNotPresent(modifierTag)
                    .orElseGet(() -> dynamic(modifierTag)
                            .orElseGet(() -> composite(modifierTag)
                                    .orElseGet(() -> restrict(modifierTag)
                                            .orElseGet(() -> pattern(modifierTag)
                                                    .orElseGet(() -> roleBasedPermissions(modifierTag)
                                                            .orElse(unknownDeadboltModifierTag)))))));
            return tuple._1.apply(requestHeader,
                                  tuple._2,
                                  rh -> processModifierTags(deadboltModifierTags, index + 1, rh, lastNext));
        }
        else
        {
            return lastNext.apply(requestHeader);
        }
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> subjectPresent(final String modifierTag)
    {
        final Matcher matcher = subjectPresentModifierTag.matcher(modifierTag);
        return matcher.matches() ? Optional.of(new F.Tuple<>(filterConstraints.subjectPresent(Optional.ofNullable(matcher.group("content"))),
                                                             handler(matcher)))
                                 : Optional.empty();
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> subjectNotPresent(final String modifierTag)
    {
        final Matcher matcher = subjectNotPresentModifierTag.matcher(modifierTag);
        return matcher.matches() ? Optional.of(new F.Tuple<>(filterConstraints.subjectNotPresent(Optional.ofNullable(matcher.group("content"))),
                                                             handler(matcher)))
                                 : Optional.empty();
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> dynamic(final String modifierTag)
    {
        final Matcher matcher = dynamicModifierTag.matcher(modifierTag);
        return matcher.matches() ? Optional.of(new F.Tuple<>(filterConstraints.dynamic(matcher.group("name"),
                                                                                Optional.ofNullable(matcher.group("meta")),
                                                                                Optional.ofNullable(matcher.group("content"))),
                                                             handler(matcher)))
                                 : Optional.empty();
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> composite(final String modifierTag)
    {
        final Matcher matcher = compositeModifierTag.matcher(modifierTag);
        return matcher.matches() ? Optional.of(new F.Tuple<>(filterConstraints.composite(matcher.group("name"),
                                                                                Optional.ofNullable(matcher.group("content"))),
                                                             handler(matcher)))
                                 : Optional.empty();
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> restrict(final String modifierTag)
    {
        final Matcher matcher = restrictModifierTag.matcher(modifierTag);
        return matcher.matches() ? Optional.of(new F.Tuple<>(filterConstraints.composite(matcher.group("name"),
                                                                                Optional.ofNullable(matcher.group("content"))),
                                                             handler(matcher)))
                                 : Optional.empty();
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> roleBasedPermissions(final String modifierTag)
    {
        final Matcher matcher = roleBasedPermissionsModifierTag.matcher(modifierTag);
        return matcher.matches() ? Optional.of(new F.Tuple<>(filterConstraints.roleBasedPermissions(matcher.group("name"),
                                                                                Optional.ofNullable(matcher.group("content"))),
                                                             handler(matcher)))
                                 : Optional.empty();
    }

    private Optional<F.Tuple<FilterFunction, DeadboltHandler>> pattern(final String modifierTag)
    {
        final Matcher matcher = patternModifierTag.matcher(modifierTag);
        if (matcher.matches())
        {
            final String invertStr = matcher.group("invert");
            final boolean invert = invertStr != null && Boolean.parseBoolean(invertStr);
            return Optional.of(new F.Tuple<>(filterConstraints.pattern(matcher.group("value"),
                                                                       PatternType.valueOf(matcher.group("type")),
                                                                       Optional.ofNullable(matcher.group("meta")),
                                                                       invert,
                                                                       Optional.ofNullable(matcher.group("content"))),
                                             handler(matcher)));
        }
        else
        {
            return Optional.empty();
        }
    }

    private DeadboltHandler handler(final Matcher matcher)
    {
        final String namedHandler = matcher.group("handler");
        return namedHandler == null ? handler
                                    : handlerCache.apply(namedHandler);
    }
}


/*
 * 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.filters;

import be.objectify.deadbolt.java.ConstraintLogic;
import be.objectify.deadbolt.java.ConstraintPoint;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.cache.BeforeAuthCheckCache;
import be.objectify.deadbolt.java.cache.CompositeCache;
import be.objectify.deadbolt.java.composite.Constraint;
import be.objectify.deadbolt.java.models.PatternType;
import be.objectify.deadbolt.java.utils.TriFunction;
import play.mvc.Http;
import play.mvc.Result;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * @author Steve Chaloner (steve@objectify.be)
 * @since 2.5.1
 */
@Singleton
public class FilterConstraints
{
    private final ConstraintLogic constraintLogic;
    private final CompositeCache compositeCache;
    private final BeforeAuthCheckCache beforeAuthCheckCache;

    @Inject
    public FilterConstraints(final ConstraintLogic constraintLogic,
                             final CompositeCache compositeCache,
                             final BeforeAuthCheckCache beforeAuthCheckCache)
    {
        this.constraintLogic = constraintLogic;
        this.compositeCache = compositeCache;
        this.beforeAuthCheckCache = beforeAuthCheckCache;
    }

    /**
     * A constraint that requires a subject to be present.
     *
     * @return a function that wraps the constraint
     * @see ConstraintLogic#subjectPresent(Http.RequestHeader, DeadboltHandler, Optional, TriFunction, TriFunction, ConstraintPoint)
     */
    public FilterFunction subjectPresent()
    {
        return subjectPresent(Optional.empty());
    }

    /**
     * A constraint that requires a subject to be present.
     *
     * @param content is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     * @see ConstraintLogic#subjectPresent(Http.RequestHeader, DeadboltHandler, Optional, TriFunction, TriFunction, ConstraintPoint)
     */
    public FilterFunction subjectPresent(final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraintLogic.subjectPresent(maybePreAuth._2,
                                                                                handler,
                                                                                content,
                                                                                (rh, hdlr, cntent) -> next.apply(rh),
                                                                                (rh, hdlr, cntent) -> hdlr.onAuthFailure(rh,
                                                                                cntent),
                                                                                ConstraintPoint.FILTER)));
    }

    /**
     * A constraint that requires a subject to not be present.
     *
     * @return a function that wraps the constraint
     * @see ConstraintLogic#subjectPresent(Http.RequestHeader, DeadboltHandler, Optional, TriFunction, TriFunction, ConstraintPoint)
     */
    public FilterFunction subjectNotPresent()
    {
        return subjectNotPresent(Optional.empty());
    }

    /**
     * A constraint that requires a subject to not be present.
     *
     * @param content is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     * @see ConstraintLogic#subjectPresent(Http.RequestHeader, DeadboltHandler, Optional, TriFunction, TriFunction, ConstraintPoint)
     */
    public FilterFunction subjectNotPresent(final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraintLogic.subjectNotPresent(maybePreAuth._2,
                                                                                handler,
                                                                                content,
                                                                                (rh, hdlr, cntent) -> hdlr.onAuthFailure(rh,
                                                                                cntent),
                                                                                (rh, hdlr, cntent) -> next.apply(rh),
                                                                                ConstraintPoint.FILTER)));
    }

    /**
     * A constraint that requires the subject to hold certain roles.
     *
     * @param roleGroups
     * @return a function that wraps the constraint
     * @see ConstraintLogic#restrict(Http.RequestHeader, DeadboltHandler, Optional, Supplier, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction restrict(final List<String[]> roleGroups)
    {
        return restrict(roleGroups,
                        Optional.empty());
    }

    /**
     * A constraint that requires the subject to hold certain roles.
     *
     * @param roleGroups
     * @param content    is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     * @see ConstraintLogic#restrict(Http.RequestHeader, DeadboltHandler, Optional, Supplier, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction restrict(final List<String[]> roleGroups,
                                   final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraintLogic.restrict(maybePreAuth._2,
                                                                                handler,
                                                                                content,
                                                                                () -> roleGroups,
                                                                                rh -> next.apply(rh),
                                                                                (rh, hdlr, cntent) -> hdlr.onAuthFailure(rh,
                                                                                cntent),
                                                                                ConstraintPoint.FILTER)));
    }

    /**
     * A constraint that checks the permissions of a subject (if using {@link PatternType#EQUALITY} or {@link PatternType#REGEX}) or
     * {@link be.objectify.deadbolt.java.DynamicResourceHandler#checkPermission(String, Optional, DeadboltHandler, Http.RequestHeader)} (if
     * using {@link PatternType#CUSTOM}).
     *
     * @param value       the constraint value
     * @param patternType the type of pattern matching
     * @return a function that wraps the constraint
     * @see ConstraintLogic#pattern(Http.RequestHeader, DeadboltHandler, Optional, String, PatternType, Optional, boolean, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction pattern(final String value,
                                  final PatternType patternType)
    {
        return pattern(value,
                       patternType,
                       Optional.empty());
    }

    /**
     * A constraint that checks the permissions of a subject (if using {@link PatternType#EQUALITY} or {@link PatternType#REGEX}) or
     * {@link be.objectify.deadbolt.java.DynamicResourceHandler#checkPermission(String, Optional, DeadboltHandler, Http.RequestHeader)} (if
     * using {@link PatternType#CUSTOM}).
     *
     * @param value       the constraint value
     * @param patternType the type of pattern matching
     * @param meta        additional information passed to {@link be.objectify.deadbolt.java.DynamicResourceHandler#checkPermission(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * @return a function that wraps the constraint
     * @see ConstraintLogic#pattern(Http.RequestHeader, DeadboltHandler, Optional, String, PatternType, Optional, boolean, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction pattern(final String value,
                                  final PatternType patternType,
                                  final Optional<String> meta)
    {
        return pattern(value,
                       patternType,
                       meta,
                       false,
                       Optional.empty());
    }

    /**
     * A constraint that checks the permissions of a subject (if using {@link PatternType#EQUALITY} or {@link PatternType#REGEX}) or
     * {@link be.objectify.deadbolt.java.DynamicResourceHandler#checkPermission(String, Optional, DeadboltHandler, Http.RequestHeader)} (if
     * using {@link PatternType#CUSTOM}).
     *
     * @param value       the constraint value
     * @param patternType the type of pattern matching
     * @param invert      invert the meaning of the constraint, where a successful match results in authorization failing
     * @return a function that wraps the constraint
     * @see ConstraintLogic#pattern(Http.RequestHeader, DeadboltHandler, Optional, String, PatternType, Optional, boolean, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction pattern(final String value,
                                  final PatternType patternType,
                                  final boolean invert)
    {
        return pattern(value,
                       patternType,
                       Optional.empty(),
                       invert,
                       Optional.empty());
    }

    /**
     * A constraint that checks the permissions of a subject (if using {@link PatternType#EQUALITY} or {@link PatternType#REGEX}) or
     * {@link be.objectify.deadbolt.java.DynamicResourceHandler#checkPermission(String, Optional, DeadboltHandler, Http.RequestHeader)} (if
     * using {@link PatternType#CUSTOM}).
     *
     * @param value       the constraint value
     * @param patternType the type of pattern matching
     * @param meta        additional information passed to {@link be.objectify.deadbolt.java.DynamicResourceHandler#checkPermission(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * @param invert      invert the meaning of the constraint, where a successful match results in authorization failing
     * @param content     is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     * @see ConstraintLogic#pattern(Http.RequestHeader, DeadboltHandler, Optional, String, PatternType, Optional, boolean, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction pattern(final String value,
                                  final PatternType patternType,
                                  final Optional<String> meta,
                                  final boolean invert,
                                  final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraintLogic.pattern(maybePreAuth._2,
                                                                                handler,
                                                                                content,
                                                                                value,
                                                                                patternType,
                                                                                meta,
                                                                                invert,
                                                                                rh -> next.apply(rh),
                                                                                (rh, hdlr, cntent) -> hdlr.onAuthFailure(rh,
                                                                                cntent),
                                                                                ConstraintPoint.FILTER)));
    }

    /**
     * An arbitrary constraint that uses {@link be.objectify.deadbolt.java.DynamicResourceHandler#isAllowed(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * to determine access.
     *
     * @param name the name of the constraint
     * @return a function that wraps the constraint
     * @see ConstraintLogic#dynamic(Http.RequestHeader, DeadboltHandler, Optional, String, Optional, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction dynamic(final String name)
    {
        return dynamic(name,
                       Optional.empty());
    }

    /**
     * An arbitrary constraint that uses {@link be.objectify.deadbolt.java.DynamicResourceHandler#isAllowed(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * to determine access.
     *
     * @param name the name of the constraint
     * @param meta additional information passed to {@link be.objectify.deadbolt.java.DynamicResourceHandler#isAllowed(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * @return a function that wraps the constraint
     * @see ConstraintLogic#dynamic(Http.RequestHeader, DeadboltHandler, Optional, String, Optional, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction dynamic(final String name,
                                  final Optional<String> meta)
    {
        return dynamic(name,
                       meta,
                       Optional.empty());
    }

    /**
     * An arbitrary constraint that uses {@link be.objectify.deadbolt.java.DynamicResourceHandler#isAllowed(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * to determine access.
     *
     * @param name    the name of the constraint
     * @param meta    additional information passed to {@link be.objectify.deadbolt.java.DynamicResourceHandler#isAllowed(String, Optional, DeadboltHandler, Http.RequestHeader)}
     * @param content is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     * @see ConstraintLogic#dynamic(Http.RequestHeader, DeadboltHandler, Optional, String, Optional, Function, TriFunction, ConstraintPoint)
     */
    public FilterFunction dynamic(final String name,
                                  final Optional<String> meta,
                                  final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraintLogic.dynamic(maybePreAuth._2,
                                                                                handler,
                                                                                content,
                                                                                name,
                                                                                meta,
                                                                                rh -> next.apply(rh),
                                                                                (rh, hdlr, cntent) -> hdlr.onAuthFailure(rh,
                                                                                cntent),
                                                                                ConstraintPoint.FILTER)));
    }

    /**
     * A constraint that uses a {@link Constraint} to determine access.  This may be an arbitrarily complex
     * tree of constraints.
     *
     * @param name the name of the composite constraint defined in {@link CompositeCache}.
     * @return a function that wraps the constraint
     * @throws IllegalStateException if no constraint with the given name is present in the composite cache
     */
    public FilterFunction composite(final String name)
    {
        return composite(name,
                         Optional.empty());
    }

    /**
     * A constraint that uses a {@link Constraint} to determine access.  This may be an arbitrarily complex
     * tree of constraints.
     *
     * @param name    the name of the composite constraint defined in {@link CompositeCache}.
     * @param content is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     * @throws IllegalStateException if no constraint with the given name is present in the composite cache
     */
    public FilterFunction composite(final String name,
                                    final Optional<String> content)
    {
        return compositeCache.apply(name).map(constraint -> composite(constraint,
                                                                      content))
                             .orElseThrow(() -> new IllegalStateException(String.format("No constraint with name [%s] found",
                                                                                name)));
    }

    /**
     * A constraint that uses a {@link Constraint} to determine access.  This may be an arbitrarily complex
     * tree of constraints.
     *
     * @param constraint the composite constraint
     * @return a function that wraps the constraint
     */
    public FilterFunction composite(final Constraint constraint)
    {
        return composite(constraint,
                         Optional.empty());
    }

    /**
     * A constraint that uses a {@link Constraint} to determine access.  This may be an arbitrarily complex
     * tree of constraints.
     *
     * @param constraint the composite constraint
     * @param content    is passed to {@link DeadboltHandler#onAuthFailure(Http.RequestHeader, Optional)} if the authorization fails
     * @return a function that wraps the constraint
     */
    public FilterFunction composite(final Constraint constraint,
                                    final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraint.test(maybePreAuth._2,
                                                                                handler)
                                                                                .thenCompose(allowed -> allowed._1 ? ((Supplier<CompletionStage<Result>>) () -> {
                                                                                handler.onAuthSuccess(allowed._2,
                                                                                "composite",
                                                                                ConstraintPoint.FILTER);
                                                                                return next.apply(allowed._2);
                                                                                }).get()
                                                                                : handler.onAuthFailure(maybePreAuth._2,
                                                                                content))));
    }

    public FilterFunction roleBasedPermissions(final String roleName)
    {
        return roleBasedPermissions(roleName,
                                    Optional.empty());
    }

    public FilterFunction roleBasedPermissions(final String roleName,
                                               final Optional<String> content)
    {
        return (Http.RequestHeader requestHeader,
                DeadboltHandler handler,
                Function<Http.RequestHeader, CompletionStage<Result>> next) ->
                beforeAuthCheckCache.apply(handler, requestHeader, content)
                       .thenCompose(maybePreAuth -> maybePreAuth._1.map(preAuthResult -> (CompletionStage<Result>) CompletableFuture.completedFuture(preAuthResult))
                                                                .orElseGet(() -> constraintLogic.roleBasedPermissions(maybePreAuth._2,
                                                                                handler,
                                                                                content,
                                                                                roleName,
                                                                                rh -> next.apply(rh),
                                                                                (rh, hdlr, cntent) -> hdlr.onAuthFailure(rh,
                                                                                cntent),
                                                                                ConstraintPoint.FILTER)));
    }
}


/*
 * 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.utils;

import be.objectify.deadbolt.java.models.Permission;
import be.objectify.deadbolt.java.models.Role;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Convenience methods for templates.
 *
 * @author Steve Chaloner
 */
public class TemplateUtils
{
    /**
     * Converts the roles into a String array.
     *
     * @param roles the roles
     * @return the roles as an array of strings
     */
    public static String[] roles(final Role... roles)
    {
        final List<String> names = new ArrayList<>(roles.length);
        for (Role role : roles)
        {
            names.add(role.getName());
        }
        return names.toArray(new String[names.size()]);
    }

    /**
     * Converts the permissions into a String array.
     *
     * @param permissions the permissions
     * @return the permissions as an array of strings
     */
    public static String[] permissions(final Permission... permissions)
    {
        final List<String> values = new ArrayList<>(permissions.length);
        for (Permission permission : permissions)
        {
            values.add(permission.getValue());
        }
        return values.toArray(new String[values.size()]);
    }

    /**
     * Converts the arguments into a String array.
     *
     * @param args the arguments
     * @return the arguments as an array
     */
    public static String[] allOf(final String... args)
    {
        return args == null ? new String[0] : args;
    }

    /**
     * Converts the arguments into a String array wrapped in a list.
     *
     * @param args the arguments
     * @return the arguments as an array within a list
     */
    public static List<String[]> allOfGroup(final String... args)
    {
        return Collections.singletonList(args == null ? new String[0] : args);
    }

    /**
     * Converts the argument array into a List of String arrays.
     *
     * @param args the arguments
     * @return a non-null list containing the arguments
     */
    public static List<String[]> anyOf(final String[]... args)
    {
        return Arrays.asList(args);
    }
}