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

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

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

import static io.quarkus.security.PermissionsAllowed.PERMISSION_TO_ACTION_SEPARATOR;

import java.lang.reflect.InvocationTargetException;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import jakarta.enterprise.inject.Instance;

import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.security.StringPermission;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.PolicyConfig;
import io.quarkus.vertx.http.runtime.PolicyMappingConfig;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.AuthorizationRequestContext;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.CheckResult;
import io.quarkus.vertx.http.runtime.security.ImmutablePathMatcher.PathMatch;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

/**
 * A security policy that allows for matching of other security policies based on paths.
 * 

* This is used for the default path/method based RBAC. */ public class AbstractPathMatchingHttpSecurityPolicy { private static final String PATH_MATCHING_POLICY_FOUND = AbstractPathMatchingHttpSecurityPolicy.class.getName() + ".POLICY_FOUND"; private final ImmutablePathMatcher> pathMatcher; private final List>> sharedPermissionsPathMatchers; private final boolean hasNoPermissions; AbstractPathMatchingHttpSecurityPolicy(Map permissions, Map rolePolicy, String rootPath, Instance installedPolicies, PolicyMappingConfig.AppliesTo appliesTo) { boolean hasNoPermissions = true; var namedHttpSecurityPolicies = toNamedHttpSecPolicies(rolePolicy, installedPolicies); List>> sharedPermsMatchers = new ArrayList<>(); final var builder = ImmutablePathMatcher.> builder().handlerAccumulator(List::addAll) .rootPath(rootPath); for (PolicyMappingConfig policyMappingConfig : permissions.values()) { if (appliesTo != policyMappingConfig.appliesTo) { continue; } if (hasNoPermissions) { hasNoPermissions = false; } if (policyMappingConfig.shared) { final var builder1 = ImmutablePathMatcher.> builder().handlerAccumulator(List::addAll) .rootPath(rootPath); addPermissionToPathMatcher(namedHttpSecurityPolicies, policyMappingConfig, builder1); sharedPermsMatchers.add(builder1.build()); } else { addPermissionToPathMatcher(namedHttpSecurityPolicies, policyMappingConfig, builder); } } this.hasNoPermissions = hasNoPermissions; this.sharedPermissionsPathMatchers = sharedPermsMatchers.isEmpty() ? null : List.copyOf(sharedPermsMatchers); this.pathMatcher = builder.build(); } public String getAuthMechanismName(RoutingContext routingContext) { if (sharedPermissionsPathMatchers != null) { for (ImmutablePathMatcher> matcher : sharedPermissionsPathMatchers) { String authMechanismName = getAuthMechanismName(routingContext, matcher); if (authMechanismName != null) { return authMechanismName; } } } return getAuthMechanismName(routingContext, pathMatcher); } public boolean hasNoPermissions() { return hasNoPermissions; } public Uni checkPermission(RoutingContext routingContext, Uni identity, AuthorizationRequestContext requestContext) { return checkPermissions(routingContext, identity, requestContext); } Uni checkPermissions(RoutingContext routingContext, Uni identity, AuthorizationRequestContext requestContext, HttpSecurityPolicy... additionalPolicies) { final List permissionCheckers = hasNoPermissions ? new ArrayList<>() : getHttpSecurityPolicies(routingContext); if (additionalPolicies.length > 0) { if (additionalPolicies.length == 1) { permissionCheckers.add(additionalPolicies[0]); } else { permissionCheckers.addAll(Arrays.asList(additionalPolicies)); } } return doPermissionCheck(routingContext, identity, 0, null, permissionCheckers, requestContext); } private List getHttpSecurityPolicies(RoutingContext routingContext) { final List permissionCheckers; if (sharedPermissionsPathMatchers == null) { permissionCheckers = findPermissionCheckers(routingContext, pathMatcher); } else { permissionCheckers = new ArrayList<>(); for (ImmutablePathMatcher> matcher : sharedPermissionsPathMatchers) { permissionCheckers.addAll(findPermissionCheckers(routingContext, matcher)); } permissionCheckers.addAll(findPermissionCheckers(routingContext, pathMatcher)); } return permissionCheckers; } private Uni doPermissionCheck(RoutingContext routingContext, Uni identity, int index, SecurityIdentity augmentedIdentity, List permissionCheckers, AuthorizationRequestContext requestContext) { if (index == permissionCheckers.size()) { if (index > 0) { routingContext.put(PATH_MATCHING_POLICY_FOUND, true); } return Uni.createFrom().item(new CheckResult(true, augmentedIdentity)); } //get the current checker HttpSecurityPolicy res = permissionCheckers.get(index); return res.checkPermission(routingContext, identity, requestContext) .flatMap(new Function>() { @Override public Uni apply(CheckResult checkResult) { if (!checkResult.isPermitted()) { if (checkResult.getAugmentedIdentity() == null) { return CheckResult.deny(); } else { return Uni.createFrom().item(new CheckResult(false, checkResult.getAugmentedIdentity())); } } else { if (checkResult.getAugmentedIdentity() != null) { //attempt to run the next checker return doPermissionCheck(routingContext, checkResult.getAugmentedIdentityAsUni(), index + 1, checkResult.getAugmentedIdentity(), permissionCheckers, requestContext); } else { //attempt to run the next checker return doPermissionCheck(routingContext, identity, index + 1, augmentedIdentity, permissionCheckers, requestContext); } } } }); } private static String getAuthMechanismName(RoutingContext routingContext, ImmutablePathMatcher> pathMatcher) { PathMatch> toCheck = pathMatcher.match(routingContext.normalizedPath()); if (toCheck.getValue() == null || toCheck.getValue().isEmpty()) { return null; } for (HttpMatcher i : toCheck.getValue()) { if (i.authMechanism != null) { return i.authMechanism; } } return null; } private static void addPermissionToPathMatcher(Map permissionCheckers, PolicyMappingConfig policyMappingConfig, ImmutablePathMatcher.ImmutablePathMatcherBuilder> builder) { HttpSecurityPolicy checker = permissionCheckers.get(policyMappingConfig.policy); if (checker == null) { throw new RuntimeException("Unable to find HTTP security policy " + policyMappingConfig.policy); } if (policyMappingConfig.enabled.orElse(Boolean.TRUE)) { for (String path : policyMappingConfig.paths.orElse(Collections.emptyList())) { HttpMatcher m = new HttpMatcher(policyMappingConfig.authMechanism.orElse(null), new HashSet<>(policyMappingConfig.methods.orElse(Collections.emptyList())), checker); List perms = new ArrayList<>(); perms.add(m); builder.addPath(path, perms); } } } private static List findPermissionCheckers(RoutingContext context, ImmutablePathMatcher> pathMatcher) { var result = new ArrayList(); PathMatch> toCheck = pathMatcher.match(context.normalizedPath()); if (toCheck.getValue() == null || toCheck.getValue().isEmpty()) { return result; } List methodMatch = new ArrayList<>(); List noMethod = new ArrayList<>(); for (HttpMatcher i : toCheck.getValue()) { if (i.methods == null || i.methods.isEmpty()) { noMethod.add(i.checker); } else if (i.methods.contains(context.request().method().toString())) { methodMatch.add(i.checker); } } if (!methodMatch.isEmpty()) { result.addAll(methodMatch); } else if (!noMethod.isEmpty()) { result.addAll(noMethod); } else { //we deny if we did not match due to method filtering result.add(DenySecurityPolicy.INSTANCE); } return result; } static boolean policyApplied(RoutingContext routingContext) { return routingContext.get(PATH_MATCHING_POLICY_FOUND) != null; } private static Map toNamedHttpSecPolicies(Map rolePolicies, Instance installedPolicies) { Map namedPolicies = new HashMap<>(); for (Instance.Handle handle : installedPolicies.handles()) { if (handle.getBean().getBeanClass().getSuperclass() == AbstractPathMatchingHttpSecurityPolicy.class) { continue; } var policy = handle.get(); if (policy.name() != null) { if (policy.name().isBlank()) { throw new ConfigurationException("HTTP Security policy '" + policy + "' name must not be blank"); } namedPolicies.put(policy.name(), policy); } } for (Map.Entry e : rolePolicies.entrySet()) { final PolicyConfig policyConfig = e.getValue(); final Map> roleToPermissions; if (policyConfig.permissions.isEmpty()) { roleToPermissions = null; } else { roleToPermissions = new HashMap<>(); for (Map.Entry> roleToPermissionStr : policyConfig.permissions.entrySet()) { // collect permission actions // perm1:action1,perm2:action2,perm1:action3 -> perm1:action1,action3 and perm2:action2 Map cache = new HashMap<>(); final String role = roleToPermissionStr.getKey(); for (String permissionToAction : roleToPermissionStr.getValue()) { // parse permission to actions and add it to cache addPermissionToAction(cache, role, permissionToAction); } // create permissions var permissions = new HashSet(); for (PermissionToActions helper : cache.values()) { if (StringPermission.class.getName().equals(policyConfig.permissionClass)) { permissions.add(new StringPermission(helper.permissionName, helper.actions.toArray(new String[0]))); } else { permissions.add(customPermissionCreator(policyConfig, helper)); } } roleToPermissions.put(role, Set.copyOf(permissions)); } } namedPolicies.put(e.getKey(), new RolesAllowedHttpSecurityPolicy(policyConfig.rolesAllowed, roleToPermissions, policyConfig.roles)); } namedPolicies.put("deny", new DenySecurityPolicy()); namedPolicies.put("permit", new PermitSecurityPolicy()); namedPolicies.put("authenticated", new AuthenticatedHttpSecurityPolicy()); return namedPolicies; } private static boolean acceptsActions(String permissionClassStr) { var permissionClass = loadClass(permissionClassStr); if (permissionClass.getConstructors().length != 1) { throw new ConfigurationException( String.format("Permission class '%s' must have exactly one constructor", permissionClass)); } var constructor = permissionClass.getConstructors()[0]; // first parameter must be permission name (String) if (constructor.getParameterCount() == 0 || !(constructor.getParameterTypes()[0] == String.class)) { throw new ConfigurationException( String.format("Permission class '%s' constructor first parameter must be '%s' (permission name)", permissionClass, String.class.getName())); } final boolean acceptsActions; if (constructor.getParameterCount() == 1) { acceptsActions = false; } else { if (constructor.getParameterCount() == 2) { if (constructor.getParameterTypes()[1] != String[].class) { throw new ConfigurationException( String.format("Permission class '%s' constructor second parameter must be '%s' array", permissionClass, String.class.getName())); } } else { throw new ConfigurationException(String.format( "Permission class '%s' constructor must accept either one parameter (String permissionName), or two parameters (String permissionName, String[] actions)", permissionClass)); } acceptsActions = true; } return acceptsActions; } private static void addPermissionToAction(Map cache, String role, String permissionToAction) { final String permissionName; final String action; // incoming value is either in format perm1:action1 or perm1 (with or withot action) if (permissionToAction.contains(PERMISSION_TO_ACTION_SEPARATOR)) { // perm1:action1 var permToActions = permissionToAction.split(PERMISSION_TO_ACTION_SEPARATOR); if (permToActions.length != 2) { throw new ConfigurationException( String.format("Invalid permission format '%s', please use exactly one permission to action separator", permissionToAction)); } permissionName = permToActions[0].trim(); action = permToActions[1].trim(); } else { // perm1 permissionName = permissionToAction.trim(); action = null; } if (permissionName.isEmpty()) { throw new ConfigurationException( String.format("Invalid permission name '%s' for role '%s'", permissionToAction, role)); } cache.computeIfAbsent(permissionName, new Function() { @Override public PermissionToActions apply(String s) { return new PermissionToActions(s); } }).addAction(action); } private static Class loadClass(String className) { try { return Thread.currentThread().getContextClassLoader().loadClass(className); } catch (ClassNotFoundException e) { throw new RuntimeException("Unable to load class '" + className + "' for creating permission", e); } } private static Permission customPermissionCreator(PolicyConfig policyConfig, PermissionToActions helper) { try { var constructor = loadClass(policyConfig.permissionClass).getConstructors()[0]; if (acceptsActions(policyConfig.permissionClass)) { return (Permission) constructor.newInstance(helper.permissionName, helper.actions.toArray(new String[0])); } else { return (Permission) constructor.newInstance(helper.permissionName); } } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(String.format("Failed to create Permission - class '%s', name '%s', actions '%s'", policyConfig.permissionClass, helper.permissionName, Arrays.toString(helper.actions.toArray(new String[0]))), e); } } private static final class PermissionToActions { private final String permissionName; private final Set actions; private PermissionToActions(String permissionName) { this.permissionName = permissionName; this.actions = new HashSet<>(); } private void addAction(String action) { if (action != null) { this.actions.add(action); } } } record HttpMatcher(String authMechanism, Set methods, HttpSecurityPolicy checker) { } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy