Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -119,7 +119,7 @@ public class SyncApiReactor extends AbstractLifecycleComponent<ReactorHandler> 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,
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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 =
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SecurityPlanHook> {
@Slf4j
public abstract class AbstractSecurityChain<
P extends AbstractSecurityPlan<? extends BaseSecurityPolicy, C>, 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<Boolean> TRUE = Single.just(true);
protected static final Single<Boolean> FALSE = Single.just(false);
private static final Logger log = LoggerFactory.getLogger(SecurityChain.class);
private final Flowable<SecurityPlan> chain;
private final ExecutionPhase executionPhase;
private final Flowable<P> chain;

private List<SecurityPlanHook> 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<SecurityPlan> securityPlans, ExecutionPhase executionPhase) {
public AbstractSecurityChain(Flowable<P> securityPlans) {
this.chain = securityPlans;
this.executionPhase = executionPhase;
}

protected abstract Completable sendError(C ctx, ExecutionFailure failure);

protected abstract Single<Boolean> 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
Expand All @@ -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)
);
}
Expand All @@ -137,7 +115,7 @@ public Completable execute(HttpExecutionContext ctx) {
});
}

private Single<Boolean> continueChain(HttpExecutionContext ctx, SecurityPlan securityPlan) {
private Single<Boolean> continueChain(C ctx, P securityPlan) {
return securityPlan
.canExecute(ctx)
.onErrorResumeNext(throwable -> {
Expand All @@ -147,19 +125,9 @@ private Single<Boolean> 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<SecurityPlanHook> hooks) {
if (this.securityPlanHooks == null) {
this.securityPlanHooks = new ArrayList<>();
}
this.securityPlanHooks.addAll(hooks);
}
}
Original file line number Diff line number Diff line change
@@ -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<HttpSecurityPlan, HttpPlainExecutionContext>
implements Hookable<SecurityPlanHook> {

private final ExecutionPhase executionPhase;

private List<SecurityPlanHook> 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<HttpSecurityPlan> securityPlans, ExecutionPhase executionPhase) {
super(securityPlans);
this.executionPhase = executionPhase;
}

@Override
protected Completable sendError(HttpPlainExecutionContext ctx, ExecutionFailure failure) {
return ctx.interruptWith(failure);
}

@Override
protected Single<Boolean> 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<SecurityPlanHook> hooks) {
if (this.securityPlanHooks == null) {
this.securityPlanHooks = new ArrayList<>();
}
this.securityPlanHooks.addAll(hooks);
}
}
Loading