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

Gateway 2.9.2 #578

Open
wants to merge 2 commits into
base: gateway-mixedendpoint-fix
Choose a base branch
from
Open
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 @@ -3,6 +3,7 @@
public class GatewayConstants {

public static final String EMPTY_STRING = "";
public static final String AUTH_TOKEN = "auth-token";
public static final String JSON_TYPE = "json";
public static final String X_WWW_FORM_URLENCODED_TYPE = "application/x-www-form-urlencoded";
public static final String FORM_DATA = "multipart/form-data";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,69 @@
package com.example.gateway.filters.pre;

import com.example.gateway.filters.pre.helpers.AuthCheckFilterHelper;
import com.example.gateway.utils.CommonUtils;
import com.example.gateway.utils.UserUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.egov.common.contract.request.User;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;

import static com.example.gateway.constants.GatewayConstants.AUTH_BOOLEAN_FLAG_NAME;
import static com.example.gateway.constants.GatewayConstants.*;

@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;

private AuthCheckFilterHelper authCheckFilterHelper;
private CommonUtils commonUtils;
private UserUtils userUtils;
private ObjectMapper objectMapper;

public AuthFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter, AuthCheckFilterHelper authCheckFilterHelper) {
public AuthFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter, AuthCheckFilterHelper authCheckFilterHelper, CommonUtils commonUtils, UserUtils userUtils, ObjectMapper objectMapper) {
this.modifyRequestBodyFilter = modifyRequestBodyFilter;
this.authCheckFilterHelper = authCheckFilterHelper;
this.commonUtils = commonUtils;
this.userUtils = userUtils;
this.objectMapper = objectMapper;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

Boolean doAuth = exchange.getAttribute(AUTH_BOOLEAN_FLAG_NAME);
boolean isGetRequest = HttpMethod.GET.equals(exchange.getRequest().getMethod());
String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);

// TODO: handle auth for form-data content type (filestore)
if (Boolean.TRUE.equals(doAuth)) {

if (isGetRequest || commonUtils.isFormContentType(contentType)) {
String authToken = exchange.getRequest().getHeaders().get(AUTH_TOKEN).get(0);
User user = userUtils.getUser(authToken, exchange);
try {
MDC.put(USER_INFO_KEY, objectMapper.writeValueAsString(user));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
} else {
return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(Map.class, Map.class, authCheckFilterHelper)).filter(exchange, chain);
}

if(doAuth) {
return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(Map.class, Map.class, authCheckFilterHelper))
.filter(exchange, chain);
}
else {
return chain.filter(exchange);
}
return chain.filter(exchange);

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.gateway.config.ApplicationProperties;
import com.example.gateway.filters.pre.helpers.AuthPreCheckFilterHelper;
import com.example.gateway.utils.CommonUtils;
import com.example.gateway.utils.UserUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -16,17 +17,25 @@
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.example.gateway.constants.GatewayConstants.*;
import static com.example.gateway.constants.GatewayConstants.OPEN_ENDPOINT_MESSAGE;
import static com.example.gateway.filters.pre.helpers.AuthPreCheckFilterHelper.ROUTING_TO_ANONYMOUS_ENDPOINT_MESSAGE;
import static com.example.gateway.filters.pre.helpers.AuthPreCheckFilterHelper.*;

@Slf4j
@Component
Expand All @@ -39,96 +48,113 @@ public class AuthPreCheckFilter implements GlobalFilter, Ordered {
private ApplicationProperties applicationProperties;

private UserUtils userUtils;

private CommonUtils commonUtils;
private MultiStateInstanceUtil centralInstanceUtil;

public AuthPreCheckFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter, AuthPreCheckFilterHelper authPreCheckFilterHelper, ApplicationProperties applicationProperties, UserUtils userUtils, MultiStateInstanceUtil centralInstanceUtil) {
public AuthPreCheckFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter, AuthPreCheckFilterHelper authPreCheckFilterHelper, ApplicationProperties applicationProperties, UserUtils userUtils, CommonUtils commonUtils, MultiStateInstanceUtil centralInstanceUtil) {
this.modifyRequestBodyFilter = modifyRequestBodyFilter;
this.authPreCheckFilterHelper = authPreCheckFilterHelper;
this.applicationProperties = applicationProperties;
this.userUtils = userUtils;
this.commonUtils = commonUtils;
this.centralInstanceUtil = centralInstanceUtil;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

String authToken;
String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
String endPointPath = exchange.getRequest().getPath().value();
boolean isGetRequest = HttpMethod.GET.equals(exchange.getRequest().getMethod());

if (applicationProperties.getOpenEndpointsWhitelist().contains(endPointPath)) {
exchange.getAttributes().put(AUTH_BOOLEAN_FLAG_NAME, Boolean.FALSE);
log.info(OPEN_ENDPOINT_MESSAGE, endPointPath);
return chain.filter(exchange);

} else if (isGetRequest || commonUtils.isFormContentType(contentType)) {
return handleAuthPreCheck(exchange, chain);

} else {
return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(Map.class, Map.class, authPreCheckFilterHelper)).filter(exchange, chain);
}
}

private void setAnonymousUser(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String CorrelationId = exchange.getAttributes().get(CORRELATION_ID_KEY).toString();
String tenantId = getStateLevelTenantForHost(request);
User systemUser = userUtils.fetchSystemUser(tenantId, CorrelationId);
modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(Map.class, Map.class, new RewriteFunction<Map, Map>() {
@Override
public Publisher<Map> apply(ServerWebExchange serverWebExchange, Map body) {
try {
ObjectMapper objectMapper = new ObjectMapper();
RequestInfo requestInfo = objectMapper.convertValue(body.get(REQUEST_INFO_FIELD_NAME_PASCAL_CASE), RequestInfo.class);
requestInfo.setUserInfo(systemUser);
body.put(REQUEST_INFO_FIELD_NAME_PASCAL_CASE, requestInfo);
return Mono.just(body);
} catch (Exception ex) {
log.error("An error occured while transforming the request body to set Anonymous User {}", ex);

// Throw a custom exception
throw new CustomException("AUTHENTICATION_ERROR", ex.getMessage());
}
}
}));
// will handle for selected content types
public Mono<Void> handleAuthPreCheck(ServerWebExchange exchange, GatewayFilterChain chain) {

}
String authToken = null;
String endPointPath = exchange.getRequest().getPath().value();

/**
* method to fetch state level tenant-id based on whether the server is a
* multi-state instance or single-state instance
*
* @param request
* @return
*/
private String getStateLevelTenantForHost(ServerHttpRequest request) {
String tenantId = "";
if (centralInstanceUtil.getIsEnvironmentCentralInstance()) {
String requestURL = getRequestURL(request);
String host = requestURL.replace(request.getURI().getPath(), "").replace("https://", "").replace("http://", "");
tenantId = userUtils.getStateLevelTenantMap().get(host);
} else {
tenantId = userUtils.getStateLevelTenant();
if (!ObjectUtils.isEmpty(exchange.getRequest().getHeaders().get(AUTH_TOKEN))) {
authToken = exchange.getRequest().getHeaders().get(AUTH_TOKEN).get(0);
}
return tenantId;
}

public String getRequestURL(ServerHttpRequest request) {
if (ObjectUtils.isEmpty(authToken)) {
if (applicationProperties.getMixedModeEndpointsWhitelist().contains(endPointPath)) {
log.info(ROUTING_TO_ANONYMOUS_ENDPOINT_MESSAGE, endPointPath);
exchange.getAttributes().put(AUTH_BOOLEAN_FLAG_NAME, Boolean.FALSE);
setAnonymousUser(exchange);
} else {

// TODO:Handle in RBAC
log.info(ROUTING_TO_PROTECTED_ENDPOINT_RESTRICTED_MESSAGE, endPointPath);
CustomException customException = new CustomException(UNAUTHORIZED_USER_MESSAGE, UNAUTHORIZED_USER_MESSAGE);
customException.setCode(HttpStatus.UNAUTHORIZED.toString());
throw customException;
}
} else {
log.info(PROCEED_ROUTING_MESSAGE, endPointPath);
exchange.getAttributes().put(AUTH_BOOLEAN_FLAG_NAME, Boolean.TRUE);

// Manually construct the full request URL
String scheme = request.getURI().getScheme(); // e.g., "http" or "https"
String host = request.getURI().getHost(); // e.g., "example.com"
int port = request.getURI().getPort(); // e.g., 80 or 443 (can be -1 if default port is used)
String path = request.getURI().getPath(); // e.g., "/api/users"
}

// Construct the full URL
StringBuilder requestURL = new StringBuilder(scheme).append("://").append(host);
return chain.filter(exchange);
}

// Add the port if it's not the default (80 for HTTP, 443 for HTTPS)
if (port != -1) {
requestURL.append(":").append(port);
}
public void setAnonymousUser(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String CorrelationId = exchange.getAttributes().get(CORRELATION_ID_KEY).toString();
String tenantId = commonUtils.getStateLevelTenantForHost(request);
User systemUser = userUtils.fetchSystemUser(tenantId, CorrelationId);
try {
ObjectMapper objectMapper = new ObjectMapper();

// Append the request path
requestURL.append(path);
// Check for PascalCase first
String requestInfoBase64 = null;
if (exchange.getRequest().getQueryParams().containsKey(REQUEST_INFO_FIELD_NAME_PASCAL_CASE)) {
requestInfoBase64 = exchange.getRequest().getQueryParams().getFirst(REQUEST_INFO_FIELD_NAME_PASCAL_CASE);
}
// Check for camelCase if PascalCase is not found
else if (exchange.getRequest().getQueryParams().containsKey(REQUEST_INFO_FIELD_NAME_CAMEL_CASE)) {
requestInfoBase64 = exchange.getRequest().getQueryParams().getFirst(REQUEST_INFO_FIELD_NAME_CAMEL_CASE);
}

return requestURL.toString();
// TODO: Handle requestInfo not send
// TODO: Modify the request to send the updated
if (requestInfoBase64 != null) {
byte[] decodedBytes = Base64.getDecoder().decode(requestInfoBase64);
String requestInfoJson = new String(decodedBytes, StandardCharsets.UTF_8);
RequestInfo requestInfo = objectMapper.readValue(requestInfoJson, RequestInfo.class);
requestInfo.setUserInfo(systemUser);
String updatedRequestInfoJson = objectMapper.writeValueAsString(requestInfo);
String updatedRequestInfoBase64 = Base64.getEncoder().encodeToString(updatedRequestInfoJson.getBytes(StandardCharsets.UTF_8));
/*
MultiValueMap<String, String> originalQueryParams = exchange.getRequest().getQueryParams();
MultiValueMap<String, String> modifiableQueryParams = new LinkedMultiValueMap<>(originalQueryParams);

if (exchange.getRequest().getPath().toString().contains("filestore")) {
exchange.getRequest().getQueryParams().put(REQUEST_INFO_FIELD_NAME_CAMEL_CASE, Collections.singletonList(updatedRequestInfoBase64));
} else {
exchange.getRequest().getQueryParams().put(REQUEST_INFO_FIELD_NAME_PASCAL_CASE, Collections.singletonList(updatedRequestInfoBase64));
}
*/
}
} catch (Exception ex) {
log.error("Failed transforming the request body to set Anonymous User in mixed mode endpoints {}", ex);
// Throw a custom exception
throw new CustomException("AUTHENTICATION_ERROR", ex.getMessage());
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
import com.example.gateway.config.ApplicationProperties;
import com.example.gateway.filters.pre.helpers.CorrIdFormDataFilterHelper;
import com.example.gateway.filters.pre.helpers.CorrelationIdFilterHelper;
import com.example.gateway.utils.CommonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.egov.common.utils.MultiStateInstanceUtil;
import org.egov.tracer.model.CustomException;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.*;

import static com.example.gateway.constants.GatewayConstants.*;

Expand All @@ -23,41 +29,39 @@
public class CorrelationIdFilter implements GlobalFilter, Ordered {

private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;

private CorrelationIdFilterHelper correlationIdFilterHelper;

private CorrIdFormDataFilterHelper correlationIdFormDataFilterHelper;

private ApplicationProperties applicationProperties;

public CorrelationIdFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilter, CorrelationIdFilterHelper correlationIdFilterHelper,
CorrIdFormDataFilterHelper correlationIdFormDataFilterHelper, ApplicationProperties applicationProperties) {
private final CommonUtils commonUtils;

public CorrelationIdFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilter, CorrelationIdFilterHelper correlationIdFilterHelper, ApplicationProperties applicationProperties, CommonUtils commonUtils) {

this.modifyRequestBodyFilter = modifyRequestBodyGatewayFilter;
this.correlationIdFilterHelper = correlationIdFilterHelper;
this.correlationIdFormDataFilterHelper = correlationIdFormDataFilterHelper;

this.applicationProperties = applicationProperties;
this.commonUtils = commonUtils;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
String endPointPath = exchange.getRequest().getPath().value();
String requestURI = exchange.getRequest().getPath().value();
String correlationId = UUID.randomUUID().toString();

if(endPointPath.contains("/filestore")){
boolean isOpenRequest = applicationProperties.getOpenEndpointsWhitelist().contains(requestURI);
boolean isMixModeRequest = applicationProperties.getMixedModeEndpointsWhitelist().contains(requestURI);
boolean isGetRequest = HttpMethod.GET.equals(exchange.getRequest().getMethod());

if (isGetRequest || commonUtils.isFormContentType(contentType)) {
commonUtils.handleCentralInstanceLogic(exchange, requestURI, isOpenRequest, isMixModeRequest, null);
MDC.put(CORRELATION_ID_KEY, correlationId);
exchange.getAttributes().put(CORRELATION_ID_KEY, correlationId);
log.debug(RECEIVED_REQUEST_MESSAGE, requestURI);
return chain.filter(exchange);
}
else if (contentType != null && (contentType.contains("multipart/form-data") || contentType.contains("application/x-www-form-urlencoded"))) {
return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(MultiValueMap.class, MultiValueMap.class, correlationIdFormDataFilterHelper))
.filter(exchange, chain);
}
else {
return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(Map.class, Map.class, correlationIdFilterHelper))
.filter(exchange, chain);

} else {
return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(Map.class, Map.class, correlationIdFilterHelper)).filter(exchange, chain);
}
}

Expand Down
Loading