diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactor.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactor.java index fccc6660409..fe070987c4f 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactor.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactor.java @@ -57,7 +57,7 @@ import io.gravitee.gateway.reactive.handlers.api.flow.FlowChain; import io.gravitee.gateway.reactive.handlers.api.flow.FlowChainFactory; import io.gravitee.gateway.reactive.handlers.api.processor.ApiProcessorChainFactory; -import io.gravitee.gateway.reactive.handlers.api.security.SecurityChain; +import io.gravitee.gateway.reactive.handlers.api.security.HttpSecurityChain; import io.gravitee.gateway.reactive.handlers.api.v4.analytics.logging.LoggingHook; import io.gravitee.gateway.reactive.policy.PolicyManager; import io.gravitee.gateway.reactive.reactor.ApiReactor; @@ -119,7 +119,7 @@ public class SyncApiReactor extends AbstractLifecycleComponent i private final AtomicInteger pendingRequests = new AtomicInteger(0); private final long pendingRequestsTimeout; protected AnalyticsContext analyticsContext; - protected SecurityChain securityChain; + protected HttpSecurityChain httpSecurityChain; public SyncApiReactor( final Api api, @@ -231,7 +231,7 @@ private Completable handleRequest(final MutableExecutionContext ctx) { // Before Security Chain. .andThen(executeProcessorChain(ctx, beforeSecurityChainProcessors, REQUEST)) // Execute security chain. - .andThen(securityChain.execute(ctx)) + .andThen(httpSecurityChain.execute(ctx)) // Execute before flows processors .andThen(executeProcessorChain(ctx, beforeApiFlowsProcessors, REQUEST)) .andThen(executeFlowChain(ctx, apiPlanFlowChain, REQUEST)) @@ -430,8 +430,8 @@ protected void doStart() throws Exception { dumpVirtualHosts(); - // Create securityChain once policy manager has been started. - this.securityChain = new SecurityChain(api.getDefinition(), policyManager, REQUEST); + // Create httpSecurityChain once policy manager has been started. + this.httpSecurityChain = new HttpSecurityChain(api.getDefinition(), policyManager, REQUEST); tracingContext.start(); this.analyticsContext = @@ -444,7 +444,7 @@ protected void doStart() throws Exception { processorChainHooks.add(new TracingHook("Processor chain")); } invokerHooks.add(new InvokerTracingHook("Invoker")); - securityChain.addHooks(new TracingHook("Security plan")); + httpSecurityChain.addHooks(new TracingHook("Security plan")); } long endTime = System.currentTimeMillis(); // Get the end Time diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChain.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/AbstractSecurityChain.java similarity index 57% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChain.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/AbstractSecurityChain.java index bcc481fd2c5..71a73317c86 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChain.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/AbstractSecurityChain.java @@ -22,84 +22,60 @@ import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_SECURITY_TOKEN; import static io.reactivex.rxjava3.core.Completable.defer; -import io.gravitee.definition.model.Api; import io.gravitee.gateway.reactive.api.ExecutionFailure; -import io.gravitee.gateway.reactive.api.ExecutionPhase; -import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; -import io.gravitee.gateway.reactive.api.hook.Hookable; -import io.gravitee.gateway.reactive.api.hook.SecurityPlanHook; -import io.gravitee.gateway.reactive.core.hook.HookHelper; -import io.gravitee.gateway.reactive.handlers.api.security.plan.SecurityPlan; -import io.gravitee.gateway.reactive.handlers.api.security.plan.SecurityPlanFactory; -import io.gravitee.gateway.reactive.policy.PolicyManager; +import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext; +import io.gravitee.gateway.reactive.api.policy.base.BaseSecurityPolicy; +import io.gravitee.gateway.reactive.handlers.api.security.plan.AbstractSecurityPlan; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** - * {@link SecurityChain} is a special chain dedicated to execute policy associated with plans. - * The security chain is responsible to create {@link SecurityPlan} for each plan of the api and executed them in order. - * Only the first {@link SecurityPlan} that can handle the current request is executed. - * The result of the security chain execution depends on this {@link SecurityPlan} execution. + * {@link AbstractSecurityChain} is a special chain dedicated to execute policy associated with plans. + * The security chain is responsible to create {@link AbstractSecurityPlan} for each plan of the api and executed them in order. + * Only the first {@link AbstractSecurityPlan} that can handle the current request is executed. + * The result of the security chain execution depends on this {@link AbstractSecurityPlan} execution. * * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) * @author GraviteeSource Team */ -public class SecurityChain implements Hookable { +@Slf4j +public abstract class AbstractSecurityChain< + P extends AbstractSecurityPlan, C extends BaseExecutionContext +> { protected static final String PLAN_UNRESOLVABLE = "GATEWAY_PLAN_UNRESOLVABLE"; protected static final String PLAN_RESOLUTION_FAILURE = "GATEWAY_PLAN_RESOLUTION_FAILURE"; protected static final String UNAUTHORIZED_MESSAGE = "Unauthorized"; protected static final String TEMPORARILY_UNAVAILABLE_MESSAGE = "Temporarily Unavailable"; - protected static final String ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE = "securityChain.planResolutionFailure"; + protected static final String ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE = "httpSecurityChain.planResolutionFailure"; protected static final Single TRUE = Single.just(true); protected static final Single FALSE = Single.just(false); - private static final Logger log = LoggerFactory.getLogger(SecurityChain.class); - private final Flowable chain; - private final ExecutionPhase executionPhase; + private final Flowable

chain; - private List securityPlanHooks; - - public SecurityChain(Api api, PolicyManager policyManager, ExecutionPhase executionPhase) { - this( - Flowable.fromIterable( - api - .getPlans() - .stream() - .map(plan -> SecurityPlanFactory.forPlan(plan, policyManager)) - .filter(Objects::nonNull) - .sorted(Comparator.comparingInt(SecurityPlan::order)) - .collect(Collectors.toList()) - ), - executionPhase - ); - } - - public SecurityChain(Flowable securityPlans, ExecutionPhase executionPhase) { + public AbstractSecurityChain(Flowable

securityPlans) { this.chain = securityPlans; - this.executionPhase = executionPhase; } + protected abstract Completable sendError(C ctx, ExecutionFailure failure); + + protected abstract Single executePlan(P securityPlan, C ctx); + /** - * Executes the security chain by executing all the {@link SecurityPlan}s in an ordered sequence. - * It's up to each {@link SecurityPlan} to provide its order. The lower is the order, the highest priority is. - * The result of the security chain execution depends on the first {@link SecurityPlan} able to execute the request. - * If no {@link SecurityPlan} has been executed because there is no {@link SecurityPlan} in the chain or none of them can execute the request, - * then the chain is interrupted with a 401 response status and the {@link Completable} returns an error. + * Executes the security chain by executing all the {@link AbstractSecurityPlan}s in an ordered sequence. + * It's up to each {@link AbstractSecurityPlan} to provide its order. The lower is the order, the highest priority is. + * The result of the security chain execution depends on the first {@link AbstractSecurityPlan} able to execute the request. + * If no {@link AbstractSecurityPlan} has been executed because there is no {@link AbstractSecurityPlan} in the chain or none of them can execute the request, + * then the `sendError` method is called and the {@link Completable} returns an error. * * @param ctx the current execution context. - * @return a {@link Completable} that completes if the request has been successfully handled by a {@link SecurityPlan} or returns - * an error if no {@link SecurityPlan} can execute the request or the {@link SecurityPlan} failed. + * @return a {@link Completable} that completes if the request has been successfully handled by a {@link AbstractSecurityPlan} or returns + * an error if no {@link AbstractSecurityPlan} can execute the request or the {@link AbstractSecurityPlan} failed. */ - public Completable execute(HttpExecutionContext ctx) { + public Completable execute(C ctx) { return defer(() -> { if (!Objects.equals(true, ctx.getInternalAttribute(ATTR_INTERNAL_SECURITY_SKIP))) { return chain @@ -109,13 +85,15 @@ public Completable execute(HttpExecutionContext ctx) { if (Boolean.FALSE.equals(securityHandled)) { Throwable throwable = ctx.getInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE); if (throwable != null) { - return ctx.interruptWith( + return sendError( + ctx, new ExecutionFailure(SERVICE_UNAVAILABLE_503) .key(PLAN_RESOLUTION_FAILURE) .message(TEMPORARILY_UNAVAILABLE_MESSAGE) ); } - return ctx.interruptWith( + return sendError( + ctx, new ExecutionFailure(UNAUTHORIZED_401).key(PLAN_UNRESOLVABLE).message(UNAUTHORIZED_MESSAGE) ); } @@ -137,7 +115,7 @@ public Completable execute(HttpExecutionContext ctx) { }); } - private Single continueChain(HttpExecutionContext ctx, SecurityPlan securityPlan) { + private Single continueChain(C ctx, P securityPlan) { return securityPlan .canExecute(ctx) .onErrorResumeNext(throwable -> { @@ -147,19 +125,9 @@ private Single continueChain(HttpExecutionContext ctx, SecurityPlan sec }) .flatMap(canExecute -> { if (Boolean.TRUE.equals(canExecute)) { - return HookHelper - .hook(() -> securityPlan.execute(ctx, executionPhase), securityPlan.id(), securityPlanHooks, ctx, executionPhase) - .andThen(TRUE); + return executePlan(securityPlan, ctx); } return FALSE; }); } - - @Override - public void addHooks(final List hooks) { - if (this.securityPlanHooks == null) { - this.securityPlanHooks = new ArrayList<>(); - } - this.securityPlanHooks.addAll(hooks); - } } diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/HttpSecurityChain.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/HttpSecurityChain.java new file mode 100644 index 00000000000..935ae220023 --- /dev/null +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/HttpSecurityChain.java @@ -0,0 +1,100 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.gateway.reactive.handlers.api.security; + +import io.gravitee.definition.model.Api; +import io.gravitee.gateway.reactive.api.ExecutionFailure; +import io.gravitee.gateway.reactive.api.ExecutionPhase; +import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; +import io.gravitee.gateway.reactive.api.context.http.HttpPlainExecutionContext; +import io.gravitee.gateway.reactive.api.hook.Hookable; +import io.gravitee.gateway.reactive.api.hook.SecurityPlanHook; +import io.gravitee.gateway.reactive.core.hook.HookHelper; +import io.gravitee.gateway.reactive.handlers.api.security.plan.HttpSecurityPlan; +import io.gravitee.gateway.reactive.handlers.api.security.plan.HttpSecurityPlanFactory; +import io.gravitee.gateway.reactive.policy.PolicyManager; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Single; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * {@link HttpSecurityChain} is a special chain dedicated to execute policy associated with plans. + * The security chain is responsible to create {@link HttpSecurityPlan} for each plan of the api and executed them in order. + * Only the first {@link HttpSecurityPlan} that can handle the current request is executed. + * The result of the security chain execution depends on this {@link HttpSecurityPlan} execution. + * + * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) + * @author GraviteeSource Team + */ +public class HttpSecurityChain + extends AbstractSecurityChain + implements Hookable { + + private final ExecutionPhase executionPhase; + + private List securityPlanHooks; + + public HttpSecurityChain(Api api, PolicyManager policyManager, ExecutionPhase executionPhase) { + super( + Flowable.fromIterable( + api + .getPlans() + .stream() + .map(plan -> HttpSecurityPlanFactory.forPlan(plan, policyManager)) + .filter(Objects::nonNull) + .sorted(Comparator.comparingInt(HttpSecurityPlan::order)) + .collect(Collectors.toList()) + ) + ); + this.executionPhase = executionPhase; + } + + public HttpSecurityChain(Flowable securityPlans, ExecutionPhase executionPhase) { + super(securityPlans); + this.executionPhase = executionPhase; + } + + @Override + protected Completable sendError(HttpPlainExecutionContext ctx, ExecutionFailure failure) { + return ctx.interruptWith(failure); + } + + @Override + protected Single executePlan(HttpSecurityPlan httpSecurityPlan, HttpPlainExecutionContext ctx) { + return HookHelper + .hook( + () -> httpSecurityPlan.execute(ctx, executionPhase), + httpSecurityPlan.id(), + securityPlanHooks, + (HttpExecutionContext) ctx, + executionPhase + ) + .andThen(TRUE); + } + + @Override + public void addHooks(final List hooks) { + if (this.securityPlanHooks == null) { + this.securityPlanHooks = new ArrayList<>(); + } + this.securityPlanHooks.addAll(hooks); + } +} diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlan.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/AbstractSecurityPlan.java similarity index 73% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlan.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/AbstractSecurityPlan.java index f4f2825d6d8..958b1612bb5 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlan.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/AbstractSecurityPlan.java @@ -26,20 +26,18 @@ import io.gravitee.gateway.api.service.Subscription; import io.gravitee.gateway.api.service.SubscriptionService; import io.gravitee.gateway.reactive.api.ExecutionPhase; -import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; -import io.gravitee.gateway.reactive.api.context.http.HttpPlainExecutionContext; +import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext; import io.gravitee.gateway.reactive.api.policy.SecurityToken; -import io.gravitee.gateway.reactive.api.policy.http.HttpSecurityPolicy; +import io.gravitee.gateway.reactive.api.policy.base.BaseSecurityPolicy; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; import jakarta.annotation.Nonnull; import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** - * {@link SecurityPlan} allows to wrap a {@link io.gravitee.gateway.reactive.api.policy.http.HttpPolicy} implementing {@link HttpSecurityPolicy} and make it working in a security chain. + * {@link AbstractSecurityPlan} allows to wrap a {@link BaseSecurityPolicy} and make it working in a security chain. * Security plan is responsible to *

    *
  • Check if a policy can handle the security or not
  • @@ -51,16 +49,16 @@ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) * @author GraviteeSource Team */ -public class SecurityPlan { +@Slf4j +public abstract class AbstractSecurityPlan { protected static final Maybe TRUE = Maybe.just(true); protected static final Maybe FALSE = Maybe.just(false); - private static final Logger log = LoggerFactory.getLogger(SecurityPlan.class); + protected final T policy; private final String planId; - private final HttpSecurityPolicy policy; private final String selectionRule; - public SecurityPlan(@Nonnull final String planId, @Nonnull final HttpSecurityPolicy policy, final String selectionRule) { + public AbstractSecurityPlan(@Nonnull final String planId, @Nonnull final T policy, final String selectionRule) { this.planId = planId; this.policy = policy; this.selectionRule = getSelectionRule(selectionRule); @@ -76,9 +74,8 @@ public String id() { * @param ctx the current execution context. * @return true if this security plan can be executed for the request, false otherwise. */ - public Single canExecute(HttpPlainExecutionContext ctx) { - return policy - .extractSecurityToken(ctx) + public Single canExecute(C ctx) { + return extractSecurityToken(ctx) .flatMap(securityToken -> { ctx.setInternalAttribute(ATTR_INTERNAL_SECURITY_TOKEN, securityToken); if (!securityToken.isInvalid()) { @@ -90,44 +87,26 @@ public Single canExecute(HttpPlainExecutionContext ctx) { } /** - * Invokes the policy's onRequest method. + * Invokes the policy's method for security. * * @param ctx the current execution context. * @return a {@link Completable} that completes when the security policy has been successfully executed or returns an error otherwise. */ - public Completable execute(final HttpPlainExecutionContext ctx, final ExecutionPhase executionPhase) { + public Completable execute(final C ctx, final ExecutionPhase executionPhase) { return executeSecurityPolicy(ctx, executionPhase).doOnSubscribe(disposable -> ctx.setAttribute(ATTR_PLAN, planId)); } - private Completable executeSecurityPolicy(final HttpPlainExecutionContext ctx, final ExecutionPhase executionPhase) { - switch (executionPhase) { - case REQUEST: - return policy.onRequest(ctx); - case MESSAGE_REQUEST: - case RESPONSE: - case MESSAGE_RESPONSE: - default: - throw new IllegalArgumentException("Execution phase unsupported for security plan execution"); - } - } - public int order() { return policy.order(); } - private String getSelectionRule(String selectionRule) { - if (selectionRule == null) { - return null; - } + protected abstract Maybe extractSecurityToken(final C executionContext); - if (selectionRule.startsWith("#")) { - // Backward compatibility. In V3 mode selection rule EL expression based can be defined with "#something" while it is usually defined with "{#something}" everywhere else. - return "{" + selectionRule + "}"; - } - return selectionRule; - } + protected abstract Completable executeSecurityPolicy(final C ctx, final ExecutionPhase executionPhase); + + protected abstract String getSelectionRule(String selectionRule); - private Maybe isApplicableWithValidSubscription(HttpPlainExecutionContext ctx, SecurityToken securityToken) { + private Maybe isApplicableWithValidSubscription(BaseExecutionContext ctx, SecurityToken securityToken) { if (selectionRule == null || selectionRule.isEmpty()) { return Maybe.just(validateSubscription(ctx, securityToken)); } @@ -145,7 +124,7 @@ private Maybe isApplicableWithValidSubscription(HttpPlainExecutionConte }); } - private boolean validateSubscription(HttpPlainExecutionContext ctx, SecurityToken securityToken) { + private boolean validateSubscription(BaseExecutionContext ctx, SecurityToken securityToken) { Boolean validateSubscriptionEnabled = ctx.getInternalAttribute(ATTR_INTERNAL_VALIDATE_SUBSCRIPTION); // Skip validating the subscription @@ -166,7 +145,7 @@ private boolean validateSubscription(HttpPlainExecutionContext ctx, SecurityToke if (subscriptionOpt.isPresent()) { Subscription subscription = subscriptionOpt.get(); - if (planId.equals(subscription.getPlan()) && subscription.isTimeValid(ctx.request().timestamp())) { + if (planId.equals(subscription.getPlan()) && subscription.isTimeValid(ctx.timestamp())) { ctx.setAttribute(ATTR_APPLICATION, subscription.getApplication()); ctx.setAttribute(ATTR_SUBSCRIPTION_ID, subscription.getId()); ctx.setInternalAttribute(ATTR_INTERNAL_SUBSCRIPTION, subscription); diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlan.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlan.java new file mode 100644 index 00000000000..8a7a60e5c59 --- /dev/null +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlan.java @@ -0,0 +1,70 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.gateway.reactive.handlers.api.security.plan; + +import io.gravitee.gateway.reactive.api.ExecutionPhase; +import io.gravitee.gateway.reactive.api.context.http.HttpPlainExecutionContext; +import io.gravitee.gateway.reactive.api.policy.SecurityToken; +import io.gravitee.gateway.reactive.api.policy.http.HttpSecurityPolicy; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Maybe; +import jakarta.annotation.Nonnull; + +/** + * {@link HttpSecurityPlan} allows to wrap a {@link io.gravitee.gateway.reactive.api.policy.http.HttpPolicy} implementing {@link HttpSecurityPolicy} and make it working in a security chain. + * Security plan is responsible to + *
      + *
    • Check if a policy can handle the security or not
    • + *
    • Check the eventual selection rule matches (useful when dealing with multiple plans relying on the same security scheme such as Authorization: bearer xxx for JWT and OAuth)
    • + *
    • Check if the policy requires to validate there is an associated subscription or not and validate the subscription accordingly
    • + *
    • Invoke the onSubscriptionInvalid method when necessary
    • + *
    + * + * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) + * @author GraviteeSource Team + */ +public class HttpSecurityPlan extends AbstractSecurityPlan { + + public HttpSecurityPlan(@Nonnull final String planId, @Nonnull final HttpSecurityPolicy policy, final String selectionRule) { + super(planId, policy, selectionRule); + } + + @Override + protected Maybe extractSecurityToken(HttpPlainExecutionContext executionContext) { + return policy.extractSecurityToken(executionContext); + } + + @Override + protected Completable executeSecurityPolicy(final HttpPlainExecutionContext ctx, final ExecutionPhase executionPhase) { + if (ExecutionPhase.REQUEST == executionPhase) { + return policy.onRequest(ctx); + } + throw new IllegalArgumentException("Execution phase unsupported for security plan execution"); + } + + @Override + protected String getSelectionRule(String selectionRule) { + if (selectionRule == null) { + return null; + } + + if (selectionRule.startsWith("#")) { + // Backward compatibility. In V3 mode selection rule EL expression based can be defined with "#something" while it is usually defined with "{#something}" everywhere else. + return "{" + selectionRule + "}"; + } + return selectionRule; + } +} diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlanFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlanFactory.java similarity index 81% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlanFactory.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlanFactory.java index dc3ae56bdc8..87ee58564a5 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlanFactory.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlanFactory.java @@ -28,18 +28,18 @@ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) * @author GraviteeSource Team */ -public class SecurityPlanFactory { +public class HttpSecurityPlanFactory { - private static final Logger log = LoggerFactory.getLogger(SecurityPlanFactory.class); + private static final Logger log = LoggerFactory.getLogger(HttpSecurityPlanFactory.class); - private SecurityPlanFactory() {} + private HttpSecurityPlanFactory() {} @Nullable - public static SecurityPlan forPlan(@Nonnull Plan plan, @Nonnull PolicyManager policyManager) { + public static HttpSecurityPlan forPlan(@Nonnull Plan plan, @Nonnull PolicyManager policyManager) { final HttpSecurityPolicy policy = SecurityPolicyFactory.forPlan(plan, policyManager); if (policy != null) { - return new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + return new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); } log.warn( diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/AbstractReactorFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/AbstractReactorFactory.java new file mode 100644 index 00000000000..f7cab02679f --- /dev/null +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/AbstractReactorFactory.java @@ -0,0 +1,206 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.gateway.reactive.handlers.api.v4; + +import io.gravitee.definition.model.v4.AbstractApi; +import io.gravitee.el.TemplateVariableProvider; +import io.gravitee.gateway.core.classloader.DefaultClassLoader; +import io.gravitee.gateway.core.component.ComponentProvider; +import io.gravitee.gateway.core.component.CompositeComponentProvider; +import io.gravitee.gateway.core.component.CustomComponentProvider; +import io.gravitee.gateway.policy.PolicyConfigurationFactory; +import io.gravitee.gateway.policy.impl.CachedPolicyConfigurationFactory; +import io.gravitee.gateway.reactive.api.context.DeploymentContext; +import io.gravitee.gateway.reactive.core.context.DefaultDeploymentContext; +import io.gravitee.gateway.reactive.handlers.api.el.ApiTemplateVariableProvider; +import io.gravitee.gateway.reactive.policy.PolicyFactoryManager; +import io.gravitee.gateway.reactive.policy.PolicyManager; +import io.gravitee.gateway.reactive.reactor.ApiReactor; +import io.gravitee.gateway.reactive.reactor.v4.reactor.ReactorFactory; +import io.gravitee.gateway.reactor.Reactable; +import io.gravitee.gateway.reactor.ReactableApi; +import io.gravitee.gateway.reactor.handler.context.ApiTemplateVariableProviderFactory; +import io.gravitee.gateway.resource.ResourceConfigurationFactory; +import io.gravitee.gateway.resource.ResourceLifecycleManager; +import io.gravitee.gateway.resource.internal.ResourceConfigurationFactoryImpl; +import io.gravitee.gateway.resource.internal.v4.DefaultResourceManager; +import io.gravitee.node.api.configuration.Configuration; +import io.gravitee.plugin.core.api.ConfigurablePluginManager; +import io.gravitee.plugin.policy.PolicyClassLoaderFactory; +import io.gravitee.plugin.policy.PolicyPlugin; +import io.gravitee.plugin.resource.ResourceClassLoaderFactory; +import io.gravitee.plugin.resource.ResourcePlugin; +import io.gravitee.resource.api.ResourceManager; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.ResolvableType; + +@Slf4j +public abstract class AbstractReactorFactory> implements ReactorFactory { + + protected final ApplicationContext applicationContext; + protected final PolicyFactoryManager policyFactoryManager; + protected final Configuration configuration; + + protected AbstractReactorFactory( + ApplicationContext applicationContext, + PolicyFactoryManager policyFactoryManager, + Configuration configuration + ) { + this.applicationContext = applicationContext; + this.policyFactoryManager = policyFactoryManager; + this.configuration = configuration; + } + + @Override + public abstract boolean support(Class clazz); + + @Override + public abstract boolean canCreate(T reactableApi); + + protected abstract void addExtraComponents( + CustomComponentProvider componentProvider, + T reactableApi, + DefaultDeploymentContext deploymentContext + ); + + protected abstract PolicyManager getPolicyManager( + DefaultClassLoader classLoader, + T reactableApi, + PolicyFactoryManager factoryManager, + PolicyConfigurationFactory policyConfigurationFactory, + ConfigurablePluginManager> ppm, + PolicyClassLoaderFactory policyClassLoaderFactory, + ComponentProvider componentProvider + ); + + protected abstract ApiReactor buildApiReactor( + T reactableApi, + CompositeComponentProvider componentProvider, + PolicyManager policyManager, + DefaultDeploymentContext deploymentContext, + ResourceLifecycleManager resourceLifecycleManager + ); + + @Override + public ApiReactor create(final T reactableApi) { + try { + if (reactableApi.isEnabled()) { + log.info("Creating Reactor Handler for api {}", reactableApi.getId()); + + final ComponentProvider globalComponentProvider = applicationContext.getBean(ComponentProvider.class); + final CustomComponentProvider customComponentProvider = new CustomComponentProvider(); + customComponentProvider.add(ReactableApi.class, reactableApi); + + final CompositeComponentProvider componentProvider = new CompositeComponentProvider( + customComponentProvider, + globalComponentProvider + ); + + final DefaultDeploymentContext deploymentContext = new DefaultDeploymentContext(); + deploymentContext.componentProvider(componentProvider); + deploymentContext.templateVariableProviders(commonTemplateVariableProviders(reactableApi)); + + final ResourceLifecycleManager resourceLifecycleManager = resourceLifecycleManager( + reactableApi, + applicationContext.getBean(ResourceClassLoaderFactory.class), + new ResourceConfigurationFactoryImpl(), + applicationContext, + deploymentContext + ); + customComponentProvider.add(ResourceManager.class, resourceLifecycleManager); + + String[] beanNamesForType = getBeanNamesForType( + ResolvableType.forClassWithGenerics(ConfigurablePluginManager.class, PolicyPlugin.class) + ); + + ConfigurablePluginManager> ppm = (ConfigurablePluginManager>) applicationContext.getBean( + beanNamesForType[0] + ); + + final PolicyManager policyManager = getPolicyManager( + applicationContext.getBean(DefaultClassLoader.class), + reactableApi, + policyFactoryManager, + new CachedPolicyConfigurationFactory(), + ppm, + applicationContext.getBean(PolicyClassLoaderFactory.class), + componentProvider + ); + + addExtraComponents(customComponentProvider, reactableApi, deploymentContext); + + return buildApiReactor(reactableApi, componentProvider, policyManager, deploymentContext, resourceLifecycleManager); + } + } catch (Exception ex) { + log.error("Unexpected error while creating AsyncApiReactor", ex); + } + return null; + } + + protected List commonTemplateVariableProviders(T reactableApi) { + final List templateVariableProviders = new ArrayList<>(); + templateVariableProviders.add(new ApiTemplateVariableProvider(reactableApi)); + templateVariableProviders.addAll( + applicationContext.getBean(ApiTemplateVariableProviderFactory.class).getTemplateVariableProviders() + ); + + return templateVariableProviders; + } + + @SuppressWarnings("unchecked") + private ResourceLifecycleManager resourceLifecycleManager( + Reactable reactable, + ResourceClassLoaderFactory resourceClassLoaderFactory, + ResourceConfigurationFactory resourceConfigurationFactory, + ApplicationContext applicationContext, + DeploymentContext deploymentContext + ) { + String[] beanNamesForType = getBeanNamesForType( + ResolvableType.forClassWithGenerics(ConfigurablePluginManager.class, ResourcePlugin.class) + ); + + ConfigurablePluginManager> cpm = (ConfigurablePluginManager>) applicationContext.getBean( + beanNamesForType[0] + ); + + return new DefaultResourceManager( + applicationContext.getBean(DefaultClassLoader.class), + reactable, + cpm, + resourceClassLoaderFactory, + resourceConfigurationFactory, + applicationContext, + deploymentContext + ); + } + + /** + * Search across tree of BeanFactory in order to find bean in a parent application context. + * @param resolvableType + * @return + */ + protected String[] getBeanNamesForType(ResolvableType resolvableType) { + return BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + ((ConfigurableApplicationContext) applicationContext).getBeanFactory(), + resolvableType + ); + } +} diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactor.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactor.java index 8fdc72f2e84..c9cfa646b3f 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactor.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactor.java @@ -57,7 +57,7 @@ import io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChain; import io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory; import io.gravitee.gateway.reactive.handlers.api.v4.processor.ApiProcessorChainFactory; -import io.gravitee.gateway.reactive.handlers.api.v4.security.SecurityChain; +import io.gravitee.gateway.reactive.handlers.api.v4.security.HttpSecurityChain; import io.gravitee.gateway.reactive.policy.PolicyManager; import io.gravitee.gateway.reactor.handler.Acceptor; import io.gravitee.gateway.reactor.handler.AccessPointHttpAcceptor; @@ -124,7 +124,7 @@ public class DefaultApiReactor extends AbstractApiReactor { protected final String loggingExcludedResponseType; protected final String loggingMaxSize; private final DeploymentContext deploymentContext; - protected SecurityChain securityChain; + protected HttpSecurityChain httpSecurityChain; private Lifecycle.State lifecycleState; protected AnalyticsContext analyticsContext; private List services; @@ -250,7 +250,7 @@ protected Completable handleRequest(final MutableExecutionContext ctx) { // Before Security Chain. .chainWith(executeProcessorChain(ctx, beforeSecurityChainProcessors, REQUEST)) // Execute security chain. - .chainWith(securityChain.execute(ctx)) + .chainWith(httpSecurityChain.execute(ctx)) // Before flows processors. .chainWith(executeProcessorChain(ctx, beforeApiExecutionProcessors, REQUEST)) // Resolve entrypoint and prepare request to be handled. @@ -472,8 +472,8 @@ protected void doStart() throws Exception { resourceLifecycleManager.start(); policyManager.start(); - // Create securityChain once policy manager has been started. - securityChain = new SecurityChain(api.getDefinition(), policyManager, ExecutionPhase.REQUEST); + // Create httpSecurityChain once policy manager has been started. + httpSecurityChain = new HttpSecurityChain(api.getDefinition(), policyManager, ExecutionPhase.REQUEST); tracingContext.start(); analyticsContext = analyticsContext(); @@ -487,7 +487,7 @@ protected void doStart() throws Exception { processorChainHooks.add(new TracingHook("Processor chain")); } invokerHooks.add(new InvokerTracingHook("Invoker")); - securityChain.addHooks(new TracingHook("Security")); + httpSecurityChain.addHooks(new TracingHook("Security")); } } addInvokerHooks(invokerHooks); diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorFactory.java index 1b5a54fe778..28bcd41fc7e 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorFactory.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorFactory.java @@ -29,15 +29,12 @@ import io.gravitee.gateway.opentelemetry.TracingContext; import io.gravitee.gateway.platform.organization.manager.OrganizationManager; import io.gravitee.gateway.policy.PolicyConfigurationFactory; -import io.gravitee.gateway.policy.impl.CachedPolicyConfigurationFactory; -import io.gravitee.gateway.reactive.api.context.DeploymentContext; import io.gravitee.gateway.reactive.core.condition.CompositeConditionFilter; import io.gravitee.gateway.reactive.core.context.DefaultDeploymentContext; import io.gravitee.gateway.reactive.core.v4.analytics.AnalyticsUtils; import io.gravitee.gateway.reactive.core.v4.endpoint.DefaultEndpointManager; import io.gravitee.gateway.reactive.core.v4.endpoint.EndpointManager; import io.gravitee.gateway.reactive.handlers.api.ApiPolicyManager; -import io.gravitee.gateway.reactive.handlers.api.el.ApiTemplateVariableProvider; import io.gravitee.gateway.reactive.handlers.api.el.ContentTemplateVariableProvider; import io.gravitee.gateway.reactive.handlers.api.flow.FlowChainFactory; import io.gravitee.gateway.reactive.handlers.api.v4.flow.resolver.FlowResolverFactory; @@ -48,19 +45,13 @@ import io.gravitee.gateway.reactive.policy.PolicyFactory; import io.gravitee.gateway.reactive.policy.PolicyFactoryManager; import io.gravitee.gateway.reactive.policy.PolicyManager; -import io.gravitee.gateway.reactive.reactor.v4.reactor.ReactorFactory; +import io.gravitee.gateway.reactive.reactor.ApiReactor; import io.gravitee.gateway.reactive.v4.flow.BestMatchFlowSelector; import io.gravitee.gateway.reactive.v4.flow.selection.ConditionSelectorConditionFilter; import io.gravitee.gateway.reactive.v4.flow.selection.HttpSelectorConditionFilter; import io.gravitee.gateway.reactor.Reactable; -import io.gravitee.gateway.reactor.ReactableApi; -import io.gravitee.gateway.reactor.handler.ReactorHandler; -import io.gravitee.gateway.reactor.handler.context.ApiTemplateVariableProviderFactory; import io.gravitee.gateway.report.ReporterService; -import io.gravitee.gateway.resource.ResourceConfigurationFactory; import io.gravitee.gateway.resource.ResourceLifecycleManager; -import io.gravitee.gateway.resource.internal.ResourceConfigurationFactoryImpl; -import io.gravitee.gateway.resource.internal.v4.DefaultResourceManager; import io.gravitee.node.api.Node; import io.gravitee.node.api.configuration.Configuration; import io.gravitee.node.api.opentelemetry.InstrumenterTracerFactory; @@ -73,30 +64,20 @@ import io.gravitee.plugin.entrypoint.EntrypointConnectorPluginManager; import io.gravitee.plugin.policy.PolicyClassLoaderFactory; import io.gravitee.plugin.policy.PolicyPlugin; -import io.gravitee.plugin.resource.ResourceClassLoaderFactory; -import io.gravitee.plugin.resource.ResourcePlugin; -import io.gravitee.resource.api.ResourceManager; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.ResolvableType; /** * @author David BRASSELY (david.brassely at graviteesource.com) * @author GraviteeSource Team */ -public class DefaultApiReactorFactory implements ReactorFactory { +public class DefaultApiReactorFactory extends AbstractReactorFactory { - protected final ApplicationContext applicationContext; - protected final Configuration configuration; protected final Node node; - protected final PolicyFactoryManager policyFactoryManager; protected final EntrypointConnectorPluginManager entrypointConnectorPluginManager; protected final EndpointConnectorPluginManager endpointConnectorPluginManager; protected final ApiServicePluginManager apiServicePluginManager; @@ -104,7 +85,7 @@ public class DefaultApiReactorFactory implements ReactorFactory { protected final OrganizationManager organizationManager; protected final AccessPointManager accessPointManager; protected final EventManager eventManager; - private final OpenTelemetryConfiguration openTelemetryConfiguration; + protected final OpenTelemetryConfiguration openTelemetryConfiguration; protected final OpenTelemetryFactory openTelemetryFactory; protected final List instrumenterTracerFactories; protected final ApiProcessorChainFactory apiProcessorChainFactory; @@ -133,10 +114,8 @@ public DefaultApiReactorFactory( final OpenTelemetryFactory openTelemetryFactory, final List instrumenterTracerFactories ) { - this.applicationContext = applicationContext; - this.configuration = configuration; + super(applicationContext, policyFactoryManager, configuration); this.node = node; - this.policyFactoryManager = policyFactoryManager; this.entrypointConnectorPluginManager = entrypointConnectorPluginManager; this.endpointConnectorPluginManager = endpointConnectorPluginManager; this.apiServicePluginManager = apiServicePluginManager; @@ -174,10 +153,8 @@ public DefaultApiReactorFactory( final OpenTelemetryFactory openTelemetryFactory, final List instrumenterTracerFactories ) { - this.applicationContext = applicationContext; - this.configuration = configuration; + super(applicationContext, new PolicyFactoryManager(new HashSet<>(Set.of(policyFactory))), configuration); this.node = node; - this.policyFactoryManager = new PolicyFactoryManager(new HashSet<>(Set.of(policyFactory))); this.entrypointConnectorPluginManager = entrypointConnectorPluginManager; this.endpointConnectorPluginManager = endpointConnectorPluginManager; this.apiServicePluginManager = apiServicePluginManager; @@ -219,103 +196,94 @@ public boolean canCreate(Api api) { } @Override - public ReactorHandler create(final Api api) { - try { - if (api.isEnabled()) { - final ComponentProvider globalComponentProvider = applicationContext.getBean(ComponentProvider.class); - final CustomComponentProvider customComponentProvider = new CustomComponentProvider(); - customComponentProvider.add(Api.class, api); - customComponentProvider.add(ReactableApi.class, api); - customComponentProvider.add(io.gravitee.definition.model.v4.Api.class, api.getDefinition()); - - final CompositeComponentProvider componentProvider = new CompositeComponentProvider( - customComponentProvider, - globalComponentProvider - ); - - final DefaultDeploymentContext deploymentContext = new DefaultDeploymentContext(); - deploymentContext.componentProvider(componentProvider); - deploymentContext.templateVariableProviders(commonTemplateVariableProviders(api)); - - final ResourceLifecycleManager resourceLifecycleManager = resourceLifecycleManager( - api, - applicationContext.getBean(ResourceClassLoaderFactory.class), - new ResourceConfigurationFactoryImpl(), - applicationContext, - deploymentContext - ); - customComponentProvider.add(ResourceManager.class, resourceLifecycleManager); + protected void addExtraComponents( + CustomComponentProvider customComponentProvider, + Api reactableApi, + DefaultDeploymentContext deploymentContext + ) { + customComponentProvider.add(Api.class, reactableApi); + customComponentProvider.add(io.gravitee.definition.model.v4.Api.class, reactableApi.getDefinition()); - final PolicyManager policyManager = policyManager( - api, - policyFactoryManager, - new CachedPolicyConfigurationFactory(), - applicationContext.getBean(PolicyClassLoaderFactory.class), - componentProvider - ); + final DefaultEndpointManager endpointManager = new DefaultEndpointManager( + reactableApi.getDefinition(), + endpointConnectorPluginManager, + deploymentContext + ); - final PolicyChainFactory policyChainFactory = new HttpPolicyChainFactory( - api.getId(), - policyManager, - isApiTracingEnabled(api) - ); + customComponentProvider.add(EndpointManager.class, endpointManager); + } - final io.gravitee.gateway.reactive.v4.policy.PolicyChainFactory v4PolicyChainFactory = policyChainFactory( - api, - policyManager - ); + @Override + protected PolicyManager getPolicyManager( + DefaultClassLoader classLoader, + Api reactableApi, + PolicyFactoryManager factoryManager, + PolicyConfigurationFactory policyConfigurationFactory, + ConfigurablePluginManager> ppm, + PolicyClassLoaderFactory policyClassLoaderFactory, + ComponentProvider componentProvider + ) { + return new ApiPolicyManager( + classLoader, + reactableApi, + factoryManager, + policyConfigurationFactory, + ppm, + policyClassLoaderFactory, + componentProvider + ); + } - final FlowChainFactory flowChainFactory = new FlowChainFactory( - organizationPolicyChainFactoryManager, - policyChainFactory, - organizationManager, - flowResolverFactory - ); + @Override + protected ApiReactor buildApiReactor( + Api api, + CompositeComponentProvider componentProvider, + PolicyManager policyManager, + DefaultDeploymentContext deploymentContext, + ResourceLifecycleManager resourceLifecycleManager + ) { + final HttpPolicyChainFactory policyChainFactory = new HttpPolicyChainFactory(api.getId(), policyManager, isApiTracingEnabled(api)); - final DefaultEndpointManager endpointManager = new DefaultEndpointManager( - api.getDefinition(), - endpointConnectorPluginManager, - deploymentContext - ); + final io.gravitee.gateway.reactive.v4.policy.HttpPolicyChainFactory v4PolicyChainFactory = policyChainFactory(api, policyManager); - customComponentProvider.add(EndpointManager.class, endpointManager); + final FlowChainFactory flowChainFactory = new FlowChainFactory( + organizationPolicyChainFactoryManager, + policyChainFactory, + organizationManager, + flowResolverFactory + ); - final io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory v4FlowChainFactory = - new io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory(v4PolicyChainFactory, v4FlowResolverFactory); + final io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory v4FlowChainFactory = + new io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory(v4PolicyChainFactory, v4FlowResolverFactory); - customComponents(api, customComponentProvider); + DefaultEndpointManager endpointManager = (DefaultEndpointManager) componentProvider.getComponent(EndpointManager.class); - final List ctxTemplateVariableProviders = ctxTemplateVariableProviders(api); - ctxTemplateVariableProviders.add(endpointManager); + final List ctxTemplateVariableProviders = ctxTemplateVariableProviders(api); + ctxTemplateVariableProviders.add(endpointManager); - return buildApiReactor( - api, - componentProvider, - deploymentContext, - resourceLifecycleManager, - policyManager, - flowChainFactory, - endpointManager, - v4FlowChainFactory, - ctxTemplateVariableProviders - ); - } - } catch (Exception ex) { - logger.error("Unexpected error while creating AsyncApiReactor", ex); - } - return null; + return buildApiReactor( + api, + deploymentContext, + componentProvider, + ctxTemplateVariableProviders, + policyManager, + endpointManager, + resourceLifecycleManager, + flowChainFactory, + v4FlowChainFactory + ); } - protected DefaultApiReactor buildApiReactor( + protected ApiReactor buildApiReactor( Api api, - CompositeComponentProvider componentProvider, DefaultDeploymentContext deploymentContext, - ResourceLifecycleManager resourceLifecycleManager, + CompositeComponentProvider componentProvider, + List ctxTemplateVariableProviders, PolicyManager policyManager, - FlowChainFactory flowChainFactory, DefaultEndpointManager endpointManager, - io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory v4FlowChainFactory, - List ctxTemplateVariableProviders + ResourceLifecycleManager resourceLifecycleManager, + FlowChainFactory flowChainFactory, + io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory v4FlowChainFactory ) { return new DefaultApiReactor( api, @@ -369,89 +337,11 @@ protected boolean isApiTracingVerboseEnabled(final Api api) { ); } - protected void customComponents(Api api, CustomComponentProvider customComponentProvider) {} - protected io.gravitee.gateway.reactive.v4.policy.HttpPolicyChainFactory policyChainFactory(Api api, PolicyManager policyManager) { return new io.gravitee.gateway.reactive.v4.policy.HttpPolicyChainFactory(api.getId(), policyManager, isApiTracingEnabled(api)); } - /** - * Search across tree of BeanFactory in order to find bean in a parent application context. - * @param resolvableType - * @return - */ - private String[] getBeanNamesForType(ResolvableType resolvableType) { - return BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - ((ConfigurableApplicationContext) applicationContext).getBeanFactory(), - resolvableType - ); - } - - @SuppressWarnings("unchecked") - public ResourceLifecycleManager resourceLifecycleManager( - Reactable reactable, - ResourceClassLoaderFactory resourceClassLoaderFactory, - ResourceConfigurationFactory resourceConfigurationFactory, - ApplicationContext applicationContext, - DeploymentContext deploymentContext - ) { - String[] beanNamesForType = getBeanNamesForType( - ResolvableType.forClassWithGenerics(ConfigurablePluginManager.class, ResourcePlugin.class) - ); - - ConfigurablePluginManager> cpm = (ConfigurablePluginManager>) applicationContext.getBean( - beanNamesForType[0] - ); - - return new DefaultResourceManager( - applicationContext.getBean(DefaultClassLoader.class), - reactable, - cpm, - resourceClassLoaderFactory, - resourceConfigurationFactory, - applicationContext, - deploymentContext - ); - } - - @SuppressWarnings("unchecked") - public PolicyManager policyManager( - Api api, - PolicyFactoryManager factoryManager, - PolicyConfigurationFactory policyConfigurationFactory, - PolicyClassLoaderFactory policyClassLoaderFactory, - ComponentProvider componentProvider - ) { - String[] beanNamesForType = getBeanNamesForType( - ResolvableType.forClassWithGenerics(ConfigurablePluginManager.class, PolicyPlugin.class) - ); - - ConfigurablePluginManager> ppm = (ConfigurablePluginManager>) applicationContext.getBean( - beanNamesForType[0] - ); - - return new ApiPolicyManager( - applicationContext.getBean(DefaultClassLoader.class), - api, - factoryManager, - policyConfigurationFactory, - ppm, - policyClassLoaderFactory, - componentProvider - ); - } - - protected List commonTemplateVariableProviders(Api api) { - final List templateVariableProviders = new ArrayList<>(); - templateVariableProviders.add(new ApiTemplateVariableProvider(api)); - templateVariableProviders.addAll( - applicationContext.getBean(ApiTemplateVariableProviderFactory.class).getTemplateVariableProviders() - ); - - return templateVariableProviders; - } - - protected List ctxTemplateVariableProviders(Api api) { + private List ctxTemplateVariableProviders(Api api) { final List requestTemplateVariableProviders = commonTemplateVariableProviders(api); if (api.getDefinition().getType() == ApiType.PROXY) { requestTemplateVariableProviders.add(new ContentTemplateVariableProvider()); diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/SecurityChain.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/HttpSecurityChain.java similarity index 74% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/SecurityChain.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/HttpSecurityChain.java index f0a64906b52..024b15f55d1 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/SecurityChain.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/HttpSecurityChain.java @@ -17,8 +17,8 @@ import io.gravitee.definition.model.v4.Api; import io.gravitee.gateway.reactive.api.ExecutionPhase; -import io.gravitee.gateway.reactive.handlers.api.security.plan.SecurityPlan; -import io.gravitee.gateway.reactive.handlers.api.v4.security.plan.SecurityPlanFactory; +import io.gravitee.gateway.reactive.handlers.api.security.plan.HttpSecurityPlan; +import io.gravitee.gateway.reactive.handlers.api.v4.security.plan.HttpSecurityPlanFactory; import io.gravitee.gateway.reactive.policy.PolicyManager; import io.reactivex.rxjava3.core.Flowable; import jakarta.annotation.Nonnull; @@ -34,15 +34,19 @@ * @author Guillaume LAMIRAND (guillaume.lamirand at graviteesource.com) * @author GraviteeSource Team */ -public class SecurityChain extends io.gravitee.gateway.reactive.handlers.api.security.SecurityChain { +public class HttpSecurityChain extends io.gravitee.gateway.reactive.handlers.api.security.HttpSecurityChain { - public SecurityChain(@Nonnull final Api api, @Nonnull final PolicyManager policyManager, @Nonnull final ExecutionPhase executionPhase) { + public HttpSecurityChain( + @Nonnull final Api api, + @Nonnull final PolicyManager policyManager, + @Nonnull final ExecutionPhase executionPhase + ) { super( Flowable.fromIterable( stream(api.getPlans()) - .map(plan -> SecurityPlanFactory.forPlan(api.getId(), plan, policyManager, executionPhase)) + .map(plan -> HttpSecurityPlanFactory.forPlan(api.getId(), plan, policyManager, executionPhase)) .filter(Objects::nonNull) - .sorted(Comparator.comparingInt(SecurityPlan::order)) + .sorted(Comparator.comparingInt(HttpSecurityPlan::order)) .collect(Collectors.toList()) ), executionPhase diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/plan/SecurityPlanFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/plan/HttpSecurityPlanFactory.java similarity index 81% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/plan/SecurityPlanFactory.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/plan/HttpSecurityPlanFactory.java index e6f31f8b59f..e0484f85751 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/plan/SecurityPlanFactory.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/security/plan/HttpSecurityPlanFactory.java @@ -18,26 +18,24 @@ import io.gravitee.definition.model.v4.plan.AbstractPlan; import io.gravitee.gateway.reactive.api.ExecutionPhase; import io.gravitee.gateway.reactive.api.policy.http.HttpSecurityPolicy; -import io.gravitee.gateway.reactive.handlers.api.security.plan.SecurityPlan; +import io.gravitee.gateway.reactive.handlers.api.security.plan.HttpSecurityPlan; import io.gravitee.gateway.reactive.handlers.api.v4.security.policy.SecurityPolicyFactory; import io.gravitee.gateway.reactive.policy.PolicyManager; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** * @author Guillaume LAMIRAND (guillaume.lamirand at graviteesource.com) * @author GraviteeSource Team */ -public class SecurityPlanFactory { +@Slf4j +public class HttpSecurityPlanFactory { - private static final Logger log = LoggerFactory.getLogger(SecurityPlanFactory.class); - - private SecurityPlanFactory() {} + private HttpSecurityPlanFactory() {} @Nullable - public static SecurityPlan forPlan( + public static HttpSecurityPlan forPlan( @Nonnull final String apiId, @Nonnull AbstractPlan plan, @Nonnull PolicyManager policyManager, @@ -48,11 +46,10 @@ public static SecurityPlan forPlan( return null; } - // FIXME: use a BaseSecurityPolicy. final HttpSecurityPolicy policy = SecurityPolicyFactory.forPlan(apiId, plan, policyManager, executionPhase); if (policy != null) { - return new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + return new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); } log.warn( diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactorTest.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactorTest.java index 19214ad5081..4bab723ba3e 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactorTest.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/SyncApiReactorTest.java @@ -62,7 +62,7 @@ import io.gravitee.gateway.reactive.handlers.api.flow.FlowChain; import io.gravitee.gateway.reactive.handlers.api.flow.FlowChainFactory; import io.gravitee.gateway.reactive.handlers.api.processor.ApiProcessorChainFactory; -import io.gravitee.gateway.reactive.handlers.api.security.SecurityChain; +import io.gravitee.gateway.reactive.handlers.api.security.HttpSecurityChain; import io.gravitee.gateway.reactive.handlers.api.v4.analytics.logging.LoggingHook; import io.gravitee.gateway.reactive.policy.PolicyManager; import io.gravitee.gateway.resource.ResourceLifecycleManager; @@ -169,7 +169,7 @@ class SyncApiReactorTest { InvokerAdapter invokerAdapter; @Mock - SecurityChain securityChain; + HttpSecurityChain httpSecurityChain; @Spy Completable spyRequestPlatformFlowChain = Completable.complete(); @@ -327,7 +327,7 @@ void shouldStartWithoutTracing() throws Exception { verify(resourceLifecycleManager).start(); verify(policyManager).start(); verify(groupLifecycleManager).start(); - assertThat(cut.securityChain).isNotNull(); + assertThat(cut.httpSecurityChain).isNotNull(); assertThat(cut.processorChainHooks).isEmpty(); assertThat(cut.invokerHooks).isEmpty(); } @@ -341,7 +341,7 @@ void shouldStartWithTracing() throws Exception { verify(resourceLifecycleManager).start(); verify(policyManager).start(); verify(groupLifecycleManager).start(); - assertThat(cut.securityChain).isNotNull(); + assertThat(cut.httpSecurityChain).isNotNull(); assertThat(cut.processorChainHooks).hasSize(1); assertThat(cut.processorChainHooks.get(0)).isInstanceOf(TracingHook.class); assertThat(cut.invokerHooks).hasSize(1); @@ -359,7 +359,7 @@ void shouldStartWithLoggingContext() throws Exception { verify(resourceLifecycleManager).start(); verify(policyManager).start(); verify(groupLifecycleManager).start(); - assertThat(cut.securityChain).isNotNull(); + assertThat(cut.httpSecurityChain).isNotNull(); assertThat(cut.invokerHooks).hasSize(1); assertThat(cut.invokerHooks.get(0)).isInstanceOf(LoggingHook.class); } @@ -423,8 +423,8 @@ void shouldHandleRequest() throws Exception { fillRequestExecutionContext(); when(invokerAdapter.invoke(any(HttpExecutionContext.class))).thenReturn(spyInvokerAdapterChain); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).test().assertComplete(); @@ -461,8 +461,8 @@ void shouldHandleRequestWhenTimeoutZeroOrLess(Long timeout) throws Exception { fillRequestExecutionContext(); when(invokerAdapter.invoke(any(HttpExecutionContext.class))).thenReturn(spyInvokerAdapterChain); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).test().assertComplete(); InOrder orderedChain = getInOrder(); @@ -506,8 +506,8 @@ void shouldHandleRequestWithLegacyInvoker() throws Exception { lenient().when(ctx.getInternalAttribute(ATTR_INTERNAL_INVOKER)).thenReturn(endpointInvoker); ArgumentCaptor> handlerArgumentCaptor = ArgumentCaptor.forClass(Handler.class); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).subscribe(); @@ -562,8 +562,8 @@ InOrder testFlowChainWithInvokeBackendError(Completable spyInvokerAdapterError, when(invokerAdapter.invoke(any(HttpExecutionContext.class))).thenReturn(spyInvokerAdapterError); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).test().assertComplete(); InOrder orderedChain = getInOrder(); @@ -597,8 +597,8 @@ void shouldInterruptChainBecauseOfTimeoutOnBackend() throws Exception { when(invokerAdapter.invoke(any(ExecutionContext.class))).thenReturn(spyTimeout); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).subscribe(); testScheduler.advanceTimeBy(REQUEST_TIMEOUT + 30L, TimeUnit.MILLISECONDS); @@ -636,8 +636,8 @@ void shouldInterruptChainBecauseOfTimeoutOnRequest() throws Exception { fillRequestExecutionContext(); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).subscribe(); testScheduler.advanceTimeBy(REQUEST_TIMEOUT + 30L, TimeUnit.MILLISECONDS); @@ -675,8 +675,8 @@ void shouldInterruptChainBecauseOfTimeoutOnResponsePlatformPolicies() throws Exc fillRequestExecutionContext(); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spySecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spySecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).subscribe(); testScheduler.advanceTimeBy(REQUEST_TIMEOUT + 30L, TimeUnit.MILLISECONDS); @@ -709,8 +709,8 @@ void shouldNotApplyFlowChain_SecurityChain() throws Exception { when(onErrorProcessors.execute(ctx, ExecutionPhase.RESPONSE)).thenReturn(spyOnErrorProcessors); fillRequestExecutionContext(); cut.doStart(); - when(securityChain.execute(any())).thenReturn(spyInterruptSecurityChain); - ReflectionTestUtils.setField(cut, "securityChain", securityChain); + when(httpSecurityChain.execute(any())).thenReturn(spyInterruptSecurityChain); + ReflectionTestUtils.setField(cut, "httpSecurityChain", httpSecurityChain); cut.handle(ctx).test().assertComplete(); InOrder orderedChain = getInOrder(); diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/processor/subscription/SubscriptionProcessorTest.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/processor/subscription/SubscriptionProcessorTest.java index 7a9c403413c..3d6de82c5cd 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/processor/subscription/SubscriptionProcessorTest.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/processor/subscription/SubscriptionProcessorTest.java @@ -93,7 +93,7 @@ void shouldReturnId() { } @Nested - class SecurityChain { + class HttpSecurityChain { @Test void should_set_metrics_when_security_chain_is_not_skipped() { diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChainTest.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/HttpSecurityChainTest.java similarity index 91% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChainTest.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/HttpSecurityChainTest.java index 2cd406c5bcc..d52e4591e5f 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChainTest.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/HttpSecurityChainTest.java @@ -27,7 +27,6 @@ import io.gravitee.definition.model.Api; import io.gravitee.definition.model.Plan; import io.gravitee.gateway.reactive.api.ExecutionPhase; -import io.gravitee.gateway.reactive.api.context.ExecutionContext; import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; import io.gravitee.gateway.reactive.api.policy.SecurityPolicy; import io.gravitee.gateway.reactive.api.policy.SecurityToken; @@ -47,7 +46,7 @@ * @author GraviteeSource Team */ @ExtendWith(MockitoExtension.class) -class SecurityChainTest { +class HttpSecurityChainTest { protected static final String MOCK_POLICY = "mock-policy"; protected static final String MOCK_POLICY_CONFIG = "mock-policy-configuration"; @@ -79,7 +78,7 @@ void shouldExecuteSecurityPolicyWhenHasRelevantSecurityToken() { when(policy2.onRequest(ctx)).thenReturn(Completable.complete()); - final SecurityChain cut = new SecurityChain(api, policyManager, ExecutionPhase.REQUEST); + final HttpSecurityChain cut = new HttpSecurityChain(api, policyManager, ExecutionPhase.REQUEST); final TestObserver obs = cut.execute(ctx).test(); obs.assertResult(); @@ -103,7 +102,7 @@ void shouldNotExecuteSecondSecurityPolicyWhenFirstHandled() { when(policy1.onRequest(ctx)).thenReturn(Completable.complete()); - final SecurityChain cut = new SecurityChain(api, policyManager, ExecutionPhase.REQUEST); + final HttpSecurityChain cut = new HttpSecurityChain(api, policyManager, ExecutionPhase.REQUEST); final TestObserver obs = cut.execute(ctx).test(); obs.assertResult(); @@ -129,7 +128,7 @@ void shouldSkipSecurityWhenSkipSecurityChainAttributeIsDefined() { when(ctx.getInternalAttribute(ATTR_INTERNAL_SECURITY_SKIP)).thenReturn(true); - final SecurityChain cut = new SecurityChain(api, policyManager, ExecutionPhase.REQUEST); + final HttpSecurityChain cut = new HttpSecurityChain(api, policyManager, ExecutionPhase.REQUEST); final TestObserver obs = cut.execute(ctx).test(); obs.assertResult(); @@ -144,7 +143,7 @@ void shouldInterrupt401WhenNoPlan() { when(api.getPlans()).thenReturn(new ArrayList<>()); when(ctx.interruptWith(any())).thenReturn(Completable.error(new RuntimeException(MOCK_EXCEPTION))); - final SecurityChain cut = new SecurityChain(api, policyManager, ExecutionPhase.REQUEST); + final HttpSecurityChain cut = new HttpSecurityChain(api, policyManager, ExecutionPhase.REQUEST); final TestObserver obs = cut.execute(ctx).test(); obs.assertError(Throwable.class); @@ -169,7 +168,7 @@ void shouldInterrupt401WhenNoPolicyHasRelevantSecurityToken() { when(ctx.interruptWith(any())).thenReturn(Completable.error(new RuntimeException(MOCK_EXCEPTION))); - final SecurityChain cut = new SecurityChain(api, policyManager, ExecutionPhase.REQUEST); + final HttpSecurityChain cut = new HttpSecurityChain(api, policyManager, ExecutionPhase.REQUEST); final TestObserver obs = cut.execute(ctx).test(); obs.assertError(Throwable.class); @@ -182,8 +181,8 @@ private void verifyUnauthorized() { .interruptWith( argThat(failure -> { assertEquals(HttpStatusCode.UNAUTHORIZED_401, failure.statusCode()); - Assertions.assertEquals(SecurityChain.UNAUTHORIZED_MESSAGE, failure.message()); - Assertions.assertEquals(SecurityChain.PLAN_UNRESOLVABLE, failure.key()); + Assertions.assertEquals(HttpSecurityChain.UNAUTHORIZED_MESSAGE, failure.message()); + Assertions.assertEquals(HttpSecurityChain.PLAN_UNRESOLVABLE, failure.key()); assertNull(failure.parameters()); assertNull(failure.contentType()); diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlanTest.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlanTest.java similarity index 88% rename from gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlanTest.java rename to gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlanTest.java index f0994601506..536a1e5d5e1 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/plan/SecurityPlanTest.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/security/plan/HttpSecurityPlanTest.java @@ -51,7 +51,7 @@ * @author GraviteeSource Team */ @ExtendWith(MockitoExtension.class) -class SecurityPlanTest { +class HttpSecurityPlanTest { protected static final String SELECTION_RULE = "{#selection-rule}"; protected static final String V3_SELECTION_RULE = "#selection-rule"; @@ -87,7 +87,7 @@ class SecurityPlanTest { void canExecute_shouldReturnTrue_whenPolicyHasSecurityTokenAndSelectionRuleIsNull() { when(policy.extractSecurityToken(ctx)).thenReturn(Maybe.just(securityToken)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(true); @@ -101,7 +101,7 @@ void canExecute_shouldReturnTrue_whenPolicyHasSecurityTokenAndSelectionRuleIsTru when(ctx.getTemplateEngine()).thenReturn(templateEngine); when(templateEngine.eval(SELECTION_RULE, Boolean.class)).thenReturn(Maybe.just(true)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(true); @@ -115,7 +115,7 @@ void canExecute_shouldReturnTrue_whenPolicyHasSecurityTokenWithV3SelectionRule() when(ctx.getTemplateEngine()).thenReturn(templateEngine); when(templateEngine.eval(SELECTION_RULE, Boolean.class)).thenReturn(Maybe.just(true)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(true); @@ -129,7 +129,7 @@ void canExecute_shouldReturnTrue_whenPolicyHasSecurityTokenAndSelectionRuleIsFal when(ctx.getTemplateEngine()).thenReturn(templateEngine); when(templateEngine.eval(SELECTION_RULE, Boolean.class)).thenReturn(Maybe.just(false)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(false); @@ -140,7 +140,7 @@ void canExecute_shouldReturnTrue_whenPolicyHasSecurityTokenAndSelectionRuleIsFal void canExecute_shouldReturnFalse_whenPolicyDontHaveSecurityToken() { when(policy.extractSecurityToken(ctx)).thenReturn(Maybe.empty()); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(false); @@ -158,7 +158,7 @@ void canExecute_shouldReturnFalse_whenPolicyHasSecurityToken_butSubscriptionNotF // no subscription found with this security token when(subscriptionService.getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID)).thenReturn(Optional.empty()); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); verify(subscriptionService).getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID); @@ -179,7 +179,7 @@ void canExecute_shouldReturnFalse_whenPolicyHasSecurityToken_butFoundSubscriptio when(subscription.getPlan()).thenReturn("another-plan-id"); when(subscriptionService.getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID)).thenReturn(Optional.of(subscription)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); verify(subscriptionService).getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID); @@ -194,8 +194,7 @@ void canExecute_shouldReturnFalse_whenPolicyHasSecurityToken_butFoundSubscriptio when(policy.extractSecurityToken(ctx)).thenReturn(Maybe.just(securityToken)); when(plan.getId()).thenReturn(PLAN_ID); when(ctx.getAttribute(ContextAttributes.ATTR_API)).thenReturn(API_ID); - when(ctx.request()).thenReturn(request); - when(request.timestamp()).thenReturn(System.currentTimeMillis()); + when(ctx.timestamp()).thenReturn(System.currentTimeMillis()); // subscription found with this security token final Subscription subscription = mock(Subscription.class); @@ -203,7 +202,7 @@ void canExecute_shouldReturnFalse_whenPolicyHasSecurityToken_butFoundSubscriptio when(subscription.isTimeValid(anyLong())).thenReturn(false); // subscription time is not valid when(subscriptionService.getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID)).thenReturn(Optional.of(subscription)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); verify(subscriptionService).getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID); @@ -218,8 +217,7 @@ void canExecute_shouldReturnTrue_whenPolicyHasSecurityToken_butFoundSubscription when(policy.extractSecurityToken(ctx)).thenReturn(Maybe.just(securityToken)); when(plan.getId()).thenReturn(PLAN_ID); when(ctx.getAttribute(ContextAttributes.ATTR_API)).thenReturn(API_ID); - when(ctx.request()).thenReturn(request); - when(request.timestamp()).thenReturn(System.currentTimeMillis()); + when(ctx.timestamp()).thenReturn(System.currentTimeMillis()); // subscription found with this security token final Subscription subscription = mock(Subscription.class); @@ -227,7 +225,7 @@ void canExecute_shouldReturnTrue_whenPolicyHasSecurityToken_butFoundSubscription when(subscription.isTimeValid(anyLong())).thenReturn(true); // subscription time is OK when(subscriptionService.getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID)).thenReturn(Optional.of(subscription)); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); verify(subscriptionService).getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID); @@ -246,7 +244,7 @@ void execute_shouldReturnFalse_whenExceptionOccurredWhileSearchingSubscriptions( when(subscriptionService.getByApiAndSecurityToken(API_ID, securityToken, PLAN_ID)) .thenThrow(new RuntimeException("Mock TechnicalException")); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(false); @@ -258,7 +256,7 @@ void execute_shouldReturnPolicyOrderWhenCallingOrder() { final int order = 123; when(policy.order()).thenReturn(order); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); assertEquals(order, cut.order()); } @@ -266,7 +264,7 @@ void execute_shouldReturnPolicyOrderWhenCallingOrder() { void execute_shouldExecutePolicyOnRequest() { when(policy.onRequest(ctx)).thenReturn(Completable.complete()); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.execute(ctx, ExecutionPhase.REQUEST).test(); obs.assertResult(); @@ -278,7 +276,7 @@ void canExecute_shouldReturnTrue_whenValidateSubscriptionIsEscaped() { when(ctx.getInternalAttribute(ATTR_INTERNAL_VALIDATE_SUBSCRIPTION)).thenReturn(false); when(plan.getId()).thenReturn(PLAN_ID); - final SecurityPlan cut = new SecurityPlan(plan.getId(), policy, plan.getSelectionRule()); + final HttpSecurityPlan cut = new HttpSecurityPlan(plan.getId(), policy, plan.getSelectionRule()); final TestObserver obs = cut.canExecute(ctx).test(); obs.assertResult(true); diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorTest.java b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorTest.java index 6363710c8f3..a1b6125ab9b 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorTest.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/test/java/io/gravitee/gateway/reactive/handlers/api/v4/DefaultApiReactorTest.java @@ -73,7 +73,7 @@ import io.gravitee.gateway.reactive.handlers.api.adapter.invoker.ConnectionHandlerAdapter; import io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChainFactory; import io.gravitee.gateway.reactive.handlers.api.v4.processor.ApiProcessorChainFactory; -import io.gravitee.gateway.reactive.handlers.api.v4.security.SecurityChain; +import io.gravitee.gateway.reactive.handlers.api.v4.security.HttpSecurityChain; import io.gravitee.gateway.reactive.policy.PolicyManager; import io.gravitee.gateway.reactor.accesspoint.ReactableAccessPoint; import io.gravitee.gateway.reactor.handler.Acceptor; @@ -267,7 +267,7 @@ class DefaultApiReactorTest { private io.gravitee.gateway.reactive.handlers.api.v4.flow.FlowChain apiFlowChain; @Mock - private SecurityChain securityChain; + private HttpSecurityChain securityChain; @Mock private RequestTimeoutConfiguration requestTimeoutConfiguration; @@ -405,7 +405,7 @@ private DefaultApiReactor buildApiReactor() { ReflectionTestUtils.setField(defaultApiReactor, "entrypointConnectorResolver", entrypointConnectorResolver); ReflectionTestUtils.setField(defaultApiReactor, "defaultInvoker", defaultInvoker); defaultApiReactor.doStart(); - ReflectionTestUtils.setField(defaultApiReactor, "securityChain", securityChain); + ReflectionTestUtils.setField(defaultApiReactor, "httpSecurityChain", securityChain); } catch (Exception e) { fail(e); } diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/AbstractPolicyChain.java b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/AbstractPolicyChain.java new file mode 100644 index 00000000000..bbfb137d72f --- /dev/null +++ b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/AbstractPolicyChain.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.gateway.reactive.policy; + +import io.gravitee.gateway.reactive.api.ExecutionPhase; +import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext; +import io.gravitee.gateway.reactive.api.policy.base.BasePolicy; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Flowable; +import jakarta.annotation.Nonnull; +import java.util.List; +import lombok.extern.slf4j.Slf4j; + +/** + * AbstractPolicyChain is responsible for executing a given list of policies respecting the original order. + * Policy execution must occur in an ordered sequence, one by one. + * It is the responsibility of the chain to handle any policy execution error and stop the entire execution of the policy chain. + * + * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) + * @author GraviteeSource Team + */ +@Slf4j +public abstract class AbstractPolicyChain implements PolicyChain { + + protected final String id; + protected final ExecutionPhase phase; + protected final Flowable policies; + + /** + * Creates a policy chain with the given list of policies. + * + * @param id an arbitrary id that helps to identify the policy chain at execution time. + * @param policies the list of the policies to be part of the execution chain. + * @param phase the execution phase that will be used to determine the method of the policies to execute. + */ + public AbstractPolicyChain(@Nonnull String id, @Nonnull List policies, @Nonnull ExecutionPhase phase) { + this.id = id; + this.phase = phase; + this.policies = Flowable.fromIterable(policies); + } + + @Override + public String getId() { + return id; + } + + /** + * Executes all the policies composing the chain. + * + * @param ctx the current context that will be passed to each policy to be executed. + * + * @return a {@link Completable} that completes when all the policies of the chain have been executed or the chain has been interrupted. + * The {@link Completable} may complete in error in case of any error occurred while executing the policies. + */ + @Override + public Completable execute(BaseExecutionContext ctx) { + return policies.concatMapCompletable(policy -> executePolicy(ctx, policy)); + } + + protected abstract Completable executePolicy(final BaseExecutionContext ctx, final T policy); +} diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/HttpPolicyChain.java b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/HttpPolicyChain.java index 83fbdf2f465..3443b972f05 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/HttpPolicyChain.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/policy/HttpPolicyChain.java @@ -16,6 +16,7 @@ package io.gravitee.gateway.reactive.policy; import io.gravitee.gateway.reactive.api.ExecutionPhase; +import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext; import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; import io.gravitee.gateway.reactive.api.hook.Hookable; import io.gravitee.gateway.reactive.api.hook.HttpHook; @@ -24,12 +25,10 @@ import io.gravitee.gateway.reactive.api.policy.http.HttpPolicy; import io.gravitee.gateway.reactive.core.hook.HookHelper; import io.reactivex.rxjava3.core.Completable; -import io.reactivex.rxjava3.core.Flowable; import jakarta.annotation.Nonnull; import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** * PolicyChain is responsible for executing a given list of policies respecting the original order. @@ -39,13 +38,9 @@ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) * @author GraviteeSource Team */ -public class HttpPolicyChain implements Hookable, PolicyChain { +@Slf4j +public class HttpPolicyChain extends AbstractPolicyChain implements Hookable { - private final Logger log = LoggerFactory.getLogger(HttpPolicyChain.class); - - private final String id; - private final Flowable policies; - private final ExecutionPhase phase; private List policyHooks; private List policyMessageHooks; @@ -57,14 +52,7 @@ public class HttpPolicyChain implements Hookable, PolicyChain policies, @Nonnull ExecutionPhase phase) { - this.id = id; - this.phase = phase; - this.policies = Flowable.fromIterable(policies); - } - - @Override - public String getId() { - return id; + super(id, policies, phase); } @Override @@ -84,33 +72,17 @@ public void addHooks(final List hooks) { }); } - /** - * Executes all the policies composing the chain. - * - * @param ctx the current context that will be passed to each policy to be executed. - * - * @return a {@link Completable} that completes when all the policies of the chain have been executed or the chain has been interrupted. - * The {@link Completable} may complete in error in case of any error occurred while executing the policies. - */ @Override - public Completable execute(HttpExecutionContext ctx) { - return policies.concatMapCompletable(policy -> executePolicy(ctx, policy)); - } - - private Completable executePolicy(final HttpExecutionContext ctx, final HttpPolicy policy) { + protected Completable executePolicy(final BaseExecutionContext baseCtx, final HttpPolicy policy) { log.debug("Executing policy {} on phase {} in policy chain {}", policy.id(), phase, id); - switch (phase) { - case REQUEST: - return HookHelper.hook(() -> policy.onRequest(ctx), policy.id(), policyHooks, ctx, phase); - case RESPONSE: - return HookHelper.hook(() -> policy.onResponse(ctx), policy.id(), policyHooks, ctx, phase); - case MESSAGE_REQUEST: - return HookHelper.hook(() -> policy.onMessageRequest(ctx), policy.id(), policyMessageHooks, ctx, phase); - case MESSAGE_RESPONSE: - return HookHelper.hook(() -> policy.onMessageResponse(ctx), policy.id(), policyMessageHooks, ctx, phase); - default: - return Completable.error(new IllegalArgumentException("Execution phase unknown")); - } + HttpExecutionContext ctx = (HttpExecutionContext) baseCtx; + return switch (phase) { + case REQUEST -> HookHelper.hook(() -> policy.onRequest(ctx), policy.id(), policyHooks, ctx, phase); + case RESPONSE -> HookHelper.hook(() -> policy.onResponse(ctx), policy.id(), policyHooks, ctx, phase); + case MESSAGE_REQUEST -> HookHelper.hook(() -> policy.onMessageRequest(ctx), policy.id(), policyMessageHooks, ctx, phase); + case MESSAGE_RESPONSE -> HookHelper.hook(() -> policy.onMessageResponse(ctx), policy.id(), policyMessageHooks, ctx, phase); + default -> Completable.error(new IllegalArgumentException("Execution phase unknown")); + }; } } diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/AbstractPolicyChainFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/AbstractPolicyChainFactory.java new file mode 100644 index 00000000000..5ebea31ecde --- /dev/null +++ b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/AbstractPolicyChainFactory.java @@ -0,0 +1,107 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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 io.gravitee.gateway.reactive.v4.policy; + +import io.gravitee.definition.model.v4.flow.AbstractFlow; +import io.gravitee.definition.model.v4.flow.Flow; +import io.gravitee.definition.model.v4.flow.step.Step; +import io.gravitee.gateway.policy.PolicyMetadata; +import io.gravitee.gateway.reactive.api.ExecutionPhase; +import io.gravitee.gateway.reactive.api.policy.base.BasePolicy; +import io.gravitee.gateway.reactive.policy.AbstractPolicyChain; +import io.gravitee.gateway.reactive.policy.HttpPolicyChain; +import io.gravitee.gateway.reactive.policy.PolicyManager; +import io.gravitee.node.api.cache.Cache; +import io.gravitee.node.api.cache.CacheConfiguration; +import io.gravitee.node.plugin.cache.common.InMemoryCache; +import java.util.List; +import java.util.Objects; + +/** + * {@link AbstractPolicyChainFactory} that can be instantiated per-api or per-organization, and optimized to maximize the reuse of created {@link AbstractPolicyChain} thanks to a cache. + * + * WARNING: this factory must absolutely be created per api to ensure proper cache destruction when deploying / un-deploying the api. + * + * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) + * @author GraviteeSource Team + */ +public abstract class AbstractPolicyChainFactory> + implements PolicyChainFactory { + + public static final long CACHE_MAX_SIZE = 15; + public static final long CACHE_TIME_TO_IDLE_IN_MS = 3_600_000; + protected static final String ID_SEPARATOR = "-"; + + protected final PolicyManager policyManager; + protected final Cache policyChains; + + public AbstractPolicyChainFactory(final String id, final PolicyManager policyManager) { + this.policyManager = policyManager; + + final CacheConfiguration cacheConfiguration = CacheConfiguration + .builder() + .maxSize(CACHE_MAX_SIZE) + .timeToIdleInMs(CACHE_TIME_TO_IDLE_IN_MS) + .build(); + + this.policyChains = new InMemoryCache<>(id + getIdSuffix(), cacheConfiguration); + } + + protected abstract String getIdSuffix(); + + protected abstract List getSteps(F flow, ExecutionPhase phase); + + protected abstract PC buildPolicyChain(String flowChainId, F flow, ExecutionPhase phase, List policies); + + protected abstract PolicyMetadata buildPolicyMetadata(Step step); + + /** + * Creates a policy chain from the provided flow, for the given execution phase. + * The policies composing the policy chain depends on the specified execution phase and the API type (Native vs HTTP): + * + * @param flowChainId the flow chain id in which one the policy chain will be executed + * @param flow the flow where to extract the policies to create the policy chain. + * @param phase the execution phase used to select the relevant steps list in the flow. + * + * @return the created {@link AbstractPolicyChain}. + */ + @Override + public PC create(final String flowChainId, F flow, ExecutionPhase phase) { + final String key = getFlowKey(flow, phase); + PC policyChain = policyChains.get(key); + + if (policyChain == null) { + final List steps = getSteps(flow, phase); + + final List policies = steps + .stream() + .filter(Step::isEnabled) + .map(this::buildPolicyMetadata) + .map(policyMetadata -> (T) policyManager.create(phase, policyMetadata)) + .filter(Objects::nonNull) + .toList(); + + policyChain = buildPolicyChain(flowChainId, flow, phase, policies); + policyChains.put(key, policyChain); + } + + return policyChain; + } + + private String getFlowKey(F flow, ExecutionPhase phase) { + return flow.hashCode() + "-" + phase.name(); + } +} diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/HttpPolicyChainFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/HttpPolicyChainFactory.java index 525dba6138f..25ad440ac0d 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/HttpPolicyChainFactory.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-policy/src/main/java/io/gravitee/gateway/reactive/v4/policy/HttpPolicyChainFactory.java @@ -27,44 +27,25 @@ import io.gravitee.gateway.reactive.policy.HttpPolicyChain; import io.gravitee.gateway.reactive.policy.PolicyManager; import io.gravitee.gateway.reactive.policy.tracing.TracingPolicyHook; -import io.gravitee.node.api.cache.Cache; -import io.gravitee.node.api.cache.CacheConfiguration; -import io.gravitee.node.api.configuration.Configuration; -import io.gravitee.node.plugin.cache.common.InMemoryCache; import io.netty.util.internal.StringUtil; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Objects; -import java.util.stream.Collectors; /** - * {@link PolicyChainFactory} that can be instantiated per-api or per-organization, and optimized to maximize the reuse of created {@link HttpPolicyChain} thanks to a cache. + * {@link HttpPolicyChainFactory} that can be instantiated per-api or per-organization, and optimized to maximize the reuse of created {@link HttpPolicyChain} thanks to a cache. * * WARNING: this factory must absolutely be created per api to ensure proper cache destruction when deploying / un-deploying the api. * * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) * @author GraviteeSource Team */ -public class HttpPolicyChainFactory implements PolicyChainFactory { +public class HttpPolicyChainFactory extends AbstractPolicyChainFactory { - public static final long CACHE_MAX_SIZE = 15; - public static final long CACHE_TIME_TO_IDLE_IN_MS = 3_600_000; - private static final String ID_SEPARATOR = "-"; protected final List policyHooks = new ArrayList<>(); - protected final PolicyManager policyManager; - protected final Cache policyChains; public HttpPolicyChainFactory(final String id, final PolicyManager policyManager, final boolean tracing) { - this.policyManager = policyManager; - - final CacheConfiguration cacheConfiguration = CacheConfiguration - .builder() - .maxSize(CACHE_MAX_SIZE) - .timeToIdleInMs(CACHE_TIME_TO_IDLE_IN_MS) - .build(); - - this.policyChains = new InMemoryCache<>(id + "-policyChainFactory", cacheConfiguration); + super(id, policyManager); initPolicyHooks(tracing); } @@ -74,47 +55,32 @@ protected void initPolicyHooks(final boolean tracing) { } } - /** - * Creates a policy chain from the provided flow, for the given execution phase. - * The policies composing the policy chain, depends on the specified execution phase: - *
      - *
    • {@link ExecutionPhase#REQUEST}: {@link Flow#getRequest()}
    • - *
    • {@link ExecutionPhase#MESSAGE_REQUEST}: {@link Flow#getPublish()}
    • - *
    • {@link ExecutionPhase#RESPONSE}: {@link Flow#getResponse()}
    • - *
    • {@link ExecutionPhase#MESSAGE_RESPONSE}: {@link Flow#getSubscribe()}
    • - *
    - * - * @param flowChainId the flow chain id in which one the policy chain will be executed - * @param flow the flow where to extract the policies to create the policy chain. - * @param phase the execution phase used to select the relevant steps list in the flow. - * - * @return the created {@link HttpPolicyChain}. - */ @Override - public HttpPolicyChain create(final String flowChainId, Flow flow, ExecutionPhase phase) { - final String key = getFlowKey(flow, phase); - HttpPolicyChain policyChain = policyChains.get(key); - - if (policyChain == null) { - final List steps = getSteps(flow, phase); + protected String getIdSuffix() { + return "-policyChainFactory"; + } - final List policies = steps - .stream() - .filter(Step::isEnabled) - .map(this::buildPolicyMetadata) - .map(policyMetadata -> (HttpPolicy) policyManager.create(phase, policyMetadata)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + @Override + protected List getSteps(Flow flow, ExecutionPhase phase) { + final List steps = + switch (phase) { + case REQUEST -> flow.getRequest(); + case RESPONSE -> flow.getResponse(); + default -> new ArrayList<>(); + }; - String policyChainId = getFlowId(flowChainId, flow); - policyChain = new HttpPolicyChain(policyChainId, policies, phase); - policyChain.addHooks(policyHooks); - policyChains.put(key, policyChain); - } + return steps != null ? steps : new ArrayList<>(); + } - return policyChain; + @Override + protected HttpPolicyChain buildPolicyChain(String flowChainId, Flow flow, ExecutionPhase phase, List policies) { + String policyChainId = getPolicyChainId(flowChainId, flow); + HttpPolicyChain httpPolicyChain = new HttpPolicyChain(policyChainId, policies, phase); + httpPolicyChain.addHooks(policyHooks); + return httpPolicyChain; } + @Override protected PolicyMetadata buildPolicyMetadata(Step step) { final PolicyMetadata policyMetadata = new PolicyMetadata(step.getPolicy(), step.getConfiguration(), step.getCondition()); policyMetadata.metadata().put(PolicyMetadata.MetadataKeys.EXECUTION_MODE, ExecutionMode.V4_EMULATION_ENGINE); @@ -122,28 +88,7 @@ protected PolicyMetadata buildPolicyMetadata(Step step) { return policyMetadata; } - protected List getSteps(Flow flow, ExecutionPhase phase) { - final List steps; - - switch (phase) { - case REQUEST: - steps = flow.getRequest(); - break; - case RESPONSE: - steps = flow.getResponse(); - break; - default: - steps = new ArrayList<>(); - } - - return steps != null ? steps : new ArrayList<>(); - } - - private String getFlowKey(Flow flow, ExecutionPhase phase) { - return flow.hashCode() + "-" + phase.name(); - } - - private String getFlowId(final String flowChainId, final Flow flow) { + private String getPolicyChainId(final String flowChainId, final Flow flow) { StringBuilder flowNameBuilder = new StringBuilder(flowChainId).append(ID_SEPARATOR); if (StringUtil.isNullOrEmpty(flow.getName())) { flow diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-reactor/src/main/java/io/gravitee/gateway/reactive/reactor/v4/reactor/ReactorFactory.java b/gravitee-apim-gateway/gravitee-apim-gateway-reactor/src/main/java/io/gravitee/gateway/reactive/reactor/v4/reactor/ReactorFactory.java index a2f77477094..72512e33e69 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-reactor/src/main/java/io/gravitee/gateway/reactive/reactor/v4/reactor/ReactorFactory.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-reactor/src/main/java/io/gravitee/gateway/reactive/reactor/v4/reactor/ReactorFactory.java @@ -15,9 +15,7 @@ */ package io.gravitee.gateway.reactive.reactor.v4.reactor; -import io.gravitee.definition.model.Api; import io.gravitee.gateway.reactor.Reactable; -import io.gravitee.gateway.reactor.ReactableApi; import io.gravitee.gateway.reactor.handler.ReactorHandler; /** diff --git a/gravitee-apim-gateway/gravitee-apim-gateway-services/gravitee-apim-gateway-services-debug/src/main/java/io/gravitee/gateway/reactive/debug/handlers/api/DebugSyncApiReactor.java b/gravitee-apim-gateway/gravitee-apim-gateway-services/gravitee-apim-gateway-services-debug/src/main/java/io/gravitee/gateway/reactive/debug/handlers/api/DebugSyncApiReactor.java index d65020ad5ec..0554499400e 100644 --- a/gravitee-apim-gateway/gravitee-apim-gateway-services/gravitee-apim-gateway-services-debug/src/main/java/io/gravitee/gateway/reactive/debug/handlers/api/DebugSyncApiReactor.java +++ b/gravitee-apim-gateway/gravitee-apim-gateway-services/gravitee-apim-gateway-services-debug/src/main/java/io/gravitee/gateway/reactive/debug/handlers/api/DebugSyncApiReactor.java @@ -102,7 +102,7 @@ public Completable handle(final MutableExecutionContext ctx) { @Override protected void doStart() throws Exception { super.doStart(); - securityChain.addHooks(new DebugPolicyHook()); + httpSecurityChain.addHooks(new DebugPolicyHook()); } @Override