Skip to content

Commit 7d95403

Browse files
committed
refactor(gateway): create abstractions
Creates: - AbstractSecurityPlan - AbstractSecurityChain - AbstractReactorFactory - AbstractPolicyChain - AbstractPolicyChainFactory
1 parent 5929eae commit 7d95403

File tree

13 files changed

+833
-531
lines changed

13 files changed

+833
-531
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright © 2015 The Gravitee team (http://gravitee.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.gravitee.gateway.reactive.handlers.api.security;
17+
18+
import static io.gravitee.common.http.HttpStatusCode.SERVICE_UNAVAILABLE_503;
19+
import static io.gravitee.common.http.HttpStatusCode.UNAUTHORIZED_401;
20+
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_FLOW_STAGE;
21+
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_SECURITY_SKIP;
22+
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_SECURITY_TOKEN;
23+
import static io.reactivex.rxjava3.core.Completable.defer;
24+
25+
import io.gravitee.gateway.reactive.api.ExecutionFailure;
26+
import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext;
27+
import io.gravitee.gateway.reactive.api.policy.base.BaseSecurityPolicy;
28+
import io.gravitee.gateway.reactive.handlers.api.security.plan.AbstractSecurityPlan;
29+
import io.reactivex.rxjava3.core.Completable;
30+
import io.reactivex.rxjava3.core.Flowable;
31+
import io.reactivex.rxjava3.core.Single;
32+
import java.util.Objects;
33+
import lombok.extern.slf4j.Slf4j;
34+
35+
/**
36+
* {@link AbstractSecurityChain} is a special chain dedicated to execute policy associated with plans.
37+
* The security chain is responsible to create {@link AbstractSecurityPlan} for each plan of the api and executed them in order.
38+
* Only the first {@link AbstractSecurityPlan} that can handle the current request is executed.
39+
* The result of the security chain execution depends on this {@link AbstractSecurityPlan} execution.
40+
*
41+
* @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
42+
* @author GraviteeSource Team
43+
*/
44+
@Slf4j
45+
public abstract class AbstractSecurityChain<
46+
P extends AbstractSecurityPlan<? extends BaseSecurityPolicy, C>, C extends BaseExecutionContext
47+
> {
48+
49+
protected static final String PLAN_UNRESOLVABLE = "GATEWAY_PLAN_UNRESOLVABLE";
50+
protected static final String PLAN_RESOLUTION_FAILURE = "GATEWAY_PLAN_RESOLUTION_FAILURE";
51+
protected static final String UNAUTHORIZED_MESSAGE = "Unauthorized";
52+
protected static final String TEMPORARILY_UNAVAILABLE_MESSAGE = "Temporarily Unavailable";
53+
protected static final String ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE = "securityChain.planResolutionFailure";
54+
55+
protected static final Single<Boolean> TRUE = Single.just(true);
56+
protected static final Single<Boolean> FALSE = Single.just(false);
57+
private final Flowable<P> chain;
58+
59+
public AbstractSecurityChain(Flowable<P> securityPlans) {
60+
this.chain = securityPlans;
61+
}
62+
63+
protected abstract Completable sendError(C ctx, ExecutionFailure failure);
64+
65+
protected abstract Single<Boolean> executePlan(P securityPlan, C ctx);
66+
67+
/**
68+
* Executes the security chain by executing all the {@link AbstractSecurityPlan}s in an ordered sequence.
69+
* It's up to each {@link AbstractSecurityPlan} to provide its order. The lower is the order, the highest priority is.
70+
* The result of the security chain execution depends on the first {@link AbstractSecurityPlan} able to execute the request.
71+
* If no {@link AbstractSecurityPlan} has been executed because there is no {@link AbstractSecurityPlan} in the chain or none of them can execute the request,
72+
* then the `sendError` method is called and the {@link Completable} returns an error.
73+
*
74+
* @param ctx the current execution context.
75+
* @return a {@link Completable} that completes if the request has been successfully handled by a {@link AbstractSecurityPlan} or returns
76+
* an error if no {@link AbstractSecurityPlan} can execute the request or the {@link AbstractSecurityPlan} failed.
77+
*/
78+
public Completable execute(C ctx) {
79+
return defer(() -> {
80+
if (!Objects.equals(true, ctx.getInternalAttribute(ATTR_INTERNAL_SECURITY_SKIP))) {
81+
return chain
82+
.concatMapSingle(securityPlan -> continueChain(ctx, securityPlan))
83+
.any(Boolean::booleanValue)
84+
.flatMapCompletable(securityHandled -> {
85+
if (Boolean.FALSE.equals(securityHandled)) {
86+
Throwable throwable = ctx.getInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE);
87+
if (throwable != null) {
88+
return sendError(
89+
ctx,
90+
new ExecutionFailure(SERVICE_UNAVAILABLE_503)
91+
.key(PLAN_RESOLUTION_FAILURE)
92+
.message(TEMPORARILY_UNAVAILABLE_MESSAGE)
93+
);
94+
}
95+
return sendError(
96+
ctx,
97+
new ExecutionFailure(UNAUTHORIZED_401).key(PLAN_UNRESOLVABLE).message(UNAUTHORIZED_MESSAGE)
98+
);
99+
}
100+
return Completable.complete();
101+
})
102+
.doOnSubscribe(disposable -> {
103+
log.debug("Executing security chain");
104+
ctx.putInternalAttribute(ATTR_INTERNAL_FLOW_STAGE, "security");
105+
})
106+
.doOnTerminate(() -> {
107+
ctx.removeInternalAttribute(ATTR_INTERNAL_FLOW_STAGE);
108+
ctx.removeInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE);
109+
ctx.removeInternalAttribute(ATTR_INTERNAL_SECURITY_TOKEN);
110+
});
111+
}
112+
113+
log.debug("Skipping security chain because it has been explicitly required");
114+
return Completable.complete();
115+
});
116+
}
117+
118+
private Single<Boolean> continueChain(C ctx, P securityPlan) {
119+
return securityPlan
120+
.canExecute(ctx)
121+
.onErrorResumeNext(throwable -> {
122+
log.error("An error occurred while checking if security plan {} can be executed", securityPlan.id(), throwable);
123+
ctx.setInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE, throwable);
124+
return FALSE;
125+
})
126+
.flatMap(canExecute -> {
127+
if (Boolean.TRUE.equals(canExecute)) {
128+
return executePlan(securityPlan, ctx);
129+
}
130+
return FALSE;
131+
});
132+
}
133+
}

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/security/SecurityChain.java

Lines changed: 20 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,11 @@
1515
*/
1616
package io.gravitee.gateway.reactive.handlers.api.security;
1717

18-
import static io.gravitee.common.http.HttpStatusCode.SERVICE_UNAVAILABLE_503;
19-
import static io.gravitee.common.http.HttpStatusCode.UNAUTHORIZED_401;
20-
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_FLOW_STAGE;
21-
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_SECURITY_SKIP;
22-
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_SECURITY_TOKEN;
23-
import static io.reactivex.rxjava3.core.Completable.defer;
24-
2518
import io.gravitee.definition.model.Api;
2619
import io.gravitee.gateway.reactive.api.ExecutionFailure;
2720
import io.gravitee.gateway.reactive.api.ExecutionPhase;
2821
import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext;
22+
import io.gravitee.gateway.reactive.api.context.http.HttpPlainExecutionContext;
2923
import io.gravitee.gateway.reactive.api.hook.Hookable;
3024
import io.gravitee.gateway.reactive.api.hook.SecurityPlanHook;
3125
import io.gravitee.gateway.reactive.core.hook.HookHelper;
@@ -40,8 +34,6 @@
4034
import java.util.List;
4135
import java.util.Objects;
4236
import java.util.stream.Collectors;
43-
import org.slf4j.Logger;
44-
import org.slf4j.LoggerFactory;
4537

4638
/**
4739
* {@link SecurityChain} is a special chain dedicated to execute policy associated with plans.
@@ -52,24 +44,14 @@
5244
* @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
5345
* @author GraviteeSource Team
5446
*/
55-
public class SecurityChain implements Hookable<SecurityPlanHook> {
56-
57-
protected static final String PLAN_UNRESOLVABLE = "GATEWAY_PLAN_UNRESOLVABLE";
58-
protected static final String PLAN_RESOLUTION_FAILURE = "GATEWAY_PLAN_RESOLUTION_FAILURE";
59-
protected static final String UNAUTHORIZED_MESSAGE = "Unauthorized";
60-
protected static final String TEMPORARILY_UNAVAILABLE_MESSAGE = "Temporarily Unavailable";
61-
protected static final String ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE = "securityChain.planResolutionFailure";
47+
public class SecurityChain extends AbstractSecurityChain<SecurityPlan, HttpPlainExecutionContext> implements Hookable<SecurityPlanHook> {
6248

63-
protected static final Single<Boolean> TRUE = Single.just(true);
64-
protected static final Single<Boolean> FALSE = Single.just(false);
65-
private static final Logger log = LoggerFactory.getLogger(SecurityChain.class);
66-
private final Flowable<SecurityPlan> chain;
6749
private final ExecutionPhase executionPhase;
6850

6951
private List<SecurityPlanHook> securityPlanHooks;
7052

7153
public SecurityChain(Api api, PolicyManager policyManager, ExecutionPhase executionPhase) {
72-
this(
54+
super(
7355
Flowable.fromIterable(
7456
api
7557
.getPlans()
@@ -78,81 +60,32 @@ public SecurityChain(Api api, PolicyManager policyManager, ExecutionPhase execut
7860
.filter(Objects::nonNull)
7961
.sorted(Comparator.comparingInt(SecurityPlan::order))
8062
.collect(Collectors.toList())
81-
),
82-
executionPhase
63+
)
8364
);
65+
this.executionPhase = executionPhase;
8466
}
8567

8668
public SecurityChain(Flowable<SecurityPlan> securityPlans, ExecutionPhase executionPhase) {
87-
this.chain = securityPlans;
69+
super(securityPlans);
8870
this.executionPhase = executionPhase;
8971
}
9072

91-
/**
92-
* Executes the security chain by executing all the {@link SecurityPlan}s in an ordered sequence.
93-
* It's up to each {@link SecurityPlan} to provide its order. The lower is the order, the highest priority is.
94-
* The result of the security chain execution depends on the first {@link SecurityPlan} able to execute the request.
95-
* If no {@link SecurityPlan} has been executed because there is no {@link SecurityPlan} in the chain or none of them can execute the request,
96-
* then the chain is interrupted with a 401 response status and the {@link Completable} returns an error.
97-
*
98-
* @param ctx the current execution context.
99-
* @return a {@link Completable} that completes if the request has been successfully handled by a {@link SecurityPlan} or returns
100-
* an error if no {@link SecurityPlan} can execute the request or the {@link SecurityPlan} failed.
101-
*/
102-
public Completable execute(HttpExecutionContext ctx) {
103-
return defer(() -> {
104-
if (!Objects.equals(true, ctx.getInternalAttribute(ATTR_INTERNAL_SECURITY_SKIP))) {
105-
return chain
106-
.concatMapSingle(securityPlan -> continueChain(ctx, securityPlan))
107-
.any(Boolean::booleanValue)
108-
.flatMapCompletable(securityHandled -> {
109-
if (Boolean.FALSE.equals(securityHandled)) {
110-
Throwable throwable = ctx.getInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE);
111-
if (throwable != null) {
112-
return ctx.interruptWith(
113-
new ExecutionFailure(SERVICE_UNAVAILABLE_503)
114-
.key(PLAN_RESOLUTION_FAILURE)
115-
.message(TEMPORARILY_UNAVAILABLE_MESSAGE)
116-
);
117-
}
118-
return ctx.interruptWith(
119-
new ExecutionFailure(UNAUTHORIZED_401).key(PLAN_UNRESOLVABLE).message(UNAUTHORIZED_MESSAGE)
120-
);
121-
}
122-
return Completable.complete();
123-
})
124-
.doOnSubscribe(disposable -> {
125-
log.debug("Executing security chain");
126-
ctx.putInternalAttribute(ATTR_INTERNAL_FLOW_STAGE, "security");
127-
})
128-
.doOnTerminate(() -> {
129-
ctx.removeInternalAttribute(ATTR_INTERNAL_FLOW_STAGE);
130-
ctx.removeInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE);
131-
ctx.removeInternalAttribute(ATTR_INTERNAL_SECURITY_TOKEN);
132-
});
133-
}
134-
135-
log.debug("Skipping security chain because it has been explicitly required");
136-
return Completable.complete();
137-
});
73+
@Override
74+
protected Completable sendError(HttpPlainExecutionContext ctx, ExecutionFailure failure) {
75+
return ctx.interruptWith(failure);
13876
}
13977

140-
private Single<Boolean> continueChain(HttpExecutionContext ctx, SecurityPlan securityPlan) {
141-
return securityPlan
142-
.canExecute(ctx)
143-
.onErrorResumeNext(throwable -> {
144-
log.error("An error occurred while checking if security plan {} can be executed", securityPlan.id(), throwable);
145-
ctx.setInternalAttribute(ATTR_INTERNAL_PLAN_RESOLUTION_FAILURE, throwable);
146-
return FALSE;
147-
})
148-
.flatMap(canExecute -> {
149-
if (Boolean.TRUE.equals(canExecute)) {
150-
return HookHelper
151-
.hook(() -> securityPlan.execute(ctx, executionPhase), securityPlan.id(), securityPlanHooks, ctx, executionPhase)
152-
.andThen(TRUE);
153-
}
154-
return FALSE;
155-
});
78+
@Override
79+
protected Single<Boolean> executePlan(SecurityPlan securityPlan, HttpPlainExecutionContext ctx) {
80+
return HookHelper
81+
.hook(
82+
() -> securityPlan.execute(ctx, executionPhase),
83+
securityPlan.id(),
84+
securityPlanHooks,
85+
(HttpExecutionContext) ctx,
86+
executionPhase
87+
)
88+
.andThen(TRUE);
15689
}
15790

15891
@Override

0 commit comments

Comments
 (0)