diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..920d132bd7568f9a1f6d7d13aef096fe202099ae --- /dev/null +++ b/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.tarento</groupId> + <artifactId>zuul</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>jar</packaging> + + <name>zuul</name> + <description>Api gateway for musti</description> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>1.5.2.RELEASE</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <java.version>1.8</java.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-tomcat</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-zuul</artifactId> + <version>1.2.6.RELEASE</version> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-io</artifactId> + <version>1.3.2</version> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-hateoas</artifactId> + </dependency> + + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.8.0</version> + </dependency> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>2.0-m02</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <mainClass>org.tarento.retail.ZuulGatewayApplication</mainClass> + </configuration> + </plugin> + </plugins> + </build> + + +</project> diff --git a/src/main/java/org/tarento/retail/ZuulGatewayApplication.java b/src/main/java/org/tarento/retail/ZuulGatewayApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..1ce2d0f6641827fdd3ad3bee86ebbdbe0550a628 --- /dev/null +++ b/src/main/java/org/tarento/retail/ZuulGatewayApplication.java @@ -0,0 +1,82 @@ +package org.tarento.retail; + +import java.util.Arrays; +import java.util.HashSet; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.tarento.retail.filters.pre.AuthFilter; +import org.tarento.retail.filters.pre.AuthPreCheckFilter; +import org.tarento.retail.filters.pre.RbacFilter; +import org.tarento.retail.filters.pre.RbacPreCheckFilter; + +@EnableZuulProxy +@SpringBootApplication +public class ZuulGatewayApplication{ + + + public static void main(String[] args) { + SpringApplication.run(ZuulGatewayApplication.class, args); + } + + @Value("${retail.user-info-header}") + private String userInfoHeader; + + @Value("#{'${retail.open-endpoints-whitelist}'.split(',')}") + private String[] openEndpointsWhitelist; + + @Value("#{'${retail.mixed-mode-endpoints-whitelist}'.split(',')}") + private String[] mixedModeEndpointsWhitelist; + + @Value("${retail.auth-service-host}") + private String authServiceHost; + + @Value("${retail.auth-service-uri}") + private String authServiceUri; + + @Bean + public AuthPreCheckFilter authCheckFilter() { + return new AuthPreCheckFilter(new HashSet<>(Arrays.asList(openEndpointsWhitelist)), + new HashSet<>(Arrays.asList(mixedModeEndpointsWhitelist))); + } + + @Bean + public AuthFilter authFilter() { + RestTemplate restTemplate = new RestTemplate(); + final ProxyRequestHelper proxyRequestHelper = new ProxyRequestHelper(); + return new AuthFilter(proxyRequestHelper, restTemplate, authServiceHost, authServiceUri); + } + + @Bean + public RbacFilter rbacFilter() { + return new RbacFilter(); + } + + @Bean + public RbacPreCheckFilter rbacCheckFilter() { + return new RbacPreCheckFilter(new HashSet<>(Arrays.asList(openEndpointsWhitelist)), + new HashSet<>(Arrays.asList(mixedModeEndpointsWhitelist)) + ); + } + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurerAdapter() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS").allowedOrigins("*") + .allowedHeaders("*"); + } + }; + } + + +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/constants/RequestContextConstants.java b/src/main/java/org/tarento/retail/constants/RequestContextConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..c47d10fa55a9fa92c343f372bbfa45ed1549fe21 --- /dev/null +++ b/src/main/java/org/tarento/retail/constants/RequestContextConstants.java @@ -0,0 +1,22 @@ + +package org.tarento.retail.constants; + +public class RequestContextConstants { + public static final String AUTH_BOOLEAN_FLAG_NAME = "shouldDoAuth"; + public static final String AUTH_TOKEN_KEY = "authToken"; + public static final String ERROR_MESSAGE_KEY = "error.message"; + public static final String ERROR_CODE_KEY = "error.status_code"; + public static final String GET = "GET"; + public static final String POST = "POST"; + public static final String FILESTORE_REGEX = "^/filestore/.*"; + public static final String REQUEST_INFO_FIELD_NAME_PASCAL_CASE = "RequestInfo"; + public static final String REQUEST_INFO_FIELD_NAME_CAMEL_CASE = "requestInfo"; + public static final String USER_INFO_FIELD_NAME = "userInfo"; + public static final String USER_INFO_KEY = "USER_INFO"; + public static final String CORRELATION_ID_FIELD_NAME = "correlationId"; + public static final String CORRELATION_ID_HEADER_NAME = "x-correlation-id"; + public static final String CORRELATION_ID_KEY = "CORRELATION_ID"; + public static final String RBAC_BOOLEAN_FLAG_NAME = "shouldDoRbac"; + public static final String SKIP_RBAC = "RBAC check skipped"; + public static final String RBAC_AVAILABLE ="RbacAvailable"; +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/contract/Action.java b/src/main/java/org/tarento/retail/contract/Action.java new file mode 100644 index 0000000000000000000000000000000000000000..2e035c04331525880474d0b1d0ff6a5162b8a9a4 --- /dev/null +++ b/src/main/java/org/tarento/retail/contract/Action.java @@ -0,0 +1,34 @@ +package org.tarento.retail.contract; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Action { + private static final String OPENING_BRACES = "{"; + private static final String CLOSING_BRACES = "}"; + private static final String PARAMETER_PLACEHOLDER_REGEX = "\\{\\w+\\}"; + private static final String ANY_WORD_REGEX = "\\\\w+"; + + @JsonProperty("url") + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @JsonIgnore + public boolean hasDynamicFields() { + return url.contains(OPENING_BRACES) & url.contains(CLOSING_BRACES); + } + + @JsonIgnore + public String getRegexUrl() { + return url.replaceAll(PARAMETER_PLACEHOLDER_REGEX, ANY_WORD_REGEX); + } +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/contract/AuthToken.java b/src/main/java/org/tarento/retail/contract/AuthToken.java new file mode 100644 index 0000000000000000000000000000000000000000..21f84f91e457257bde42b46a214914cf412cd3d2 --- /dev/null +++ b/src/main/java/org/tarento/retail/contract/AuthToken.java @@ -0,0 +1,25 @@ +package org.tarento.retail.contract; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AuthToken { + + @JsonProperty("authToken") + private String authToken; + + public AuthToken(){ + + } + + public AuthToken(String token){ + this.authToken = token; + } + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } +} diff --git a/src/main/java/org/tarento/retail/contract/Role.java b/src/main/java/org/tarento/retail/contract/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..eaa31174a2ba50bd4c882bfacda252ffb1af62d0 --- /dev/null +++ b/src/main/java/org/tarento/retail/contract/Role.java @@ -0,0 +1,28 @@ +package org.tarento.retail.contract; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Role { + @JsonProperty("id") + private Long id; + + @JsonProperty("name") + private String name; + + @JsonProperty("code") + private String code; + + public void setId(Long id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/src/main/java/org/tarento/retail/contract/User.java b/src/main/java/org/tarento/retail/contract/User.java new file mode 100644 index 0000000000000000000000000000000000000000..4a90bada91b3a75528c47e255c3b4f9e07f4e403 --- /dev/null +++ b/src/main/java/org/tarento/retail/contract/User.java @@ -0,0 +1,111 @@ +package org.tarento.retail.contract; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class User { + @JsonProperty("id") + private Integer id; + + @JsonProperty("userName") + private String userName; + + @JsonProperty("name") + private String name; + + @JsonProperty("type") + private String type; + + @JsonProperty("mobileNumber") + private String mobileNumber; + + @JsonProperty("authToken") + private String authToken; + + @JsonProperty("emailId") + private String emailId; + + @JsonProperty("orgId") + private String orgId; + + @JsonProperty("roles") + private List<Role> roles; + + @JsonIgnore + @JsonProperty("actions") + private List<Action> actions; + + @JsonIgnore + @JsonProperty("timeZone") + private String timeZone; + + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public User() { + } + + @JsonIgnore + public List<Action> getActions(){ + return this.actions; + + } + + + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public void setId(Integer id) { + this.id = id; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public void setName(String name) { + this.name = name; + } + + public void setType(String type) { + this.type = type; + } + + public void setMobileNumber(String mobileNumber) { + this.mobileNumber = mobileNumber; + } + + public void setEmailId(String emailId) { + this.emailId = emailId; + } + + public void setRoles(List<Role> roles) { + this.roles = roles; + } + + public void setActions(List<Action> actions) { + this.actions = actions; + } + + public void setOrgId(String orgId) { + this.orgId = orgId; + } + +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/exceptions/zuulExceptions/RbacException.java b/src/main/java/org/tarento/retail/exceptions/zuulExceptions/RbacException.java new file mode 100644 index 0000000000000000000000000000000000000000..ecd18df2aead26d5642e38389988532f91ff86f1 --- /dev/null +++ b/src/main/java/org/tarento/retail/exceptions/zuulExceptions/RbacException.java @@ -0,0 +1,193 @@ +package org.tarento.retail.exceptions.zuulExceptions; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * + * @author Darshan Nagesh + * + */ + +public class RbacException extends Exception{ + + private static final long serialVersionUID = 1L; + /** + * The primary message for the exception + */ + private String message; + /** + * designated error code of the error + */ + private String errorCode; + /** + * Server timestamp when error occured + */ + private long timeStamp; + /** + * Additional parameters + */ + private Map<String,Object> params; + /** + * root Exception object + */ + private Exception ex; + /** + * root throwable object + */ + private Throwable t; + /** + * variable determining the error been logged to file or not + */ + private boolean logged; + + private String originalStackTrace; + + public RbacException(Exception e) { + super(e); + initializeException(e.getMessage()); + processOriginalStackTrace(e.getStackTrace()); + } + + public RbacException(Throwable t) { + super(t); + initializeException(t.getMessage()); + processOriginalStackTrace(t.getStackTrace()); + } + + public RbacException(String msg) { + super(msg); + initializeException(msg); + } + + public RbacException(String msg, Throwable t) { + super(msg,t); + initializeException(msg); + processOriginalStackTrace(t.getStackTrace()); + } + + public RbacException withParam(String name, Object value){ + if(params == null){ + params = new HashMap<String,Object>(); + } + params.put(name, value); + return this; + } + + public RbacException withErrorCode(String errorCode){ + this.errorCode=errorCode; + return this; + } + + private void initializeException(String msg){ + this.timeStamp = System.currentTimeMillis(); + this.message = msg; + } + + public String prepareFullErrorDescription(){ + StringBuilder stackBuilder = new StringBuilder(); + stackBuilder.append("Exception Message : ").append(this.message).append(" \n"); + stackBuilder.append("Exception Time : ").append(new Date(this.timeStamp)).append(" \n"); + stackBuilder.append("Error code : ").append(this.errorCode).append(" \n "); + if(this.params != null && !this.params.isEmpty()){ + stackBuilder.append(" Parameters : "); + for(Entry<String,Object> entry : params.entrySet()){ + stackBuilder.append("\n\t ").append(entry.getKey()).append(" : ").append(entry.getValue()); + } + } + if(originalStackTrace != null){ + stackBuilder.append("\n stacktrace : ").append(originalStackTrace); + return stackBuilder.toString(); + } + StackTraceElement[] stack; + if(this.ex != null){ + stack = ex.getStackTrace(); + }else if(this.t != null){ + stack = t.getStackTrace(); + }else{ + stack = super.getStackTrace(); + } + if(stack == null){ + return stackBuilder.toString(); + } + stackBuilder.append("\n stacktrace : "); + for(StackTraceElement s : stack){ + stackBuilder.append(" \n ").append(s); + } + return stackBuilder.toString(); + } + + private void processOriginalStackTrace(StackTraceElement[] stack){ + if(stack == null || stack.length == 0){ + return; + } + StringBuilder stackBuilder = new StringBuilder(); + for(StackTraceElement trace : stack){ + stackBuilder.append(trace).append("\n "); + } + originalStackTrace = stackBuilder.toString(); + } + + public boolean isLogged() { + return logged; + } + + public void setLogged(boolean logged) { + this.logged = logged; + } + + public RbacException logToFile(String prependingMessage){ + if(!logged){ + if(prependingMessage != null){ + //logger.error(prependingMessage); + } + //logger.error(prepareFullErrorDescription()); + logged = true; + } + return this; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public long getTimeStamp() { + return timeStamp; + } + + + public Map<String, Object> getParams() { + return params; + } + + public void addParamsToMessage(){ + if(params == null){ + return; + } + StringBuilder message = new StringBuilder("{ "); + int i = params.size(); + for(Entry<String, Object> entry :params.entrySet()){ + message.append(entry.getKey()).append(" : ").append(entry.getValue()); + i--; + if(i>0){ + message.append(", "); + } + } + message.append(" } , ").append(getMessage()); + this.message = message.toString(); + } +} diff --git a/src/main/java/org/tarento/retail/filters/post/CustomResponseFilter.java b/src/main/java/org/tarento/retail/filters/post/CustomResponseFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..c91cffea207843ec4cafb6a191d02bfef640d97f --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/post/CustomResponseFilter.java @@ -0,0 +1,87 @@ +package org.tarento.retail.filters.post; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.client.HttpServerErrorException; +import org.tarento.retail.exceptions.zuulExceptions.RbacException; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; + +/** + * + * @author Darshan Nagesh + * + */ + +@Component +public class CustomResponseFilter extends ZuulFilter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public String filterType() { + return "post"; + } + + @Override + public int filterOrder() { + return 0; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + return RequestContext.getCurrentContext().containsKey("error.status_code"); + } + + @Override + public Object run() { + try { + RequestContext ctx = RequestContext.getCurrentContext(); + Object e = ctx.get("error.exception"); + + if (e != null && e instanceof ZuulException) { + ZuulException zuulException = (ZuulException)e; + logger.error("Zuul failure detected: " + zuulException.getMessage(), zuulException); + + ctx.remove("error.status_code"); + + ctx.setResponseBody("Overriding Zuul Exception Body"); + ctx.getResponse().setContentType("application/json"); + ctx.setResponseStatusCode(500); + } + + if (e != null && e instanceof HttpServerErrorException) { + HttpServerErrorException httpException = (HttpServerErrorException)e; + logger.error("Zuul failure detected: " + httpException.getMessage(), httpException); + + /*ctx.remove("error.status_code"); + + ctx.setResponseBody("Overriding Http Exception Body"); + ctx.getResponse().setContentType("application/json"); + ctx.setResponseStatusCode(500); */ + } + + if (e != null && e instanceof RbacException) { + RbacException rbacException = (RbacException)e; + logger.error("Zuul failure detected: " + rbacException.getMessage(), rbacException); + + ctx.remove("error.status_code"); + + ctx.setResponseBody("Overriding Http Exception Body"); + ctx.getResponse().setContentType("application/json"); + ctx.setResponseStatusCode(400); + } + } + catch (Exception ex) { + ReflectionUtils.rethrowRuntimeException(ex); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/filters/post/ResponseEnhancementFilter.java b/src/main/java/org/tarento/retail/filters/post/ResponseEnhancementFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..21df4c5430e3052ef1c4318bd31bac3c02a62595 --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/post/ResponseEnhancementFilter.java @@ -0,0 +1,161 @@ +package org.tarento.retail.filters.post; + +import static org.tarento.retail.constants.RequestContextConstants.CORRELATION_ID_KEY; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; + +/** + * Sets the correlation id to the response header. + */ +@Component +public class ResponseEnhancementFilter extends ZuulFilter { + + private static final String CORRELATION_HEADER_NAME = "x-correlation-id"; + private static final String RECEIVED_RESPONSE_MESSAGE = "Received response code: {} from upstream URI {}"; + private Logger logger = LoggerFactory.getLogger(this.getClass()); + protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran"; + + @Value("${error.path:/error}") + private String errorPath; + + @Override + public String filterType() { + return "post"; + } + + @Override + public int filterOrder() { + return 1; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + RequestContext ctx = RequestContext.getCurrentContext(); + // only forward to errorPath if it hasn't been forwarded to already + return !ctx.getBoolean("RbacAvailable") && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false); + } + + @Override + public Object run() { + try { + RequestContext ctx = RequestContext.getCurrentContext(); + HttpServletRequest request = ctx.getRequest(); + Integer exceptionStatusCode = null; + exceptionStatusCode = 400; + request.setAttribute("javax.servlet.error.status_code", 400); + request.setAttribute("javax.servlet.error.message", "RbacFilter"); + ctx.addZuulResponseHeader(CORRELATION_HEADER_NAME, getCorrelationId()); + ExceptionHolder exception = findZuulException(ctx.getThrowable()); + request.setAttribute("javax.servlet.error.status_code", exception.getStatusCode()); + exceptionStatusCode = exception.getStatusCode(); + logger.warn("Error during filtering", exception.getThrowable()); + request.setAttribute("javax.servlet.error.exception", exception.getThrowable()); + if (StringUtils.hasText(exception.getErrorCause())) { + request.setAttribute("javax.servlet.error.message", exception.getErrorCause()); + } + + RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPath); + if (dispatcher != null) { + ctx.set(SEND_ERROR_FILTER_RAN, true); + if (!ctx.getResponse().isCommitted()) { + ctx.setResponseStatusCode(exceptionStatusCode); + dispatcher.forward(request, ctx.getResponse()); + } + } + } catch (Exception ex) { + ReflectionUtils.rethrowRuntimeException(ex); + } + return null; + } + + private String getCorrelationId() { + RequestContext ctx = RequestContext.getCurrentContext(); + logger.info(RECEIVED_RESPONSE_MESSAGE, + ctx.getResponse().getStatus(), ctx.getRequest().getRequestURI()); + return (String) ctx.get(CORRELATION_ID_KEY); + } + + protected ExceptionHolder findZuulException(Throwable throwable) { + + if (throwable.getCause() instanceof ZuulException) { + // wrapped zuul exception + return new ZuulExceptionHolder((ZuulException) throwable.getCause()); + } + + if (throwable instanceof ZuulException) { + // exception thrown by zuul lifecycle + return new ZuulExceptionHolder((ZuulException) throwable); + } + + // fallback + return new DefaultExceptionHolder(throwable); + } + + protected interface ExceptionHolder { + Throwable getThrowable(); + + default int getStatusCode() { + return HttpStatus.INTERNAL_SERVER_ERROR.value(); + } + + default String getErrorCause() { + return null; + } + } + + protected static class DefaultExceptionHolder implements ExceptionHolder { + private final Throwable throwable; + + public DefaultExceptionHolder(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public Throwable getThrowable() { + return this.throwable; + } + } + + protected static class ZuulExceptionHolder implements ExceptionHolder { + private final ZuulException exception; + + public ZuulExceptionHolder(ZuulException exception) { + this.exception = exception; + } + + @Override + public Throwable getThrowable() { + return this.exception; + } + + @Override + public int getStatusCode() { + return this.exception.nStatusCode; + } + + @Override + public String getErrorCause() { + return this.exception.errorCause; + } + } + + public void setErrorPath(String errorPath) { + this.errorPath = errorPath; + } +} diff --git a/src/main/java/org/tarento/retail/filters/pre/AuthFilter.java b/src/main/java/org/tarento/retail/filters/pre/AuthFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..4cea6843c8f4d6a839a7e555e3017f91e514879d --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/pre/AuthFilter.java @@ -0,0 +1,142 @@ +package org.tarento.retail.filters.pre; + +import static org.tarento.retail.constants.RequestContextConstants.AUTH_BOOLEAN_FLAG_NAME; +import static org.tarento.retail.constants.RequestContextConstants.AUTH_TOKEN_KEY; +import static org.tarento.retail.constants.RequestContextConstants.CORRELATION_ID_HEADER_NAME; +import static org.tarento.retail.constants.RequestContextConstants.CORRELATION_ID_KEY; +import static org.tarento.retail.constants.RequestContextConstants.ERROR_CODE_KEY; +import static org.tarento.retail.constants.RequestContextConstants.ERROR_MESSAGE_KEY; +import static org.tarento.retail.constants.RequestContextConstants.RBAC_BOOLEAN_FLAG_NAME; +import static org.tarento.retail.constants.RequestContextConstants.USER_INFO_KEY; + +import java.io.IOException; +import java.net.URI; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.tarento.retail.contract.AuthToken; +import org.tarento.retail.contract.User; +import org.tarento.retail.util.ResponseMessages; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +/** + * 4th pre filter to get executed. + * If the auth flag is enabled then the user is retrieved for the given auth token. + */ +public class AuthFilter extends ZuulFilter { + + private static final String INPUT_STREAM_CONVERSION_FAILED_MESSAGE = "Failed to convert to input stream"; + private static final String RETRIEVING_USER_FAILED_MESSAGE = "Retrieving user failed"; + private static final String TOKEN_EXPIRED = "Token Invalid or Expired"; + private final ProxyRequestHelper helper; + private final String authServiceHost; + private final String authUri; + private final RestTemplate restTemplate; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + + public AuthFilter(ProxyRequestHelper helper, RestTemplate restTemplate, String authServiceHost, String authUri) { + this.helper = helper; + this.restTemplate = restTemplate; + this.authServiceHost = authServiceHost; + this.authUri = authUri; + } + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 3; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + return RequestContext.getCurrentContext().getBoolean(AUTH_BOOLEAN_FLAG_NAME); + } + + @Override + public Object run() { + RequestContext ctx = RequestContext.getCurrentContext(); + String authToken = (String) ctx.get(AUTH_TOKEN_KEY); + try { + User user = getUser(authToken, ctx); + logger.info(user.toString()); + ctx.set(USER_INFO_KEY, user); + } catch (HttpClientErrorException ex) { + logger.error(RETRIEVING_USER_FAILED_MESSAGE, ex); + abortWithStatus(306, ResponseMessages.StandardKeysToCompare.INVALID_AUTH_FILTER_SOURCE, ex); + throw ex; + }catch (Exception ex) { + logger.error(RETRIEVING_USER_FAILED_MESSAGE, ex); + abortWithStatus(306, ResponseMessages.StandardKeysToCompare.INVALID_AUTH_FILTER_SOURCE, ex); + throw ex; + } + return null; + } + + private User getUser(String authToken, RequestContext ctx) { + String authURL = String.format("%s%s%s", authServiceHost, authUri, ""); + final HttpHeaders headers = new HttpHeaders(); + headers.add(CORRELATION_ID_HEADER_NAME, (String) ctx.get(CORRELATION_ID_KEY)); + headers.setContentType(MediaType.APPLICATION_JSON); + authToken = authToken.replace("Bearer ", "").trim(); + headers.add("Authorization",authToken); + + AuthToken au = new AuthToken(); + au.setAuthToken(authToken); + final HttpEntity<AuthToken> httpEntity = new HttpEntity<>(au, headers); + URI uri = URI.create(authServiceHost); + logger.info("Auth Token Object Being passed : " + au.toString()); + logger.info("URI Being passed : " + uri); + logger.info("HTTP Entity Being passed : " + httpEntity); + return restTemplate.postForObject(uri, httpEntity, User.class); + + + } + + private void abortWithException(RequestContext ctx, HttpClientErrorException ex) { + ctx.setSendZuulResponse(false); + try { + helper.setResponse(ex.getStatusCode().value(), + IOUtils.toInputStream(ex.getResponseBodyAsString()), + ex.getResponseHeaders()); + } catch (IOException e) { + logger.error(INPUT_STREAM_CONVERSION_FAILED_MESSAGE, e); + throw new RuntimeException(e); + } + } + + private void abortWithStatus(HttpStatus status, String message, Exception ex) { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(ERROR_CODE_KEY, status.value()); + ctx.set(ERROR_MESSAGE_KEY, message); + ctx.set("error.exception", ex); + ctx.set(RBAC_BOOLEAN_FLAG_NAME, Boolean.FALSE); + ctx.setSendZuulResponse(false); + } + + private void abortWithStatus(int status, String message, Exception ex) { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(ERROR_CODE_KEY, status); + ctx.set(ERROR_MESSAGE_KEY, message); + ctx.set("error.exception", ex); + ctx.set(RBAC_BOOLEAN_FLAG_NAME, Boolean.FALSE); + ctx.setSendZuulResponse(false); + } +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/filters/pre/AuthPreCheckFilter.java b/src/main/java/org/tarento/retail/filters/pre/AuthPreCheckFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..96aaad615fdf245988a3161da409400f7a077769 --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/pre/AuthPreCheckFilter.java @@ -0,0 +1,208 @@ +package org.tarento.retail.filters.pre; + +import static org.tarento.retail.constants.RequestContextConstants.AUTH_BOOLEAN_FLAG_NAME; +import static org.tarento.retail.constants.RequestContextConstants.AUTH_TOKEN_KEY; +import static org.tarento.retail.constants.RequestContextConstants.ERROR_CODE_KEY; +import static org.tarento.retail.constants.RequestContextConstants.ERROR_MESSAGE_KEY; +import static org.tarento.retail.constants.RequestContextConstants.FILESTORE_REGEX; +import static org.tarento.retail.constants.RequestContextConstants.GET; +import static org.tarento.retail.constants.RequestContextConstants.USER_INFO_FIELD_NAME; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.tarento.retail.model.RequestBodyInspector; +import org.tarento.retail.wrapper.CustomRequestWrapper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +/** + * 2nd pre filter to get executed. + * Identifies if the URI is part of open or mixed endpoint list. + * If its not present in the open list then the auth token is retrieved from the request body. + * For a restricted endpoint if auth token is not present then an error response is returned. + */ +public class AuthPreCheckFilter extends ZuulFilter { + private static final String AUTH_TOKEN_RETRIEVE_FAILURE_MESSAGE = "Retrieving of auth token failed"; + private static final String OPEN_ENDPOINT_MESSAGE = "Routing to an open endpoint: {}"; + private static final String AUTH_TOKEN_HEADER_MESSAGE = "Fetching auth-token from header for URI: {}"; + private static final String AUTH_TOKEN_BODY_MESSAGE = "Fetching auth-token from request body for URI: {}"; + private static final String AUTH_TOKEN_HEADER_NAME = "auth-token"; + private static final String RETRIEVED_AUTH_TOKEN_MESSAGE = "Auth-token: {}"; + private static final String ROUTING_TO_ANONYMOUS_ENDPOINT_MESSAGE = "Routing to anonymous endpoint: {}"; + private static final String ROUTING_TO_PROTECTED_ENDPOINT_RESTRICTED_MESSAGE = + "Routing to protected endpoint {} restricted - No auth token"; + private static final String UNAUTHORIZED_USER_MESSAGE = "You are not authorized to access this resource"; + private static final String PROCEED_ROUTING_MESSAGE = "Routing to an endpoint: {} - auth provided"; + private static final String NO_REQUEST_INFO_FIELD_MESSAGE = "No request-info field in request body for: {}"; + private static final String AUTH_TOKEN_REQUEST_BODY_FIELD_NAME = "authToken"; + private static final String FAILED_TO_SERIALIZE_REQUEST_BODY_MESSAGE = "Failed to serialize requestBody"; + private static final String AUTHENTICATION_SCHEME = "Bearer"; + private HashSet<String> openEndpointsWhitelist; + private HashSet<String> mixedModeEndpointsWhitelist; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final ObjectMapper objectMapper; + + + public AuthPreCheckFilter(HashSet<String> openEndpointsWhitelist, + HashSet<String> mixedModeEndpointsWhitelist) { + this.openEndpointsWhitelist = openEndpointsWhitelist; + this.mixedModeEndpointsWhitelist = mixedModeEndpointsWhitelist; + objectMapper = new ObjectMapper(); + } + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 1; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + return true; + } + + @Override + public Object run() { + String authToken; + if (openEndpointsWhitelist.contains(getRequestURI())) { + setShouldDoAuth(false); + logger.info(OPEN_ENDPOINT_MESSAGE, getRequestURI()); + return null; + } + try { + authToken = getAuthTokenFromRequest(); + logger.info("Getting the Auth token value from request:" +authToken); + } catch (IOException e) { + logger.error(AUTH_TOKEN_RETRIEVE_FAILURE_MESSAGE, e); + abortWithStatus(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + return null; + } + RequestContext.getCurrentContext().set(AUTH_TOKEN_KEY, authToken); + logger.info(RETRIEVED_AUTH_TOKEN_MESSAGE, authToken); + if (authToken == null) { + if (mixedModeEndpointsWhitelist.contains(getRequestURI())) { + logger.info(ROUTING_TO_ANONYMOUS_ENDPOINT_MESSAGE, getRequestURI()); + setShouldDoAuth(false); + } else { + logger.info(ROUTING_TO_PROTECTED_ENDPOINT_RESTRICTED_MESSAGE, getRequestURI()); + abortWithStatus(HttpStatus.UNAUTHORIZED, UNAUTHORIZED_USER_MESSAGE); + return null; + } + } else { + logger.info(PROCEED_ROUTING_MESSAGE, getRequestURI()); + setShouldDoAuth(true); + } + return null; + } + + private String getAuthTokenFromRequest() throws IOException { + if (GET.equalsIgnoreCase(getRequestMethod()) || getRequestURI().matches(FILESTORE_REGEX)) { + logger.info(AUTH_TOKEN_HEADER_MESSAGE, getRequestURI()); + return getAuthTokenFromRequestBody(); + } else { + logger.info(AUTH_TOKEN_BODY_MESSAGE, getRequestURI()); + return getAuthTokenFromRequestBody(); + } + } + + private String getAuthTokenFromRequestBody() throws IOException { + + String requestInfotoken = ""; + String authorizationHeader = ""; + CustomRequestWrapper requestWrapper = new CustomRequestWrapper(getRequest()); + /* HashMap<String, Object> requestBody = getRequestBody(requestWrapper); + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + @SuppressWarnings("unchecked") + HashMap<String, Object> requestInfo = requestBodyInspector.getRequestInfo(); + if (requestInfo != null) { + logger.info(NO_REQUEST_INFO_FIELD_MESSAGE, getRequestURI()); + requestInfotoken = (String) requestInfo.get(AUTH_TOKEN_REQUEST_BODY_FIELD_NAME); + sanitizeAndSetRequest(requestBodyInspector, requestWrapper); + return requestInfotoken; + + } else { + */ RequestContext ctx = RequestContext.getCurrentContext(); + + authorizationHeader = ctx.getRequest().getHeader(javax.ws.rs.core.HttpHeaders.AUTHORIZATION); + logger.info("Authorization header: "+authorizationHeader); + return authorizationHeader; + /*}*/ + + + + + + + + + + } + + + private HashMap<String, Object> getRequestBody(CustomRequestWrapper requestWrapper) throws IOException { + return objectMapper.readValue(requestWrapper.getPayload(), + new TypeReference<HashMap<String, Object>>() { }); + } + + private void sanitizeAndSetRequest(RequestBodyInspector requestBodyInspector, CustomRequestWrapper requestWrapper) { + HashMap<String, Object> requestInfo = requestBodyInspector.getRequestInfo(); + requestInfo.remove(USER_INFO_FIELD_NAME); + requestBodyInspector.updateRequestInfo(requestInfo); + try { + requestWrapper.setPayload(objectMapper.writeValueAsString(requestBodyInspector.getRequestBody())); + } catch (JsonProcessingException e) { + logger.error(FAILED_TO_SERIALIZE_REQUEST_BODY_MESSAGE, e); + throw new RuntimeException(e); + } + RequestContext.getCurrentContext().setRequest(requestWrapper); + } + + private String getAuthTokenFromRequestHeader() { + RequestContext ctx = RequestContext.getCurrentContext(); + + return ctx.getRequest().getHeader(AUTH_TOKEN_HEADER_NAME); + } + + private void setShouldDoAuth(boolean enableAuth) { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(AUTH_BOOLEAN_FLAG_NAME, enableAuth); + } + + private String getRequestURI() { + return getRequest().getRequestURI(); + } + + private HttpServletRequest getRequest() { + RequestContext ctx = RequestContext.getCurrentContext(); + return ctx.getRequest(); + } + + private String getRequestMethod() { + return getRequest().getMethod(); + } + + private void abortWithStatus(HttpStatus status, String message) { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(ERROR_CODE_KEY, status.value()); + ctx.set(ERROR_MESSAGE_KEY, message); + ctx.setSendZuulResponse(false); + } +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/filters/pre/CorrelationIdFilter.java b/src/main/java/org/tarento/retail/filters/pre/CorrelationIdFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..7a148924c0acdba8069888dd6ab3de6ce656b472 --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/pre/CorrelationIdFilter.java @@ -0,0 +1,54 @@ +package org.tarento.retail.filters.pre; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; + +import static org.tarento.retail.constants.RequestContextConstants.CORRELATION_ID_KEY; + +import java.util.UUID; + +/** + * 1st pre filter to get executed. + * Sets the context and MDC with the newly generated correlation id. + */ +@Component +public class CorrelationIdFilter extends ZuulFilter { + + private static final String RECEIVED_REQUEST_MESSAGE = "Received request for: {}"; + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 0; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + return true; + } + + @Override + public Object run() { + RequestContext ctx = RequestContext.getCurrentContext(); + final String correlationId = UUID.randomUUID().toString(); + MDC.put(CORRELATION_ID_KEY, correlationId); + ctx.set(CORRELATION_ID_KEY, correlationId); + logger.info(RECEIVED_REQUEST_MESSAGE, ctx.getRequest().getRequestURI()); + logger.info(RECEIVED_REQUEST_MESSAGE, ctx.getRequest().getMethod()); + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/org/tarento/retail/filters/pre/RbacFilter.java b/src/main/java/org/tarento/retail/filters/pre/RbacFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..87b3d1ecd39172abdc6352118ff1e381c4276416 --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/pre/RbacFilter.java @@ -0,0 +1,80 @@ +package org.tarento.retail.filters.pre; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +import static org.tarento.retail.constants.RequestContextConstants.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.tarento.retail.contract.Action; +import org.tarento.retail.contract.User; +import org.tarento.retail.exceptions.zuulExceptions.RbacException; + +/** + * 5th pre filter to get executed. + * Filter gets executed if the RBAC flag is enabled. Returns an error if the URI is not present in the authorized action list. + */ +public class RbacFilter extends ZuulFilter{ + + private static final String FORBIDDEN_MESSAGE = "Not authorized to access this resource"; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public String filterType() {return "pre";} + + @Override + public int filterOrder() {return 4;} + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + RequestContext ctx = RequestContext.getCurrentContext(); + return ctx.getBoolean(RBAC_BOOLEAN_FLAG_NAME); + } + + @Override + public Object run() { + RequestContext ctx = RequestContext.getCurrentContext(); + try { + final boolean isIncomingURIInAuthorizedActionList = isIncomingURIInAuthorizedActionList(ctx); + if(isIncomingURIInAuthorizedActionList) { + ctx.set("RbacAvailable",Boolean.TRUE); + return null; + } + else { + ctx.set("RbacAvailable",Boolean.FALSE); + } + } catch (Exception e) { + logger.error("Failed : " +e) ; + abortWithStatus(ctx,HttpStatus.FORBIDDEN, FORBIDDEN_MESSAGE); + throw e; + } + return null; + } + + private boolean isIncomingURIInAuthorizedActionList(RequestContext ctx) { + String requestUri = ctx.getRequest().getRequestURI(); + User user = (User) ctx.get(USER_INFO_KEY); + return user.getActions().stream() + .anyMatch(action -> isActionMatchingIncomingURI(requestUri, action)); + } + + private boolean isActionMatchingIncomingURI(String requestUri, Action action) { + if(action.hasDynamicFields()) { + return requestUri.matches(action.getRegexUrl()); + } + return requestUri.equals(action.getUrl()); + } + + + private void abortWithStatus(RequestContext ctx, HttpStatus status, String message) { + ctx.set(ERROR_CODE_KEY, status.value()); + ctx.set(ERROR_MESSAGE_KEY, message); + ctx.set("error.exception", new RbacException("Role does not have access to this URL")); + ctx.setSendZuulResponse(false); + } +} diff --git a/src/main/java/org/tarento/retail/filters/pre/RbacPreCheckFilter.java b/src/main/java/org/tarento/retail/filters/pre/RbacPreCheckFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..a61f60786424d712ac3f51b02a5cb90fcda33e98 --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/pre/RbacPreCheckFilter.java @@ -0,0 +1,76 @@ +package org.tarento.retail.filters.pre; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; + +import static org.tarento.retail.constants.RequestContextConstants.RBAC_AVAILABLE; +import static org.tarento.retail.constants.RequestContextConstants.RBAC_BOOLEAN_FLAG_NAME; +import static org.tarento.retail.constants.RequestContextConstants.SKIP_RBAC; + +import java.util.HashSet; + +/** + * 3rd pre filter to get executed. + * If the URI is part of open or mixed endpoint list then RBAC check is marked as false + */ +public class RbacPreCheckFilter extends ZuulFilter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private HashSet<String> openEndpointsWhitelist; + private HashSet<String> anonymousEndpointsWhitelist; + + public RbacPreCheckFilter(HashSet<String> openEndpointsWhitelist, + HashSet<String> anonymousEndpointsWhitelist) { + this.openEndpointsWhitelist = openEndpointsWhitelist; + this.anonymousEndpointsWhitelist = anonymousEndpointsWhitelist; + } + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 2; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + return true; + } + + @Override + public Object run() { + if ((openEndpointsWhitelist.contains(getRequestURI()) + || anonymousEndpointsWhitelist.contains(getRequestURI()))) { + setShouldDoRbac(false); + logger.info(SKIP_RBAC, getRequestURI()); + return null; + } + setShouldDoRbac(true); + return null; + } + + private void setShouldDoRbac(boolean enableRbac) { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(RBAC_BOOLEAN_FLAG_NAME, enableRbac); + ctx.set(RBAC_AVAILABLE, Boolean.TRUE); + } + + private String getRequestURI() { + return getRequest().getRequestURI(); + } + + private HttpServletRequest getRequest() { + RequestContext ctx = RequestContext.getCurrentContext(); + return ctx.getRequest(); + } +} diff --git a/src/main/java/org/tarento/retail/filters/pre/RequestEnrichmentFilter.java b/src/main/java/org/tarento/retail/filters/pre/RequestEnrichmentFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..9407436be5b4bec5bf14e75a481fb11510ce8e5a --- /dev/null +++ b/src/main/java/org/tarento/retail/filters/pre/RequestEnrichmentFilter.java @@ -0,0 +1,195 @@ +package org.tarento.retail.filters.pre; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.tarento.retail.contract.User; +import org.tarento.retail.model.RequestBodyInspector; +import org.tarento.retail.wrapper.CustomRequestWrapper; + +import javax.servlet.http.HttpServletRequest; + +import static org.tarento.retail.constants.RequestContextConstants.*; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Optional; + +/** + * 6th pre filter to get executed. + * Enriches the request body and header with 1) correlation id 2) user info + */ +@Component +public class RequestEnrichmentFilter extends ZuulFilter { + + private static final String FAILED_TO_ENRICH_REQUEST_BODY_MESSAGE = "Failed to enrich request body"; + private static final String USER_SERIALIZATION_MESSAGE = "Failed to serialize user"; + private static final String SKIPPED_BODY_ENRICHMENT_DUE_TO_NO_KNOWN_FIELD_MESSAGE = + "Skipped enriching request body since request info field is not present."; + private static final String BODY_ENRICHED_MESSAGE = "Enriched request payload."; + private static final String ADDED_USER_INFO_TO_HEADER_MESSAGE = "Adding user info to header."; + private static final String EMPTY_STRING = ""; + private static final String JSON_TYPE = "json"; + private final ObjectMapper objectMapper; + private static final String USER_INFO_HEADER_NAME = "x-user-info"; + private static final String PASS_THROUGH_GATEWAY_HEADER_NAME = "x-pass-through-gateway"; + private static final String PASS_THROUGH_GATEWAY_HEADER_VALUE = "true"; + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + public RequestEnrichmentFilter() { + this.objectMapper = new ObjectMapper(); + objectMapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true); + + } + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 5; + } + + @Override + public boolean shouldFilter() { + if("OPTIONS".equals(RequestContext.getCurrentContext().getRequest().getMethod())) { + return false; + } + return true; + } + + @Override + public Object run() { + // modifyRequestBody(); + addRequestHeaders(); + return null; + } + + private void addRequestHeaders() { + RequestContext ctx = RequestContext.getCurrentContext(); + addCorrelationIdHeader(ctx); + addUserInfoHeader(ctx); + addPassThroughGatewayHeader(ctx); + } + + private void addUserInfoHeader(RequestContext ctx) { + if (isUserInfoPresent()) { + User user = getUser(); + user.setAuthToken(ctx.get(AUTH_TOKEN_KEY).toString()); + try { + ctx.addZuulRequestHeader(USER_INFO_HEADER_NAME, objectMapper.writeValueAsString(user)); + logger.info(ADDED_USER_INFO_TO_HEADER_MESSAGE); + } catch (JsonProcessingException e) { + logger.error(USER_SERIALIZATION_MESSAGE, e); + throw new RuntimeException(e); + } + + } + } + + private void addCorrelationIdHeader(RequestContext ctx) { + ctx.addZuulRequestHeader(CORRELATION_ID_HEADER_NAME, getCorrelationId()); + } + + private void addPassThroughGatewayHeader(RequestContext ctx) { + ctx.addZuulRequestHeader(PASS_THROUGH_GATEWAY_HEADER_NAME, PASS_THROUGH_GATEWAY_HEADER_VALUE); + } + + private void modifyRequestBody() { + if (!isRequestBodyCompatible()) { + return; + } + try { + enrichRequestBody(); + } catch (IOException e) { + logger.error(FAILED_TO_ENRICH_REQUEST_BODY_MESSAGE, e); + throw new RuntimeException(e); + } + } + + private boolean isRequestBodyCompatible() { + return POST.equalsIgnoreCase(getRequestMethod()) + && !getRequestURI().matches(FILESTORE_REGEX) + && getRequestContentType().contains(JSON_TYPE); + } + + private HttpServletRequest getRequest() { + RequestContext ctx = RequestContext.getCurrentContext(); + return ctx.getRequest(); + } + + private String getRequestMethod() { + return getRequest().getMethod(); + } + + private String getRequestContentType() { + return Optional.ofNullable(getRequest().getContentType()).orElse(EMPTY_STRING).toLowerCase(); + } + + private String getRequestURI() { + return getRequest().getRequestURI(); + } + + @SuppressWarnings("unchecked") + private void enrichRequestBody() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + final RequestBodyInspector requestBodyInspector = getRequestBodyInspector(ctx); + HashMap<String, Object> requestInfo = requestBodyInspector.getRequestInfo(); + if (requestInfo == null) { + logger.info(SKIPPED_BODY_ENRICHMENT_DUE_TO_NO_KNOWN_FIELD_MESSAGE); + return; + } + setUserInfo(requestInfo); + setCorrelationId(requestInfo); + requestBodyInspector.updateRequestInfo(requestInfo); + CustomRequestWrapper requestWrapper = new CustomRequestWrapper(ctx.getRequest()); + requestWrapper.setPayload(objectMapper.writeValueAsString(requestBodyInspector.getRequestBody())); + logger.info(BODY_ENRICHED_MESSAGE); + ctx.setRequest(requestWrapper); + } + + private RequestBodyInspector getRequestBodyInspector(RequestContext ctx) throws IOException { + HashMap<String, Object> requestBody = getRequestBody(ctx); + return new RequestBodyInspector(requestBody); + } + + private void setCorrelationId(HashMap<String, Object> requestInfo) { + requestInfo.put(CORRELATION_ID_FIELD_NAME, getCorrelationId()); + } + + private String getCorrelationId() { + RequestContext ctx = RequestContext.getCurrentContext(); + return (String) ctx.get(CORRELATION_ID_KEY); + } + + private void setUserInfo(HashMap<String, Object> requestInfo) { + if (isUserInfoPresent()) { + requestInfo.put(USER_INFO_FIELD_NAME, getUser()); + } + } + + private User getUser() { + RequestContext ctx = RequestContext.getCurrentContext(); + return (User) ctx.get(USER_INFO_KEY); + } + + private boolean isUserInfoPresent() { + RequestContext ctx = RequestContext.getCurrentContext(); + return ctx.get(USER_INFO_KEY) != null; + } + + private HashMap<String, Object> getRequestBody(RequestContext ctx) throws IOException { + String payload = IOUtils.toString(ctx.getRequest().getInputStream()); + return objectMapper.readValue(payload, new TypeReference<HashMap<String, Object>>() { }); + } + +} diff --git a/src/main/java/org/tarento/retail/model/RequestBodyInspector.java b/src/main/java/org/tarento/retail/model/RequestBodyInspector.java new file mode 100644 index 0000000000000000000000000000000000000000..5ff047fb3d32bff033c79c2e0e819ab0cbda8061 --- /dev/null +++ b/src/main/java/org/tarento/retail/model/RequestBodyInspector.java @@ -0,0 +1,58 @@ +package org.tarento.retail.model; + +import static org.tarento.retail.constants.RequestContextConstants.REQUEST_INFO_FIELD_NAME_CAMEL_CASE; +import static org.tarento.retail.constants.RequestContextConstants.REQUEST_INFO_FIELD_NAME_PASCAL_CASE; + +import java.util.HashMap; + +public class RequestBodyInspector { + private HashMap<String, Object> requestBody; + + public RequestBodyInspector(HashMap<String, Object> requestBody) { + this.requestBody = requestBody; + } + + public boolean isRequestInfoPresent() { + return requestBody != null && isRequestInfoContainerFieldPresent(); + } + + public HashMap<String, Object> getRequestBody() { + return requestBody; + } + + public void updateRequestInfo(HashMap<String, Object> requestInfo) { + if (!isRequestInfoPresent()) { + return; + } + requestBody.put(getRequestInfoFieldNamePresent(), requestInfo); + } + + @SuppressWarnings("unchecked") + public HashMap<String, Object> getRequestInfo() { + if (isRequestInfoPresent()) { + return (HashMap<String, Object>) requestBody.get(getRequestInfoFieldNamePresent()); + } + return null; + } + + private String getRequestInfoFieldNamePresent() { + if (isPascalCasePresent()) { + return REQUEST_INFO_FIELD_NAME_PASCAL_CASE; + } else { + return REQUEST_INFO_FIELD_NAME_CAMEL_CASE; + } + } + + private boolean isRequestInfoContainerFieldPresent() { + return isPascalCasePresent() || isCamelCasePresent(); + } + + private boolean isCamelCasePresent() { + return requestBody.containsKey(REQUEST_INFO_FIELD_NAME_CAMEL_CASE); + } + + private boolean isPascalCasePresent() { + return requestBody.containsKey(REQUEST_INFO_FIELD_NAME_PASCAL_CASE); + } + +} diff --git a/src/main/java/org/tarento/retail/util/Constants.java b/src/main/java/org/tarento/retail/util/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..6a3ec86b1711ff4771d344bed01ce7156deb91ab --- /dev/null +++ b/src/main/java/org/tarento/retail/util/Constants.java @@ -0,0 +1,49 @@ +package org.tarento.retail.util; + +public class Constants { + + public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60; + public static final String SIGNING_KEY = "devglan123r"; + public static final String TOKEN_PREFIX = "Bearer "; + public static final String HEADER_STRING = "Authorization"; + public static final String HEADER_APPLICATION_JSON = "application/json"; + public static final String ERROR_CODE = "errorCode"; + public static final String ERROR_FIELD = "errorField"; + public static final String ERROR_MESSAGE_CODE = "errorMessageCode"; + public static final String ERROR_MESSAGE_VALUE = "common.error."; + public static final String SUCCESS_CODE = "successCode"; + public static final String ERROR_MESSAGE = "errorMessage"; + public static final String SUCCESS_MESSAGE = "successMessage"; + public static final String AUTH_HEADER = "Authorization"; + public static String SUCCESS= "success"; + public static String ASC= "asc"; + public static String DESC= "desc"; + public static String TRUE = "true"; + public static String FALSE = "false"; + public static String STRING_BLANK=""; + public static String COMMA_SPACE_SEPARATOR=", "; + public static final String DATE = "date"; + public static String QUERY_ALERT_SUBJECT = "Query Alert!!"; + public static String SCHEDULER_ALERT_SUBJECT = "Scheduler Alert!!"; + public static String STRING_SPACE = " "; + public static String STRING_HYPEN = "-"; + public static String NEW_MESSAGE = "New"; + public static String READ_MESSAGE = "Read"; + public static String DELETE_MESSAGE = "Delete"; + public static String SEND_MESSAGE = "Send"; + public static String FILE_TYPE = "PDF,DOC,TXT,JPG,JPEG,PNG,GIF,AAC,MP3,MP4"; + public static String IMAGE_FILE_TYPE = "JPG,JPEG,PNG,GIF"; + public static String FCM_API_URL = "fcm.api.url"; + public static String FCM_API_KEY = "fcm.api.key"; + public static String UPLOADED_FOLDER = "/home/uploads/"; + + public static int UNAUTHORIZED_ID = 401; + public static int SUCCESS_ID = 200; + public static int FAILURE_ID = 320; + public static int SESSION_INVALID_ID = 306; + public static int INVALID_AUTH_ID = 400; + public static String UNAUTHORIZED = "Invalid credentials. Please try again."; + public static String INVALID_AUTH = "Invalid Auth Token. Please try again"; + public static String PROCESS_FAIL = "Process failed, Please try again."; + public static String SESSION_INVALID = "Session invalid. Please login again"; +} diff --git a/src/main/java/org/tarento/retail/util/JSONObjectUtil.java b/src/main/java/org/tarento/retail/util/JSONObjectUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..2823c1051561ac7fb66e9296e0c6cc7dcabfd2a8 --- /dev/null +++ b/src/main/java/org/tarento/retail/util/JSONObjectUtil.java @@ -0,0 +1,63 @@ +package org.tarento.retail.util; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; + +/** + * Created by Abhishek on 10/5/2017. + */ +@Service(value = "jsonObjectUtil") +public class JSONObjectUtil { + @Autowired + public ObjectMapper mapper; + @Autowired + public Gson gson; + + private static Logger logger = Logger.getLogger(JSONObjectUtil.class); + + + /** + * @return + */ + public static String getJsonString(ObjectMapper objectMapper,Object object) throws JsonProcessingException { + //initialize(); + if(objectMapper != null){ + return objectMapper.writeValueAsString(object); + } + return null; + } + + public String getJsonString(Object object) throws JsonProcessingException { + //initialize(); + if(mapper != null){ + return mapper.writeValueAsString(object); + } + if(gson != null){ + return gson.toJson(object); + } + return null; + } + + + public ObjectMapper getMapper() { + return mapper; + } + + public void setObjectMapper(ObjectMapper objectMapper){ + mapper=objectMapper; + } + + public Gson getGson() { + return gson; + } + + public void setGson(Gson gsonn) + { + gson = gsonn; + } +} diff --git a/src/main/java/org/tarento/retail/util/JsonKey.java b/src/main/java/org/tarento/retail/util/JsonKey.java new file mode 100644 index 0000000000000000000000000000000000000000..80778ced258761e80c5e675a3ef07b4197c7c3b9 --- /dev/null +++ b/src/main/java/org/tarento/retail/util/JsonKey.java @@ -0,0 +1,97 @@ +/** + * + */ +package org.tarento.retail.util; + +/** + * @author Abhishek + * + */ +public class JsonKey { + /* + * USER_NAME contains userName as key + */ + public static final String USER_ID = "userId"; + /* + * USER_NAME contains userName as key + */ + public static final String USER_NAME = "username"; + /* + * PASSWORD contains password + */ + public static final String PASSWORD = "password"; + /* + * OLD_PASSWORD + */ + public static final String OLD_PASSWORD = "oldPass"; + /* + * NEW_PASSWORD + */ + public static final String NEW_PASSWORD = "newPass"; + /* + * DEVICE_TYPE user device type + */ + public static final String DEVICE_TYPE = "deviceType"; + /* + * DEVICE_ID user device id. + */ + public static final String DEVICE_ID = "deviceId"; + /* + * TIMEZONE + */ + public static final String TIMEZONE = "timeZone"; + /* + * SESSION + */ + public static final String SESSION = "sessionId"; + /* + * FIRST_NAME + */ + public static final String FIRST_NAME = "firstName"; + /* + * LAST_NAME + */ + public static final String LAST_NAME = "lastName"; + + public static final String URL = "url"; + + public static final String USERNAME = "username"; + + public static final String LINK = "link"; + + public static final String ROLE_IDS = "roleIds"; + + public static final String ROLENAME = "roleName"; + /* + * EMAIL + */ + public static final String EMAIL = "email"; + public static final String STATUS_CODE = "statusCode"; + public static final String STATUS = "statusInfo"; + public static final String STATUS_MESSAGE = "statusMessage"; + public static final String ERROR_MESSAGE = "errorMessage"; + /** + * RESPONSE. + */ + public static final String RESPONSE = "response"; + public static final String RESPONSE_DATA = "responseData"; + + /** + * IS_ACTIVE. + */ + public static final String IS_ACTIVE = "isActive"; + /** + * IS_DELETED. + */ + public static final String IS_DELETED = "isDeleted"; + + public static final String TIME = "time"; + + public static final String SUBSCRIPTION_NAME = "subscriptionName"; + + public static final String ASSET_TYPE = "assetType"; + public static final String ASSET_NAME = "assetName"; + public static final String FROM_USER = "fromUser"; + + +} diff --git a/src/main/java/org/tarento/retail/util/ResponseCode.java b/src/main/java/org/tarento/retail/util/ResponseCode.java new file mode 100644 index 0000000000000000000000000000000000000000..7a6c0797327b876d67fc9f08eb5e45a8b771f5a3 --- /dev/null +++ b/src/main/java/org/tarento/retail/util/ResponseCode.java @@ -0,0 +1,84 @@ +package org.tarento.retail.util; + +/** + * + * @author Abhishek + * + */ +public enum ResponseCode { + UnAuthorised(Constants.UNAUTHORIZED_ID, Constants.UNAUTHORIZED), Success( + Constants.SUCCESS_ID, Constants.SUCCESS),FAILURE( + Constants.FAILURE_ID, Constants.PROCESS_FAIL), SESSIONINVALID(Constants.SESSION_INVALID_ID, Constants.SESSION_INVALID), + INVALIDAUTH(Constants.INVALID_AUTH_ID, Constants.INVALID_AUTH); + /** + * error code contains int value + */ + private int errorCode; + /** + * errorMessage contains proper error message. + */ + private String errorMessage; + + /** + * @param errorCode + * @param errorMessage + */ + private ResponseCode(int errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + /** + * + * @param errorCode + * @return + */ + public String getMessage(int errorCode) { + return ""; + } + + /** + * @return + */ + public int getErrorCode() { + return errorCode; + } + + /** + * @param errorCode + */ + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + /** + * @return + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * @param errorMessage + */ + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + /** + * This method will provide status message based on code + * + * @param code + * @return String + */ + public static String getResponseMessage(int code) { + String value = ""; + ResponseCode responseCodes[] = ResponseCode.values(); + for (ResponseCode actionState : responseCodes) { + if (actionState.getErrorCode() == code) { + value = actionState.getErrorMessage(); + } + } + return value; + } +} diff --git a/src/main/java/org/tarento/retail/util/ResponseGenerator.java b/src/main/java/org/tarento/retail/util/ResponseGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..f72e4db152889be8f9b180004be44fee46255eaa --- /dev/null +++ b/src/main/java/org/tarento/retail/util/ResponseGenerator.java @@ -0,0 +1,116 @@ +package org.tarento.retail.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class ResponseGenerator { + + + private static ObjectMapper objectMapper = new ObjectMapper(); + + public static String failureResponse() throws JsonProcessingException{ + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.FAILURE.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, + ResponseCode.FAILURE.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE, + ResponseCode.FAILURE.getErrorMessage()); + return JSONObjectUtil.getJsonString(objectMapper,response); + } + + + public static String failureResponse(String message) throws JsonProcessingException{ + ObjectNode actualResponse = objectMapper.createObjectNode(); + + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.FAILURE.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, + ResponseCode.FAILURE.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE,message); + actualResponse.putPOJO(JsonKey.STATUS,response); + + return JSONObjectUtil.getJsonString(objectMapper,actualResponse); + } + + public static String unauthorizedResponse(String message) throws JsonProcessingException{ + ObjectNode actualResponse = objectMapper.createObjectNode(); + + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.UnAuthorised.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, + ResponseCode.UnAuthorised.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE,message); + actualResponse.putPOJO(JsonKey.STATUS,response); + + return JSONObjectUtil.getJsonString(objectMapper,actualResponse); + } + + + public static String invalidSessionResponse(String message) throws JsonProcessingException{ + ObjectNode actualResponse = objectMapper.createObjectNode(); + + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.SESSIONINVALID.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, + ResponseCode.SESSIONINVALID.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE,message); + actualResponse.putPOJO(JsonKey.STATUS,response); + + return JSONObjectUtil.getJsonString(objectMapper,actualResponse); + } + + + public static String invalidAuthResponse(String message) throws JsonProcessingException{ + ObjectNode actualResponse = objectMapper.createObjectNode(); + + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.INVALIDAUTH.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, + ResponseCode.INVALIDAUTH.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE,message); + actualResponse.putPOJO(JsonKey.STATUS,response); + + return JSONObjectUtil.getJsonString(objectMapper,actualResponse); + } + + /** + * this method will crate success response and send to controller. + * + * @param obj + * Object + * @return ObjectNode object. + */ + public static String successResponse(Object obj) throws JsonProcessingException { + ObjectNode actualResponse = objectMapper.createObjectNode(); + + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.Success.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, ResponseCode.Success.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE, ""); + actualResponse.putPOJO(JsonKey.STATUS,response); + if (obj != null) { + actualResponse.putPOJO(JsonKey.RESPONSE_DATA, obj); + } + + return JSONObjectUtil.getJsonString(objectMapper,actualResponse); + } + + /** + * this method will crate success response and send to controller. + * + * @return ObjectNode object. + */ + public static String successResponse() throws JsonProcessingException{ + ObjectNode actualResponse = objectMapper.createObjectNode(); + + ObjectNode response = objectMapper.createObjectNode(); + response.put(JsonKey.STATUS_CODE, ResponseCode.Success.getErrorCode()); + response.put(JsonKey.STATUS_MESSAGE, + ResponseCode.Success.getErrorMessage()); + response.put(JsonKey.ERROR_MESSAGE, ""); + actualResponse.putPOJO(JsonKey.STATUS,response); + + return JSONObjectUtil.getJsonString(objectMapper,actualResponse); + } +} diff --git a/src/main/java/org/tarento/retail/util/ResponseMessages.java b/src/main/java/org/tarento/retail/util/ResponseMessages.java new file mode 100644 index 0000000000000000000000000000000000000000..c103bce7e4d2f8c587df08aa04705a9d00307286 --- /dev/null +++ b/src/main/java/org/tarento/retail/util/ResponseMessages.java @@ -0,0 +1,24 @@ +package org.tarento.retail.util; + +/** + * + * @author Darshan Nagesh + * + */ + +public interface ResponseMessages { + + public interface StandardKeysToCompare { + final String ERROR_STATUS_CODE = "javax.servlet.error.status_code"; + final String UNAUTHORIZED_FILTER_SOURCE = "pre:RbacFilter"; + final String INVALID_AUTH_FILTER_SOURCE = "pre:AuthFilter"; + final String ERROR_STATUS_MESSAGE = "javax.servlet.error.message"; + + + } + + public interface FailureMessages { + final String INVALID_SESSION = "Session has expired. Please login again and continue"; + final String INVALID_AUTH_TOKEN = "Invalid Token ID. Please login again and continue"; + } +} diff --git a/src/main/java/org/tarento/retail/web/controller/ZuulErrorController.java b/src/main/java/org/tarento/retail/web/controller/ZuulErrorController.java new file mode 100644 index 0000000000000000000000000000000000000000..63abcfd8514c0d516d1aaacec8c025ab3172905a --- /dev/null +++ b/src/main/java/org/tarento/retail/web/controller/ZuulErrorController.java @@ -0,0 +1,62 @@ +package org.tarento.retail.web.controller; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.tarento.retail.util.ResponseGenerator; +import org.tarento.retail.util.ResponseMessages.FailureMessages; +import org.tarento.retail.util.ResponseMessages.StandardKeysToCompare; + +import com.fasterxml.jackson.core.JsonProcessingException; + + + + +/** + * + * @author Darshan Nagesh + * + */ + +@ControllerAdvice +@RestController +public class ZuulErrorController implements ErrorController { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Value("${error.path:/error}") + private String errorPath; + + @Override + public String getErrorPath() { + return errorPath; + } + + @RequestMapping(value = "${error.path:/error}", produces = "application/json") + public @ResponseBody String error(HttpServletRequest request) throws JsonProcessingException { + logger.info("Request on the Zuul Error Controller : " + request.toString()); + logger.info("Request URL : " + request.getPathInfo()); + logger.info("Error Status Message : "+ request.getAttribute(StandardKeysToCompare.ERROR_STATUS_MESSAGE)); + logger.info("Error Status Code : " + request.getAttribute(StandardKeysToCompare.ERROR_STATUS_CODE)); + if(request.getAttribute(StandardKeysToCompare.ERROR_STATUS_CODE) != null + && Integer.parseInt(String.valueOf(request.getAttribute(StandardKeysToCompare.ERROR_STATUS_CODE))) == 500 + && request.getAttribute(StandardKeysToCompare.ERROR_STATUS_MESSAGE).equals(StandardKeysToCompare.UNAUTHORIZED_FILTER_SOURCE)) { + return ResponseGenerator.unauthorizedResponse(FailureMessages.INVALID_SESSION); + } + + if(request.getAttribute(StandardKeysToCompare.ERROR_STATUS_CODE) != null + && Integer.parseInt(String.valueOf(request.getAttribute(StandardKeysToCompare.ERROR_STATUS_CODE))) == 500 + && request.getAttribute(StandardKeysToCompare.ERROR_STATUS_MESSAGE).equals(StandardKeysToCompare.INVALID_AUTH_FILTER_SOURCE)) { + return ResponseGenerator.invalidSessionResponse(FailureMessages.INVALID_AUTH_TOKEN); + } + return ResponseGenerator.invalidSessionResponse(FailureMessages.INVALID_SESSION); + } +} + diff --git a/src/main/java/org/tarento/retail/wrapper/CustomRequestWrapper.java b/src/main/java/org/tarento/retail/wrapper/CustomRequestWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..49ce069b65a6179793e931c58baf2090c70e3185 --- /dev/null +++ b/src/main/java/org/tarento/retail/wrapper/CustomRequestWrapper.java @@ -0,0 +1,50 @@ +package org.tarento.retail.wrapper; + +import com.netflix.zuul.http.HttpServletRequestWrapper; +import com.netflix.zuul.http.ServletInputStreamWrapper; +import org.apache.commons.io.IOUtils; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public class CustomRequestWrapper extends HttpServletRequestWrapper { + + private String payload; + + public CustomRequestWrapper(HttpServletRequest request) { + super(request); + convertInputStreamToString(request); + } + + private void convertInputStreamToString(HttpServletRequest request) { + try { + payload = IOUtils.toString(request.getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload){ + this.payload = payload; + } + + @Override + public int getContentLength() { + return payload.length(); + } + + @Override + public long getContentLengthLong() { + return payload.length(); + } + + @Override + public ServletInputStream getInputStream() { + return new ServletInputStreamWrapper(payload.getBytes()); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..e8f215ff41cfa027c7d67478f87da729bf973e70 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,38 @@ +zuul.routes.user.path=/user/** +zuul.routes.user.stripPrefix=false +zuul.routes.user.url=http://localhost:8081/ + +zuul.routes.login.path=/login/** +zuul.routes.login.stripPrefix=false +zuul.routes.login.url=http://localhost:8081/ + +zuul.routes.ingest.path=/ingest/** +zuul.routes.ingest.stripPrefix=false +zuul.routes.ingest.url=http://localhost:8097/ + +zuul.routes.analytics.path=/dashboard/** +zuul.routes.analytics.stripPrefix=false +zuul.routes.analytics.url=http://localhost:8091/ + +zuul.routes.form.path=/forms/** +zuul.routes.form.stripPrefix=false +zuul.routes.form.url=http://localhost:8099/ + +zuul.sensitiveHeaders=Cookie,Set-Cookie,x-user-info,auth-token + +retail.auth-service-host=http://localhost:8081/token/validate +retail.auth-service-uri=user/_details?access_token= +retail.user-info-header=x-user-info + + +retail.open-endpoints-whitelist=/login,/forms/saveFormSubmit,/forms/getFormById,/forms/uploadData +retail.mixed-mode-endpoints-whitelist= +spring.http.multipart.max-file-size=100MB +spring.http.multipart.max-request-size=100MB + +spring.mvc.dispatch-options-request=true + +logging.pattern.console=%clr(%X{CORRELATION_ID:-}) %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx} + +zuul.host.socket-timeout-millis=60000 +zuul.host.connect-timeout-millis=60000 \ No newline at end of file diff --git a/src/test/java/org/musti/Resources.java b/src/test/java/org/musti/Resources.java new file mode 100644 index 0000000000000000000000000000000000000000..818119e5fe5412dc234659a1855e134f9a230b3c --- /dev/null +++ b/src/test/java/org/musti/Resources.java @@ -0,0 +1,23 @@ +/*package org.musti; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; + +public class Resources { + + public String getFileContents(String fileName) { + try { + return IOUtils.toString(this.getClass().getClassLoader() + .getResourceAsStream(fileName), "UTF-8") + .replaceAll("\n", "") + .replaceAll("\\s*\\{\\s*", "\\{") + .replaceAll("\\s*\\}\\s*", "\\}") + .replaceAll("\\s*:\\s*", ":") + .replaceAll("\\s*,\\s*", ","); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +*/ \ No newline at end of file diff --git a/src/test/java/org/musti/contract/ActionTest.java b/src/test/java/org/musti/contract/ActionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fe051e67b0cfab01b1f07b3c99fa49a6ede10325 --- /dev/null +++ b/src/test/java/org/musti/contract/ActionTest.java @@ -0,0 +1,20 @@ +/*package org.musti.contract; + +import org.junit.Test; +import org.musti.contract.Action; + +import static org.junit.Assert.assertEquals; + +public class ActionTest { + + @Test + public void test_should_convert_URI_with_dynamic_placeholders_to_regex_equivalent() { + final Action action = new Action(); + action.setUrl("/pgr/seva/{id}/_update"); + + final String actualRegexURI = action.getRegexUrl(); + + assertEquals("/pgr/seva/\\w+/_update", actualRegexURI); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/post/ResponseEnhancementFilterTest.java b/src/test/java/org/musti/filters/post/ResponseEnhancementFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b68c7adb6a966d0cf33eeea36a60c538422cb82a --- /dev/null +++ b/src/test/java/org/musti/filters/post/ResponseEnhancementFilterTest.java @@ -0,0 +1,56 @@ +/*package org.musti.filters.post; + +import com.netflix.util.Pair; +import com.netflix.zuul.context.RequestContext; +import org.junit.Before; +import org.junit.Test; +import org.musti.filters.post.ResponseEnhancementFilter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.List; + +import static org.junit.Assert.*; + +public class ResponseEnhancementFilterTest { + + private ResponseEnhancementFilter filter; + + @Before + public void before() { + filter = new ResponseEnhancementFilter(); + RequestContext.getCurrentContext().clear(); + } + + @Test + public void test_should_set_correlation_id_response_header() { + RequestContext.getCurrentContext().set("CORRELATION_ID", "someCorrelationId"); + final MockHttpServletResponse response = new MockHttpServletResponse(); + response.setStatus(400); + RequestContext.getCurrentContext().setResponse(response); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://host/api/v1"); + RequestContext.getCurrentContext().setRequest(request); + + filter.run(); + + final List<Pair<String, String>> zuulResponseHeaders = + RequestContext.getCurrentContext().getZuulResponseHeaders(); + + assertEquals(1, zuulResponseHeaders.size()); + final Pair<String, String> stringPair = zuulResponseHeaders.get(0); + assertEquals("x-correlation-id", stringPair.first()); + assertEquals("someCorrelationId", stringPair.second()); + } + + @Test + public void test_should_always_execute_filter() { + assertTrue( filter.shouldFilter()); + } + + @Test + public void test_should_execute_as_last_post_filter() { + assertEquals(0, filter.filterOrder()); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/pre/AuthFilterTest.java b/src/test/java/org/musti/filters/pre/AuthFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f2065d31d5cebfe3511a1cfdf0d25ab36583469b --- /dev/null +++ b/src/test/java/org/musti/filters/pre/AuthFilterTest.java @@ -0,0 +1,87 @@ +/*package org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.musti.Resources; +import org.musti.contract.User; +import org.musti.filters.pre.AuthFilter; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AuthFilterTest { + private MockHttpServletRequest request = new MockHttpServletRequest(); + private Resources resources = new Resources(); + + @Mock + private RestTemplate restTemplate; + + @Mock + private ProxyRequestHelper proxyRequestHelper; + + private AuthFilter authFilter; + + private String authServiceHost = "http://localhost:8081/"; + private String authUri = "user/_details?access_token="; + private String userInfoHeader = "x-user-info"; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + authFilter = new AuthFilter(proxyRequestHelper, restTemplate, authServiceHost, authUri); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.clear(); + ctx.setRequest(request); + } + + @Test + public void testBasicProperties() { + assertThat(authFilter.filterType(), is("pre")); + assertThat(authFilter.filterOrder(), is(3)); + } + + @Test + public void testThatFilterShouldBeAppliedBasedOnContext() { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set("shouldDoAuth", false); + assertFalse(authFilter.shouldFilter()); + + ctx.set("shouldDoAuth", true); + assertTrue(authFilter.shouldFilter()); + } + + @Test + public void testThatFilterShouldAbortIfValidatingAuthTokenFails() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + String authToken = "dummy-auth-token"; + ctx.set("authToken", authToken); + request.setMethod("POST"); + ctx.setRequest(request); + ctx.setResponse(new MockHttpServletResponse()); + String authUrl = String.format("%s%s%s", authServiceHost, authUri, ""); + when(restTemplate.postForObject(eq(authUrl), any(), eq(User.class))) + .thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); + + authFilter.run(); + + assertFalse(ctx.sendZuulResponse()); + verify(proxyRequestHelper).setResponse(eq(401), any(), any()); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/pre/AuthPreCheckFilterTest.java b/src/test/java/org/musti/filters/pre/AuthPreCheckFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..90325bfcad4ef4215d3efed0c5818c366f81c0a1 --- /dev/null +++ b/src/test/java/org/musti/filters/pre/AuthPreCheckFilterTest.java @@ -0,0 +1,311 @@ +/*package org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.musti.filters.pre.AuthPreCheckFilter; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.io.IOException; +import java.util.HashSet; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class AuthPreCheckFilterTest { + private MockHttpServletRequest request = new MockHttpServletRequest(); + + private AuthPreCheckFilter authPreCheckFilter; + + private HashSet<String> openEndpointsWhitelist = new HashSet<>(); + private HashSet<String> anonymousEndpointsWhitelist = new HashSet<>(); + + @Before + public void init() { + openEndpointsWhitelist.add("open-endpoint1"); + openEndpointsWhitelist.add("open-endpoint2"); + anonymousEndpointsWhitelist.add("anonymous-endpoint1"); + anonymousEndpointsWhitelist.add("anonymous-endpoint2"); + authPreCheckFilter = new AuthPreCheckFilter(openEndpointsWhitelist, anonymousEndpointsWhitelist); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.clear(); + ctx.setRequest(request); + } + + @Test + public void testBasicProperties() { + assertThat(authPreCheckFilter.filterType(), is("pre")); + assertThat(authPreCheckFilter.filterOrder(), is(1)); + assertTrue(authPreCheckFilter.shouldFilter()); + } + + @Test + public void testThatAuthShouldNotHappenForOpenEndpoints() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("open-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("open-endpoint2"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldNotHappenForAnonymousGETEndpointsOnNoAuthToken() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("anonymous-endpoint1"); + request.setMethod("GET"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldHappenForAnonymousGETEndpointsOnAuthTokenInHeader() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("GET"); + request.addHeader("auth-token", "token"); + + request.setRequestURI("/anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertTrue((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("/anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertTrue((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldNotHappenForAnonymousPOSTEndpointsOnNoAuthToken() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("anonymous-endpoint1"); + request.setMethod("POST"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\"}}"))); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldNotHappenForAnonymousPOSTEndpointsOnNoRequestInfo() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("anonymous-endpoint1"); + request.setMethod("POST"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"ServiceRequest\": {\"fu\": \"bar\"}}"))); + + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldNotHappenForAnonymousPUTEndpointsOnNoAuthToken() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("anonymous-endpoint1"); + request.setMethod("PUT"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\"}}"))); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldNotHappenForAnonymousPUTEndpointsOnNoRequestInfo() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("anonymous-endpoint1"); + request.setMethod("PUT"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"ServiceRequest\": {\"fu\": \"bar\"}}"))); + + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + + request.setRequestURI("anonymous-endpoint1"); + ctx.setRequest(request); + authPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldHappenForOtherGETEndpointsOnAuthTokenInHeader() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.addHeader("auth-token", "token"); + request.setMethod("GET"); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertTrue((Boolean) ctx.get("shouldDoAuth")); + } + + @Test + public void testThatAuthShouldHappenForOtherPOSTEndpointsOnAuthTokenInRequestBody() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.addHeader("auth-token", "token"); + request.setMethod("POST"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\", \"authToken\": \"authtoken\"}}"))); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertTrue((Boolean) ctx.get("shouldDoAuth")); + assertEquals("authtoken", ctx.get("authToken")); + } + + @Test + public void testThatAuthShouldHappenForOtherPUTEndpointsOnAuthTokenInRequestBody() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.addHeader("auth-token", "token"); + request.setMethod("PUT"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\", \"authToken\": \"authtoken\"}}"))); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertTrue((Boolean) ctx.get("shouldDoAuth")); + assertEquals("authtoken", ctx.get("authToken")); + } + + @Test + public void testThatFilterShouldAbortForOtherGETEndpointsOnNoAuthToken() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("GET"); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(401)); + } + + @Test + public void testThatFilterShouldAbortForOtherPOSTEndpointsOnNoAuthToken() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("POST"); + request.setRequestURI("other-endpoint"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\"}}"))); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(401)); + } + + @Test + public void testThatFilterShouldAbortForOtherPOSTEndpointsOnNoRequestnInfo() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("POST"); + request.setRequestURI("other-endpoint"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"ServiceRequest\": {\"fu\": \"bar\"}}"))); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(401)); + } + + @Test + public void testThatFilterShouldAbortForPOSTEndpointsOnNoRequestBody() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("POST"); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(500)); + } + + @Test + public void testThatFilterShouldAbortForOtherPUTEndpointsOnNoAuthToken() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("PUT"); + request.setRequestURI("other-endpoint"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\"}}"))); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(401)); + } + + @Test + public void testThatFilterShouldAbortForOtherPUTEndpointsOnNoRequestnInfo() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("PUT"); + request.setRequestURI("other-endpoint"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"ServiceRequest\": {\"fu\": \"bar\"}}"))); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(401)); + } + + @Test + public void testThatFilterShouldAbortForPUTEndpointsOnNoRequestBody() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("PUT"); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertFalse(ctx.sendZuulResponse()); + assertThat(ctx.get("error.status_code"), is(500)); + } + + @Test + public void testThatAuthTokenIsAlwaysReferredFromHeaderForFileStoreEndpoints() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setMethod("POST"); + request.addHeader("auth-token", "authtoken"); + request.setRequestURI("/filestore/v1/files"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + assertEquals("authtoken", ctx.get("authToken")); + } + + @Test + public void testThatRequestInfoIsSanitizedForOtherPUTEndpoints() throws IOException { + RequestContext ctx = RequestContext.getCurrentContext(); + request.addHeader("auth-token", "token"); + request.setMethod("PUT"); + request.setContent(IOUtils.toByteArray(IOUtils.toInputStream("{\"RequestInfo\": {\"fu\": \"bar\", \"authToken\": \"authtoken\", \"userInfo\": {\"name\": \"fubarred\"}}}"))); + request.setRequestURI("other-endpoint"); + ctx.setRequest(request); + + authPreCheckFilter.run(); + + String expectedBody = "{\"RequestInfo\":{\"fu\":\"bar\",\"authToken\":\"authtoken\"}}"; + assertEquals(expectedBody, IOUtils.toString(ctx.getRequest().getInputStream())); + } +} +*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/pre/CorrelationIdFilterTest.java b/src/test/java/org/musti/filters/pre/CorrelationIdFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f871fdfe7319475f3015b1891a8266f347c48835 --- /dev/null +++ b/src/test/java/org/musti/filters/pre/CorrelationIdFilterTest.java @@ -0,0 +1,108 @@ +/*package org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; + +import org.junit.Before; +import org.junit.Test; +import org.musti.filters.pre.CorrelationIdFilter; +import org.slf4j.MDC; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +public class CorrelationIdFilterTest { + + private CorrelationIdFilter correlationIdFilter; + + @Before + public void before() { + correlationIdFilter = new CorrelationIdFilter(); + } + + @Testpackage org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; + +public class CorrelationIdFilterTest { + + private CorrelationIdFilter correlationIdFilter; + + @Before + public void before() { + correlationIdFilter = new CorrelationIdFilter(); + } + + @Test + public void test_should_set_context_with_correlation_id() { + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContext.getCurrentContext().setRequest(request); + + correlationIdFilter.run(); + + assertNotNull(RequestContext.getCurrentContext().get("CORRELATION_ID")); + } + + @Test + public void test_should_set_mdc_with_correlation_id() { + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContext.getCurrentContext().setRequest(request); + + correlationIdFilter.run(); + + assertNotNull(MDC.get("CORRELATION_ID")); + } + + @Test + public void test_should_set_filter_order_to_beginning() { + assertEquals(0, correlationIdFilter.filterOrder()); + } + + @Test + public void test_should_execute_as_pre_filter() { + assertEquals("pre", correlationIdFilter.filterType()); + } + + @Test + public void test_should_always_execute_filter() { + assertTrue( correlationIdFilter.shouldFilter()); + } + +} + public void test_should_set_context_with_correlation_id() { + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContext.getCurrentContext().setRequest(request); + + correlationIdFilter.run(); + + assertNotNull(RequestContext.getCurrentContext().get("CORRELATION_ID")); + } + + @Test + public void test_should_set_mdc_with_correlation_id() { + MockHttpServletRequest request = new MockHttpServletRequest(); + RequestContext.getCurrentContext().setRequest(request); + + correlationIdFilter.run(); + + assertNotNull(MDC.get("CORRELATION_ID")); + } + + @Test + public void test_should_set_filter_order_to_beginning() { + assertEquals(0, correlationIdFilter.filterOrder()); + } + + @Test + public void test_should_execute_as_pre_filter() { + assertEquals("pre", correlationIdFilter.filterType()); + } + + @Test + public void test_should_always_execute_filter() { + assertTrue( correlationIdFilter.shouldFilter()); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/pre/RbacFilterTest.java b/src/test/java/org/musti/filters/pre/RbacFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ecb4ecf28547c0bf39d8001750157b5fe6747d35 --- /dev/null +++ b/src/test/java/org/musti/filters/pre/RbacFilterTest.java @@ -0,0 +1,143 @@ +/*package org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; + +import org.junit.Before; +import org.junit.Test; +import org.musti.contract.Action; +import org.musti.contract.User; +import org.musti.filters.pre.RbacFilter; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; +import static org.musti.constants.RequestContextConstants.ERROR_CODE_KEY; +import static org.musti.constants.RequestContextConstants.USER_INFO_KEY; + +public class RbacFilterTest { + + private MockHttpServletRequest request; + private RbacFilter rbacFilter; + + @Before + public void init(){ + request = new MockHttpServletRequest(); + rbacFilter = new RbacFilter(); + RequestContext.getCurrentContext().clear(); + } + + @Test + public void testThatFilterOrderIs3() throws Exception { + assertThat(rbacFilter.filterOrder(), is(4)); + } + + @Test + public void testThatFilterShouldNotRunWhenRbacIsNotRequired() throws Exception { + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set("shouldDoRbac", false); + assertFalse(rbacFilter.shouldFilter()); + } + + @Test + public void shouldAbortWhenUserIsRequestingUnauthorizedURI() throws Exception { + User user = new User(); + Action action1 = new Action(); + action1.setUrl("/pgr/seva"); + user.setActions(new ArrayList<>(Arrays.asList(action1))); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(USER_INFO_KEY, user); + + request.setRequestURI("/hr-masters/do/something"); + ctx.setRequest(request); + rbacFilter.run(); + + assertForbiddenResponse(ctx); + } + + @Test + public void shouldNotAbortWhenUserIsRequestingAuthorizedURI() throws Exception { + User user = new User(); + Action action1 = new Action(); + action1.setUrl("/pgr/seva"); + user.setActions(new ArrayList<>(Arrays.asList(action1))); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(USER_INFO_KEY, user); + + request.setRequestURI("/pgr/seva"); + ctx.setRequest(request); + rbacFilter.run(); + + assertEquals(null, ctx.get(ERROR_CODE_KEY)); + } + + @Test + public void shouldAbortWhenUserDoesNotHaveAnyAuthorizedURI() throws Exception { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("/hr-masters/do/something"); + ctx.setRequest(request); + User user = new User(); + user.setActions(new ArrayList<>()); + ctx.set(USER_INFO_KEY, user); + + rbacFilter.run(); + + assertForbiddenResponse(ctx); + } + + @Test + public void shouldNotAbortWhenUserIsRequestingURIAndAuthorizedURIHasDynamicPlaceHolders() throws Exception { + User user = new User(); + Action action1 = new Action(); + action1.setUrl("/pgr/seva/{id}/_update"); + user.setActions(new ArrayList<>(Arrays.asList(action1))); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(USER_INFO_KEY, user); + request.setRequestURI("/pgr/seva/123/_update"); + ctx.setRequest(request); + + rbacFilter.run(); + + assertEquals(null, ctx.get(ERROR_CODE_KEY)); + } + + @Test + public void shouldNotAbortWhenUserIsRequestingURIAndAuthorizedURIHasMultipleDynamicPlaceHolders() throws Exception { + User user = new User(); + Action action1 = new Action(); + action1.setUrl("/pgr/seva/{tenantCode}/{id}/_update"); + user.setActions(new ArrayList<>(Arrays.asList(action1))); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(USER_INFO_KEY, user); + request.setRequestURI("/pgr/seva/default/123/_update"); + ctx.setRequest(request); + + rbacFilter.run(); + + assertEquals(null, ctx.get(ERROR_CODE_KEY)); + } + + @Test + public void shouldAbortWhenUserIsRequestingURIAndAuthorizedURIWithDynamicPlaceHoldersDoesNotMatch() throws Exception { + User user = new User(); + Action action1 = new Action(); + action1.setUrl("/pgr/seva/{id}/_create"); + user.setActions(new ArrayList<>(Arrays.asList(action1))); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.set(USER_INFO_KEY, user); + + request.setRequestURI("/pgr/seva/123/_update"); + ctx.setRequest(request); + rbacFilter.run(); + + assertForbiddenResponse(ctx); + } + + private void assertForbiddenResponse(RequestContext ctx) { + assertEquals(403, ctx.get(ERROR_CODE_KEY)); + assertFalse(ctx.sendZuulResponse()); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/pre/RbacPreCheckFilterTest.java b/src/test/java/org/musti/filters/pre/RbacPreCheckFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..87a5767cb3f348dc0b1d633cc0a16845a1b50cec --- /dev/null +++ b/src/test/java/org/musti/filters/pre/RbacPreCheckFilterTest.java @@ -0,0 +1,72 @@ +/*package org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; + +import org.junit.Before; +import org.junit.Test; +import org.musti.filters.pre.RbacPreCheckFilter; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class RbacPreCheckFilterTest { + private MockHttpServletRequest request = new MockHttpServletRequest(); + + private HashSet<String> openEndpointsWhitelist = new HashSet<>(); + private HashSet<String> anonymousEndpointsWhitelist = new HashSet<>(); + + private RbacPreCheckFilter rbacPreCheckFilter; + + @Before + public void init(){ + openEndpointsWhitelist.add("/user/_details"); + openEndpointsWhitelist.add("open-endpoint2"); + anonymousEndpointsWhitelist.add("/pgr/complaintTypeCategories"); + anonymousEndpointsWhitelist.add("anonymous-endpoint2"); + rbacPreCheckFilter = new RbacPreCheckFilter(openEndpointsWhitelist, anonymousEndpointsWhitelist); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.clear(); + ctx.setRequest(request); + + } + + @Test + public void testBasicProperties() { + assertThat(rbacPreCheckFilter.filterType(), is("pre")); + assertThat(rbacPreCheckFilter.filterOrder(), is(2)); + } + + @Test + public void testThatRbacCheckShouldNotHappenForOpenEndpoints() { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("/user/_details"); + ctx.setRequest(request); + rbacPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoRbac")); + } + + @Test + public void test_That_Rbac_Check_Sould_Not_Happen_For_AnonymousEndPoints(){ + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("/pgr/complaintTypeCategories"); + ctx.setRequest(request); + rbacPreCheckFilter.run(); + assertFalse((Boolean) ctx.get("shouldDoRbac")); + } + + @Test + public void test_should_return_true_when_uri_is_not_in_open_or_anonymous_endpoint_and_uri_is_present_in_rbacwhitelist() throws Exception { + RequestContext ctx = RequestContext.getCurrentContext(); + request.setRequestURI("/pgr/seva/_create"); + ctx.setRequest(request); + rbacPreCheckFilter.run(); + assertTrue((Boolean) ctx.get("shouldDoRbac")); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/filters/pre/RequestEnrichmentFilterTest.java b/src/test/java/org/musti/filters/pre/RequestEnrichmentFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0c1510baf53c940872846d911bec4bf3c71b9ed2 --- /dev/null +++ b/src/test/java/org/musti/filters/pre/RequestEnrichmentFilterTest.java @@ -0,0 +1,164 @@ +/*package org.musti.filters.pre; + +import com.netflix.zuul.context.RequestContext; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.musti.Resources; +import org.musti.contract.Role; +import org.musti.contract.User; +import org.musti.filters.pre.RequestEnrichmentFilter; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class RequestEnrichmentFilterTest { + + private RequestEnrichmentFilter filter; + private Resources resources = new Resources(); + + @Before + public void before() { + filter = new RequestEnrichmentFilter(); + RequestContext.getCurrentContext().clear(); + } + + @Test + public void test_should_set_filter_order_to_execute_last() { + assertEquals(5, filter.filterOrder()); + } + + @Test + public void test_should_always_execute_filter() { + assertTrue(filter.shouldFilter()); + } + + @Test + public void test_should_add_correlation_id_request_header() { + final RequestContext currentContext = RequestContext.getCurrentContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + currentContext.setRequest(request); + final String expectedCorrelationId = "someCorrelationId"; + currentContext.set("CORRELATION_ID", expectedCorrelationId); + + filter.run(); + + final Map<String, String> zuulRequestHeaders = currentContext.getZuulRequestHeaders(); + assertEquals(2, zuulRequestHeaders.size()); + assertEquals(expectedCorrelationId, zuulRequestHeaders.get("x-correlation-id")); + } + + @Test + public void test_should_add_correlation_id_to_request_info_section_of_request_body() throws IOException { + final RequestContext currentContext = RequestContext.getCurrentContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setRequestURI("http://foo/bar/v1/_create"); + request.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + request.setContent(getContent("postRequestFromConsumer.json")); + currentContext.setRequest(request); + final String expectedCorrelationId = "someCorrelationId"; + currentContext.set("CORRELATION_ID", expectedCorrelationId); + currentContext.set("USER_INFO", null); + + filter.run(); + + String expectedBody = resources.getFileContents("postRequestWithCorrelationId.json"); + assertEquals(expectedBody, IOUtils.toString(currentContext.getRequest().getInputStream())); + } + + @Test + public void test_should_add_user_info_to_request_info_section_of_request_body() throws IOException { + final RequestContext currentContext = RequestContext.getCurrentContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setRequestURI("http://foo/bar/v1/_create"); + request.setContent(getContent("postRequestFromConsumer.json")); + request.setContentType(MediaType.APPLICATION_JSON_VALUE); + currentContext.setRequest(request); + final String expectedCorrelationId = "someCorrelationId"; + currentContext.set("CORRELATION_ID", expectedCorrelationId); + currentContext.set("USER_INFO", getUser()); + + filter.run(); + + String expectedBody = resources.getFileContents("enrichedPostRequest.json"); + assertEquals(expectedBody, IOUtils.toString(currentContext.getRequest().getInputStream())); + } + + @Test + public void test_should_add_user_info_request_header_for_GET_request_type() throws IOException { + final RequestContext currentContext = RequestContext.getCurrentContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.setRequestURI("http://foo/bar/v1/_search"); + currentContext.setRequest(request); + currentContext.set("CORRELATION_ID", "someCorrelationId"); + currentContext.set("USER_INFO", getUser()); + + filter.run(); + + String expectedHeaderValue = resources.getFileContents("userInfoHeader.json"); + final Map<String, String> zuulRequestHeaders = currentContext.getZuulRequestHeaders(); + assertEquals(expectedHeaderValue, zuulRequestHeaders.get("x-user-info")); + } + + @Test + public void test_should_not_modify_request_body_when_request_info_section_is_not_present() throws IOException { + final RequestContext currentContext = RequestContext.getCurrentContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setRequestURI("http://foo/bar/v1/_create"); + request.setContent(getContent("postRequestWithoutRequestInfoFromConsumer.json")); + currentContext.setRequest(request); + final String expectedCorrelationId = "someCorrelationId"; + currentContext.set("CORRELATION_ID", expectedCorrelationId); + currentContext.set("USER_INFO", getUser()); + + filter.run(); + + String expectedBody = resources.getFileContents("postRequestWithoutRequestInfoFromConsumer.json"); + assertEquals(expectedBody, IOUtils.toString(currentContext.getRequest().getInputStream())); + } + + private User getUser() { + User mockUser = new User(); + mockUser.setId(30); + mockUser.setUserName("userName"); + mockUser.setName("name"); + mockUser.setMobileNumber("1234567890"); + mockUser.setEmailId("fu@bar.com"); + mockUser.setTenantId("default"); + mockUser.setType("EMPLOYEE"); + Role mockRole1 = new Role(); + mockRole1.setId(1L); + mockRole1.setName("Employee"); + mockRole1.setCode("EMPLOYEE"); + Role mockRole2 = new Role(); + mockRole2.setId(2L); + mockRole2.setName("ULB Operator"); + mockRole2.setCode("ULB"); + List<Role> roles = new ArrayList<>(); + roles.add(mockRole1); + roles.add(mockRole2); + mockUser.setRoles(roles); + return mockUser; + } + + private byte[] getContent(String fileName) { + try { + return IOUtils.toByteArray(IOUtils.toInputStream(resources.getFileContents(fileName))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/model/RequestBodyInspectorTest.java b/src/test/java/org/musti/model/RequestBodyInspectorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2c36f7a92e78c348b565cc2d9a27f443f4343c9b --- /dev/null +++ b/src/test/java/org/musti/model/RequestBodyInspectorTest.java @@ -0,0 +1,90 @@ +/*package org.musti.model; + +import org.junit.Test; +import org.musti.model.RequestBodyInspector; + +import java.util.HashMap; +import java.util.Set; + +import static org.junit.Assert.*; + +public class RequestBodyInspectorTest { + + @Test + public void test_should_return_request_info_when_request_info_container_field_name_has_pascal_case() { + final HashMap<String, Object> requestBody = new HashMap<>(); + final HashMap<Object, Object> requestInfoBody = new HashMap<>(); + requestBody.put("RequestInfo", requestInfoBody); + + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + + assertEquals(requestInfoBody, requestBodyInspector.getRequestInfo()); + } + + @Test + public void test_should_return_request_info_when_request_info_container_field_name_has_camel_case() { + final HashMap<String, Object> requestBody = new HashMap<>(); + final HashMap<Object, Object> requestInfoBody = new HashMap<>(); + requestBody.put("requestInfo", requestInfoBody); + + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + + assertEquals(requestInfoBody, requestBodyInspector.getRequestInfo()); + } + + @Test + public void test_should_return_null_when_request_body_is_empty() { + final HashMap<String, Object> requestBody = new HashMap<>(); + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + + assertNull(requestBodyInspector.getRequestInfo()); + } + + @Test + public void test_should_return_null_when_request_body_does_not_have_request_info() { + final HashMap<String, Object> requestBody = new HashMap<>(); + requestBody.put("someField", new HashMap<>()); + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + + assertNull(requestBodyInspector.getRequestInfo()); + } + + @Test + public void test_should_update_request_info() { + final HashMap<String, Object> requestBody = new HashMap<>(); + final HashMap<Object, Object> originalRequestInfoBody = new HashMap<>(); + requestBody.put("requestInfo", originalRequestInfoBody); + + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + + final HashMap<String, Object> updatedRequestInfo = new HashMap<>(); + updatedRequestInfo.put("foo", "bar"); + + requestBodyInspector.updateRequestInfo(updatedRequestInfo); + + final HashMap<String, Object> actualRequestBody = requestBodyInspector.getRequestBody(); + final HashMap<String, Object> actualRequestInfo = + (HashMap<String, Object>) actualRequestBody.get("requestInfo"); + assertNotNull(actualRequestInfo); + assertTrue(actualRequestInfo.containsKey("foo")); + } + + @Test + public void test_should_not_update_request_body_with_new_request_info_when_original_request_body_does_not_have_request_info_field() { + final HashMap<String, Object> requestBody = new HashMap<>(); + requestBody.put("foo", new HashMap<>()); + + final RequestBodyInspector requestBodyInspector = new RequestBodyInspector(requestBody); + + final HashMap<String, Object> updatedRequestInfo = new HashMap<>(); + updatedRequestInfo.put("userInfo", "user"); + + requestBodyInspector.updateRequestInfo(updatedRequestInfo); + + final HashMap<String, Object> actualRequestBody = requestBodyInspector.getRequestBody(); + final Set<String> keys = actualRequestBody.keySet(); + assertEquals(1, keys.size()); + assertTrue(keys.contains("foo")); + } + +}*/ \ No newline at end of file diff --git a/src/test/java/org/musti/wrapper/CustomRequestWrapperTest.java b/src/test/java/org/musti/wrapper/CustomRequestWrapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e4e5e664bf1718f7aea4c75c4d5096a435322588 --- /dev/null +++ b/src/test/java/org/musti/wrapper/CustomRequestWrapperTest.java @@ -0,0 +1,58 @@ +/*package org.musti.wrapper; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.musti.Resources; +import org.musti.wrapper.CustomRequestWrapper; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.io.IOException; +import java.io.StringReader; + +import static org.junit.Assert.*; + +public class CustomRequestWrapperTest { + private final Resources resources = new Resources(); + + @Test + public void test_should_allow_play_load_to_be_retrieved_multiple_times() throws IOException { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String expectedContent = "foobar"; + request.setContent(IOUtils.toByteArray(new StringReader(expectedContent))); + final CustomRequestWrapper wrapper = new CustomRequestWrapper(request); + + assertEquals(expectedContent, new String(IOUtils.toByteArray(wrapper.getInputStream()))); + assertEquals(expectedContent, new String(IOUtils.toByteArray(wrapper.getInputStream()))); + } + + @Test + public void test_should_allow_play_load_to_set() throws IOException { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String expectedContent = "foobar"; + request.setContent(IOUtils.toByteArray(new StringReader("originalContent"))); + final CustomRequestWrapper wrapper = new CustomRequestWrapper(request); + + wrapper.setPayload(expectedContent); + + assertEquals(expectedContent, new String(IOUtils.toByteArray(wrapper.getInputStream()))); + } + + @Test + public void test_should_return_pay_load_length() throws IOException { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContent(IOUtils.toByteArray(new StringReader("foobar"))); + final CustomRequestWrapper wrapper = new CustomRequestWrapper(request); + + assertEquals(6, wrapper.getContentLength()); + assertEquals(6, wrapper.getContentLengthLong()); + } + + private byte[] getContent(String fileName) { + try { + return IOUtils.toByteArray(IOUtils.toInputStream(resources.getFileContents(fileName))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +}*/ \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..daa4ab8530acba52230ef9f9b54c14b2ff41f224 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,12 @@ +zuul.routes.notification.path=/notification/** +zuul.routes.notification.stripPrefix=false +zuul.routes.notification.url=http://localhost:9999/ + + +zuul.sensitiveHeaders=Cookie,Set-Cookie,x-user-info,auth-token + +musti.auth-service-host=http://localhost:8082/ +musti.auth-service-uri=user/_details?access_token= +musti.user-info-header=x-user-info +musti.open-endpoints-whitelist= +musti.mixed-mode-endpoints-whitelist= \ No newline at end of file diff --git a/src/test/resources/enrichedPostRequest.json b/src/test/resources/enrichedPostRequest.json new file mode 100644 index 0000000000000000000000000000000000000000..3ea6314326c64de8c8272b06894c7908fbec9aff --- /dev/null +++ b/src/test/resources/enrichedPostRequest.json @@ -0,0 +1,28 @@ +{ + "RequestInfo": { + "fu": "bar", + "authToken": "dummy-auth-token", + "userInfo": { + "id": 30, + "userName": "userName", + "name": "name", + "type": "EMPLOYEE", + "mobileNumber": "1234567890", + "emailId": "fu@bar.com", + "tenantId": "default", + "roles": [ + { + "id": 1, + "name": "Employee", + "code": "EMPLOYEE" + }, + { + "id": 2, + "name": "ULB Operator", + "code": "ULB" + } + ] + }, + "correlationId": "someCorrelationId" + } +} \ No newline at end of file diff --git a/src/test/resources/postRequestFromConsumer.json b/src/test/resources/postRequestFromConsumer.json new file mode 100644 index 0000000000000000000000000000000000000000..4dd93c32b9e5777de98974328027cf7e562e5755 --- /dev/null +++ b/src/test/resources/postRequestFromConsumer.json @@ -0,0 +1,6 @@ +{ + "RequestInfo": { + "fu": "bar", + "authToken": "dummy-auth-token" + } +} \ No newline at end of file diff --git a/src/test/resources/postRequestWithCorrelationId.json b/src/test/resources/postRequestWithCorrelationId.json new file mode 100644 index 0000000000000000000000000000000000000000..761e76a64bb5c6a1c3ba1319325184e409a441ca --- /dev/null +++ b/src/test/resources/postRequestWithCorrelationId.json @@ -0,0 +1,7 @@ +{ + "RequestInfo": { + "fu": "bar", + "authToken": "dummy-auth-token", + "correlationId": "someCorrelationId" + } +} \ No newline at end of file diff --git a/src/test/resources/postRequestWithoutRequestInfoFromConsumer.json b/src/test/resources/postRequestWithoutRequestInfoFromConsumer.json new file mode 100644 index 0000000000000000000000000000000000000000..797a4058099711d3f889294c2f74bcef123bb02b --- /dev/null +++ b/src/test/resources/postRequestWithoutRequestInfoFromConsumer.json @@ -0,0 +1,5 @@ +{ + "foo": { + "bar": "bar1" + } +} \ No newline at end of file diff --git a/src/test/resources/userInfoHeader.json b/src/test/resources/userInfoHeader.json new file mode 100644 index 0000000000000000000000000000000000000000..87746e632c7f41089811f6301a596b7211c96e25 --- /dev/null +++ b/src/test/resources/userInfoHeader.json @@ -0,0 +1,21 @@ +{ + "id": 30, + "userName": "userName", + "name": "name", + "type": "EMPLOYEE", + "mobileNumber": "1234567890", + "emailId": "fu@bar.com", + "tenantId": "default", + "roles": [ + { + "id": 1, + "name": "Employee", + "code": "EMPLOYEE" + }, + { + "id": 2, + "name": "ULB Operator", + "code": "ULB" + } + ] +} \ No newline at end of file