Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove gateway control. #107

Merged
merged 2 commits into from
Jul 19, 2024
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 @@ -8,36 +8,13 @@
package org.gridsuite.gateway.filters;

import lombok.NonNull;
import org.gridsuite.gateway.ServiceURIsConfig;
import org.gridsuite.gateway.dto.AccessControlInfos;
import org.gridsuite.gateway.endpoints.EndPointElementServer;
import org.gridsuite.gateway.endpoints.EndPointServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.regex.Pattern;

import static org.gridsuite.gateway.GatewayConfig.END_POINT_SERVICE_NAME;
import static org.gridsuite.gateway.GatewayConfig.HEADER_USER_ID;
import static org.gridsuite.gateway.endpoints.EndPointElementServer.QUERY_PARAM_IDS;
import static org.springframework.http.HttpStatus.*;

/**
* @author Slimane Amar <slimane.amar at rte-france.com>
Expand All @@ -47,19 +24,6 @@ public class ElementAccessControllerGlobalPreFilter extends AbstractGlobalPreFil

private static final Logger LOGGER = LoggerFactory.getLogger(ElementAccessControllerGlobalPreFilter.class);

private static final String ROOT_CATEGORY_REACTOR = "reactor.";

private static final String ELEMENTS_ROOT_PATH = "elements";

private final WebClient webClient;

private final ApplicationContext applicationContext;

public ElementAccessControllerGlobalPreFilter(ApplicationContext context, ServiceURIsConfig servicesURIsConfig, WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl(servicesURIsConfig.getDirectoryServerBaseUri()).build();
this.applicationContext = context;
}

@Override
public int getOrder() {
// Before WebsocketRoutingFilter to control access
Expand All @@ -69,61 +33,8 @@ public int getOrder() {
@Override
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull GatewayFilterChain chain) {
LOGGER.debug("Filter : {}", getClass().getSimpleName());

RequestPath path = exchange.getRequest().getPath();

// Filter only requests to the endpoint servers with this pattern : /v<number>/<appli_root_path>
if (!Pattern.matches("/v(\\d)+/.*", path.value())) {
return chain.filter(exchange);
}

// Is an elements' endpoint with a controlled access ?
String endPointServiceName = Objects.requireNonNull((String) (Objects.requireNonNull((Route) exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)).getMetadata()).get(END_POINT_SERVICE_NAME));
EndPointServer endPointServer = applicationContext.containsBean(endPointServiceName) ? (EndPointServer) applicationContext.getBean(endPointServiceName) : null;
if (endPointServer == null || !endPointServer.hasElementsAccessControl()) {
return chain.filter(exchange);
}

// Is a root path with a controlled access ?
EndPointElementServer endPointElementServer = (EndPointElementServer) endPointServer;
if (endPointElementServer.isNotControlledRootPath(path.elements().get(3).value())) {
return chain.filter(exchange);
}

// Is a method allowed ?
if (!endPointElementServer.isAllowedMethod(exchange.getRequest().getMethod())) {
return completeWithCode(exchange, FORBIDDEN);
}

Optional<AccessControlInfos> accessControlInfos = endPointElementServer.getAccessControlInfos(exchange.getRequest());
return accessControlInfos.isEmpty() ? completeWithCode(exchange, FORBIDDEN) : isAccessAllowed(exchange, chain, accessControlInfos.get());
}

private Mono<Void> isAccessAllowed(ServerWebExchange exchange, GatewayFilterChain chain, AccessControlInfos accessControlInfos) {
ServerHttpRequest httpRequest = exchange.getRequest();
HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
return webClient
.head()
.uri(uriBuilder -> uriBuilder
.path(httpRequest.getPath().subPath(0, 3).value()) // version
.path(ELEMENTS_ROOT_PATH)
.queryParam(QUERY_PARAM_IDS, accessControlInfos.getElementUuids())
.build()
)
.header(HEADER_USER_ID, Objects.requireNonNull(httpHeaders.get(HEADER_USER_ID)).get(0))
.exchangeToMono(response -> {
HttpStatusCode httpStatusCode = response.statusCode();
if (httpStatusCode.equals(OK)) {
return chain.filter(exchange);
} else if (httpStatusCode.equals(NOT_FOUND)) {
return completeWithCode(exchange, NOT_FOUND);
} else if (httpStatusCode.equals(FORBIDDEN)) {
return completeWithCode(exchange, FORBIDDEN);
}
return response.createException().flatMap(Mono::error);
})
.publishOn(Schedulers.boundedElastic())
.log(ROOT_CATEGORY_REACTOR, Level.FINE);
//TODO: the control is disabled for the moment, it will be processed in another US. For more details contact slimane
return chain.filter(exchange);
}
}

90 changes: 47 additions & 43 deletions src/test/java/org/gridsuite/gateway/ElementAccessControlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

Expand Down Expand Up @@ -186,40 +185,45 @@ public void testGetElements() {
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// user2 not allowed
// user2 allowed
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user2"))
.willReturn(aResponse().withStatus(HttpStatus.FORBIDDEN.value())));
.willReturn(aResponse()));

stubFor(get(urlEqualTo(String.format("/v1/studies/%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

stubFor(get(urlEqualTo(String.format("/v1/studies/%s", uuid))).withHeader("userId", equalTo("user2"))
.willReturn(aResponse()));

stubFor(get(urlEqualTo(String.format("/v1/studies/metadata?ids=%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

stubFor(get(urlEqualTo(String.format("/v1/studies/metadata?ids=%s", uuid))).withHeader("userId", equalTo("user2"))
.willReturn(aResponse()));

stubFor(get(urlEqualTo(String.format("/v1/filters/%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

stubFor(get(urlEqualTo(String.format("/v1/contingency-lists/%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// No uuid element forbidden
webClient
.get().uri("study/v1/studies")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

// Bad uuid forbidden
// Bad uuid
webClient
.get().uri(String.format("study/v1/studies/%s", "badUuid"))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.get().uri(String.format("study/v1/studies/%s", (UUID) null))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.get().uri(String.format("study/v1/studies/%s", uuid))
Expand Down Expand Up @@ -249,25 +253,25 @@ public void testGetElements() {
.get().uri(String.format("study/v1/studies/%s", uuid))
.header("Authorization", "Bearer " + tokenUser2)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isOk();

webClient
.get().uri(String.format("study/v1/studies/metadata?ids=%s", uuid))
.header("Authorization", "Bearer " + tokenUser2)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isOk();

webClient
.get().uri(String.format("actions/v1/contingency-lists/%s", uuid))
.header("Authorization", "Bearer " + tokenUser2)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.get().uri(String.format("filter/v1/filters/%s", uuid))
.header("Authorization", "Bearer " + tokenUser2)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
}

@Test
Expand All @@ -282,11 +286,11 @@ public void testCreateElements() {
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// user2 not allowed
// user2 is also allowed
stubFor(head(urlEqualTo(String.format("/v1/directories?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user2"))
.willReturn(aResponse().withStatus(HttpStatus.FORBIDDEN.value())));
.willReturn(aResponse()));
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user2"))
.willReturn(aResponse().withStatus(HttpStatus.FORBIDDEN.value())));
.willReturn(aResponse()));

stubFor(post(urlEqualTo(String.format("/v1/explore/studies?%s=%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID, uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));
Expand All @@ -297,55 +301,55 @@ public void testCreateElements() {
stubFor(post(urlEqualTo(String.format("/v1/explore/filters?%s=%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID, uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// Direct creation of elements without going through the explor server is forbidden
// Direct creation of elements without going through the explore server
webClient
.post().uri("study/v1/studies")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.post().uri("actions/v1/script-contingency-lists")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.post().uri("filter/v1/filters")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

// Creation of elements without directory parent is forbidden
// Creation of elements without directory parent
webClient
.post().uri(String.format("explore/v1/explore/studies"))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

// Creation of elements with bad parameter for directory parent uuid is forbidden
// Creation of elements with bad parameter for directory parent uuid
webClient
.post().uri(String.format("explore/v1/explore/studies?%s=%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID + "bad", uuid))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

// Creation of elements with bad directory parent uuid is forbidden
// Creation of elements with bad directory parent uuid
webClient
.post().uri(String.format("explore/v1/explore/studies?%s=%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID, "badUuid"))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.post().uri(String.format("explore/v1/explore/studies?%s=%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID, null))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

// Creation of elements with multiple directory parent uuids is forbidden
// Creation of elements with multiple directory parent uuids
webClient
.post().uri(String.format("explore/v1/explore/studies?%s=%s,%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID, uuid, uuid))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.post().uri(String.format("explore/v1/explore/studies?%s=%s", ExploreServer.QUERY_PARAM_PARENT_DIRECTORY_ID, uuid))
Expand Down Expand Up @@ -384,7 +388,7 @@ public void testCreateSubElements() {
.post().uri("study/v1/studies")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.post().uri(String.format("study/v1/studies/%s/tree/nodes", uuid))
Expand All @@ -403,9 +407,9 @@ public void testUpdateElements() {
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// user2 not allowed
// user2 allowed
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user2"))
.willReturn(aResponse().withStatus(HttpStatus.FORBIDDEN.value())));
.willReturn(aResponse()));

stubFor(put(urlEqualTo(String.format("/v1/studies/%s/nodes/idNode", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));
Expand All @@ -416,22 +420,22 @@ public void testUpdateElements() {
stubFor(put(urlEqualTo(String.format("/v1/filters/%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// Put with no or bad uuid is forbidden
// Put with no or bad uuid
webClient
.put().uri("study/v1/studies/nodes/idNode")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.put().uri(String.format("study/v1/studies/%s/nodes/idNode", (UUID) null))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.put().uri(String.format("study/v1/studies/%s/nodes/idNode", "badUuid"))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.put().uri(String.format("study/v1/studies/%s/nodes/idNode", uuid))
Expand Down Expand Up @@ -462,9 +466,9 @@ public void testDeleteElements() {
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// user2 not allowed
// user2 allowed
stubFor(head(urlEqualTo(String.format("/v1/elements?ids=%s", uuid))).withPort(port).withHeader("userId", equalTo("user2"))
.willReturn(aResponse().withStatus(HttpStatus.FORBIDDEN.value())));
.willReturn(aResponse()));

stubFor(delete(urlEqualTo(String.format("/v1/explore/elements/%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));
Expand All @@ -478,28 +482,28 @@ public void testDeleteElements() {
stubFor(delete(urlEqualTo(String.format("/v1/filters/%s", uuid))).withHeader("userId", equalTo("user1"))
.willReturn(aResponse()));

// Delete elements with no or bad uuid is forbidden
// Delete elements with no or bad uuid
webClient
.delete().uri("explore/v1/explore/elements")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.delete().uri(String.format("explore/v1/explore/elements/%s", (UUID) null))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();
webClient
.delete().uri(String.format("explore/v1/explore/elements/%s", "badUuid"))
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.delete().uri(String.format("explore/v1/explore/elements/%s", uuid))
.header("Authorization", "Bearer " + tokenUser2)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.delete().uri(String.format("explore/v1/explore/elements/%s", uuid))
Expand Down Expand Up @@ -572,7 +576,7 @@ public void testDuplicateElements() {
.post().uri("study/v1/studies")
.header("Authorization", "Bearer " + tokenUser1)
.exchange()
.expectStatus().isForbidden();
.expectStatus().isNotFound();

webClient
.post().uri(String.format("explore/v1/explore/studies?%s=%s", ExploreServer.QUERY_PARAM_DUPLICATE_FROM_ID, uuid))
Expand Down
Loading