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

io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerAuthorizer Maven / Gradle / Ivy

Go to download

Policy enforcer using Keycloak-managed permissions to control access to protected resources

There is a newer version: 3.18.0.CR1
Show newest version
package io.quarkus.keycloak.pep.runtime;

import static io.quarkus.oidc.runtime.OidcUtils.TENANT_ID_ATTRIBUTE;

import java.security.Permission;
import java.util.function.Function;
import java.util.function.Supplier;

import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;

import io.quarkus.arc.Arc;
import io.quarkus.keycloak.pep.PolicyEnforcerResolver;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.runtime.BlockingOperationControl;
import io.quarkus.runtime.BlockingOperationNotAllowedException;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@Singleton
public class KeycloakPolicyEnforcerAuthorizer implements HttpSecurityPolicy {
    private static final String PERMISSIONS_ATTRIBUTE = "permissions";
    private static final String POLICY_ENFORCER = "io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerAuthorizer#POLICY_ENFORCER";

    @Inject
    PolicyEnforcerResolver resolver;

    @Inject
    Instance identityInstance;

    @Inject
    BlockingSecurityExecutor blockingExecutor;

    @Override
    public Uni checkPermission(RoutingContext routingContext, Uni identity,
            AuthorizationRequestContext requestContext) {
        return identity.flatMap(new Function>() {
            @Override
            public Uni apply(SecurityIdentity identity) {
                if (identity.isAnonymous()) {
                    return resolver.resolvePolicyEnforcer(routingContext, null)
                            .flatMap(new Function>() {
                                @Override
                                public Uni apply(PolicyEnforcer policyEnforcer) {
                                    storePolicyEnforcerOnContext(policyEnforcer, routingContext);
                                    return blockingExecutor.executeBlocking(new Supplier() {
                                        @Override
                                        public PathConfig get() {
                                            return policyEnforcer.getPathMatcher().matches(routingContext.normalizedPath());
                                        }
                                    }).flatMap(new Function>() {
                                        @Override
                                        public Uni apply(PathConfig pathConfig) {
                                            if (pathConfig != null
                                                    && pathConfig.getEnforcementMode() == EnforcementMode.ENFORCING) {
                                                return CheckResult.deny();
                                            }
                                            return checkPermissionInternal(routingContext, identity);
                                        }
                                    });
                                }
                            });
                }
                return checkPermissionInternal(routingContext, identity);
            }
        });
    }

    @Produces
    @RequestScoped
    public AuthzClient getAuthzClient() {
        SecurityIdentity identity = identityInstance.get();
        final RoutingContext routingContext;
        if (identity.getAttribute(RoutingContext.class.getName()) != null) {
            routingContext = identity.getAttribute(RoutingContext.class.getName());
        } else {
            routingContext = Arc.container().instance(CurrentVertxRequest.class).get().getCurrent();
        }

        if (routingContext != null && routingContext.get(POLICY_ENFORCER) != null) {
            return routingContext. get(POLICY_ENFORCER).getAuthzClient();
        } else if (BlockingOperationControl.isBlockingAllowed()) {
            OidcTenantConfig tenantConfig = routingContext == null ? null
                    : routingContext.get(OidcTenantConfig.class.getName());
            return resolver.resolvePolicyEnforcer(routingContext, tenantConfig)
                    .await().indefinitely()
                    .getAuthzClient();
        } else {
            if (resolver instanceof DefaultPolicyEnforcerResolver defaultResolver
                    && !defaultResolver.hasDynamicPolicyEnforcers()) {
                return defaultResolver.getStaticPolicyEnforcer(identity.getAttribute(TENANT_ID_ATTRIBUTE)).getAuthzClient();
            } else {
                // this shouldn't happen inside HTTP request as policy enforcer is in most cases accessible from context
                // and the Authz client itself is blocking so users can as well inject it when on the worker thread
                throw new BlockingOperationNotAllowedException("""
                        You have attempted to inject AuthzClient on a IO thread.
                        This is not allowed when PolicyEnforcer is resolved dynamically as blocking operations are required.
                        Make sure you are injecting AuthzClient from a worker thread.
                        """);
            }
        }
    }

    private Uni checkPermissionInternal(RoutingContext routingContext, SecurityIdentity identity) {
        AccessTokenCredential credential = identity.getCredential(AccessTokenCredential.class);

        if (credential == null) {
            // SecurityIdentity has been created by the authentication mechanism other than quarkus-oidc
            return CheckResult.permit();
        }

        VertxHttpFacade httpFacade = new VertxHttpFacade(routingContext, credential.getToken(), resolver.getReadTimeout());
        return resolver.resolvePolicyEnforcer(routingContext, routingContext.get(OidcTenantConfig.class.getName()))
                .flatMap(new Function>() {
                    @Override
                    public Uni apply(PolicyEnforcer policyEnforcer) {
                        storePolicyEnforcerOnContext(policyEnforcer, routingContext);
                        return blockingExecutor.executeBlocking(new Supplier() {
                            @Override
                            public AuthorizationContext get() {
                                return policyEnforcer.enforce(httpFacade, httpFacade);
                            }
                        });
                    }
                }).map(new Function() {
                    @Override
                    public CheckResult apply(AuthorizationContext authorizationContext) {
                        if (authorizationContext.isGranted()) {
                            return new CheckResult(true, enhanceSecurityIdentity(identity, authorizationContext));
                        }
                        return CheckResult.DENY;
                    }
                });
    }

    private static void storePolicyEnforcerOnContext(PolicyEnforcer policyEnforcer, RoutingContext routingContext) {
        routingContext.put(POLICY_ENFORCER, policyEnforcer);
    }

    private static SecurityIdentity enhanceSecurityIdentity(SecurityIdentity current, AuthorizationContext context) {
        return QuarkusSecurityIdentity
                .builder(current)
                .addAttribute(PERMISSIONS_ATTRIBUTE, context.getPermissions())
                .addPermissionChecker(new Function>() {
                    @Override
                    public Uni apply(Permission permission) {
                        String scopes = permission.getActions();

                        if (scopes == null || scopes.isEmpty()) {
                            return Uni.createFrom().item(context.hasResourcePermission(permission.getName()));
                        }

                        for (String scope : scopes.split(",")) {
                            if (!context.hasPermission(permission.getName(), scope)) {
                                return Uni.createFrom().item(false);
                            }
                        }

                        return Uni.createFrom().item(true);
                    }
                })
                .build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy