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

io.quarkus.vertx.http.runtime.security.AbstractHttpAuthorizer Maven / Gradle / Ivy

The newest version!
package io.quarkus.vertx.http.runtime.security;

import static io.quarkus.security.spi.runtime.SecurityEventHelper.AUTHORIZATION_FAILURE;
import static io.quarkus.security.spi.runtime.SecurityEventHelper.AUTHORIZATION_SUCCESS;
import static io.quarkus.vertx.http.runtime.security.QuarkusHttpUser.setIdentity;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.spi.BeanManager;

import org.jboss.logging.Logger;

import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.security.AuthenticationRedirectException;
import io.quarkus.security.ForbiddenException;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.spi.runtime.AuthorizationController;
import io.quarkus.security.spi.runtime.AuthorizationFailureEvent;
import io.quarkus.security.spi.runtime.AuthorizationSuccessEvent;
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
import io.quarkus.security.spi.runtime.SecurityEventHelper;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.subscription.UniSubscriber;
import io.smallrye.mutiny.subscription.UniSubscription;
import io.vertx.ext.web.RoutingContext;

/**
 * Class that is responsible for running the HTTP based permission checks
 */
abstract class AbstractHttpAuthorizer {

    private static final Logger log = Logger.getLogger(AbstractHttpAuthorizer.class);

    private final HttpAuthenticator httpAuthenticator;
    private final IdentityProviderManager identityProviderManager;
    private final AuthorizationController controller;
    private final List policies;
    private final SecurityEventHelper securityEventHelper;
    private final HttpSecurityPolicy.AuthorizationRequestContext context;

    AbstractHttpAuthorizer(HttpAuthenticator httpAuthenticator, IdentityProviderManager identityProviderManager,
            AuthorizationController controller, List policies, BeanManager beanManager,
            BlockingSecurityExecutor blockingExecutor, Event authZFailureEvent,
            Event authZSuccessEvent, boolean securityEventsEnabled) {
        this.httpAuthenticator = httpAuthenticator;
        this.identityProviderManager = identityProviderManager;
        this.controller = controller;
        this.policies = policies;
        this.context = new HttpSecurityPolicy.DefaultAuthorizationRequestContext(blockingExecutor);
        this.securityEventHelper = new SecurityEventHelper<>(authZSuccessEvent, authZFailureEvent, AUTHORIZATION_SUCCESS,
                AUTHORIZATION_FAILURE, beanManager, securityEventsEnabled);
    }

    /**
     * Checks that the request is allowed to proceed. If it is then {@link RoutingContext#next()} will
     * be invoked, if not appropriate action will be taken to either report the failure or attempt authentication.
     */
    public void checkPermission(RoutingContext routingContext) {
        if (!controller.isAuthorizationEnabled()) {
            routingContext.next();
            return;
        }
        //check their permissions
        doPermissionCheck(routingContext, QuarkusHttpUser.getSecurityIdentity(routingContext, identityProviderManager), 0, null,
                policies);
    }

    private void doPermissionCheck(RoutingContext routingContext,
            Uni identity, int index,
            SecurityIdentity augmentedIdentity,
            List permissionCheckers) {
        if (index == permissionCheckers.size()) {
            QuarkusHttpUser currentUser = (QuarkusHttpUser) routingContext.user();
            if (augmentedIdentity != null) {
                if (!augmentedIdentity.isAnonymous()
                        && (currentUser == null || currentUser.getSecurityIdentity() != augmentedIdentity)) {
                    setIdentity(augmentedIdentity, routingContext);
                }
                if (securityEventHelper.fireEventOnSuccess()) {
                    securityEventHelper.fireSuccessEvent(new AuthorizationSuccessEvent(augmentedIdentity,
                            Map.of(RoutingContext.class.getName(), routingContext)));
                }
            } else if (securityEventHelper.fireEventOnSuccess()
                    && permissionCheckPerformed(permissionCheckers, routingContext, index)) {
                securityEventHelper.fireSuccessEvent(
                        new AuthorizationSuccessEvent(currentUser == null ? null : currentUser.getSecurityIdentity(),
                                Map.of(RoutingContext.class.getName(), routingContext)));
            }
            routingContext.next();
            return;
        }
        //get the current checker
        HttpSecurityPolicy res = permissionCheckers.get(index);
        res.checkPermission(routingContext, identity, context)
                .subscribe().with(new Consumer() {
                    @Override
                    public void accept(HttpSecurityPolicy.CheckResult checkResult) {
                        if (!checkResult.isPermitted()) {
                            doDeny(identity, routingContext, res, checkResult.getAugmentedIdentity());
                        } else {
                            if (checkResult.getAugmentedIdentity() != null) {
                                doPermissionCheck(routingContext, checkResult.getAugmentedIdentityAsUni(),
                                        index + 1, checkResult.getAugmentedIdentity(), permissionCheckers);
                            } else {
                                //attempt to run the next checker
                                doPermissionCheck(routingContext, identity, index + 1, augmentedIdentity, permissionCheckers);
                            }
                        }
                    }
                }, new Consumer() {
                    @Override
                    public void accept(Throwable throwable) {
                        // we don't fail event if it's already failed with same exception as we don't want to process
                        // the exception twice;at this point, the exception could be failed by the default auth failure handler
                        if (!routingContext.response().ended() && !throwable.equals(routingContext.failure())) {
                            routingContext.fail(throwable);
                        } else if (throwable instanceof AuthenticationFailedException) {
                            log.debug("Authentication challenge is required");
                        } else if (throwable instanceof AuthenticationRedirectException) {
                            log.debugf("Completing authentication with a redirect to %s",
                                    ((AuthenticationRedirectException) throwable).getRedirectUri());
                        } else {
                            log.error("Exception occurred during authorization", throwable);
                        }

                    }
                });
    }

    private void doDeny(SecurityIdentity identity, RoutingContext routingContext, HttpSecurityPolicy policy) {
        //if we were denied we send a challenge if we are not authenticated, otherwise we send a 403
        if (identity.isAnonymous()) {
            httpAuthenticator.sendChallenge(routingContext).subscribe().withSubscriber(new UniSubscriber() {
                @Override
                public void onSubscribe(UniSubscription subscription) {

                }

                @Override
                public void onItem(Boolean item) {
                    if (!routingContext.response().ended()) {
                        routingContext.response().end();
                    }
                    fireAuthZFailureEvent(routingContext, policy, null, identity);
                }

                @Override
                public void onFailure(Throwable failure) {
                    fireAuthZFailureEvent(routingContext, policy, failure, identity);
                    if (!routingContext.response().ended()) {
                        routingContext.fail(failure);
                    } else if (!(failure instanceof IOException)) {
                        log.error("Failed to send challenge", failure);
                    } else {
                        log.debug("Failed to send challenge", failure);
                    }
                }
            });
        } else {
            ForbiddenException forbiddenException = new ForbiddenException();
            fireAuthZFailureEvent(routingContext, policy, forbiddenException, identity);
            routingContext.fail(new ForbiddenException());
        }
    }

    private void doDeny(Uni identity, RoutingContext routingContext, HttpSecurityPolicy policy,
            SecurityIdentity augmentedIdentity) {
        if (augmentedIdentity == null) {
            identity.subscribe().withSubscriber(new UniSubscriber() {
                @Override
                public void onSubscribe(UniSubscription subscription) {

                }

                @Override
                public void onItem(SecurityIdentity identity) {
                    doDeny(identity, routingContext, policy);
                }

                @Override
                public void onFailure(Throwable failure) {
                    fireAuthZFailureEvent(routingContext, policy, failure, null);
                    routingContext.fail(failure);
                }
            });
        } else {
            doDeny(augmentedIdentity, routingContext, policy);
        }
    }

    private void fireAuthZFailureEvent(RoutingContext routingContext, HttpSecurityPolicy policy, Throwable failure,
            SecurityIdentity identity) {
        if (securityEventHelper.fireEventOnFailure()) {
            final String context = policy != null ? policy.getClass().getName() : null;
            final AuthorizationFailureEvent event = new AuthorizationFailureEvent(identity, failure, context,
                    Map.of(RoutingContext.class.getName(), routingContext));
            securityEventHelper.fireFailureEvent(event);
        }
    }

    private static boolean permissionCheckPerformed(List permissionCheckers,
            RoutingContext routingContext, int index) {
        // the path matching policy is not permission check itself, it selects policy based on
        // configured HTTP permissions, but if there are no path matching HTTP permissions, there is no check
        if (index == 1 && permissionCheckers.get(0) instanceof AbstractPathMatchingHttpSecurityPolicy) {
            return AbstractPathMatchingHttpSecurityPolicy.policyApplied(routingContext);
        }
        return index > 0;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy