Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

- solved #840 #849

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions audit/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>body</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
144 changes: 128 additions & 16 deletions audit/src/main/java/com/networknt/audit/AuditHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@
import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import io.undertow.server.handlers.Cookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
* This is a simple audit handler that dump most important info per request basis. The following
Expand Down Expand Up @@ -75,13 +74,21 @@ public class AuditHandler implements MiddlewareHandler {
static final String RESPONSE_TIME = "responseTime";
static final String TIMESTAMP = "timestamp";
static final String MASK_KEY = "audit";
static final String REQUEST_BODY_KEY = "requestBody";
static final String RESPONSE_BODY_KEY = "responseBody";
static final String QUERY_PARAMETERS_KEY = "queryParameters";
static final String PATH_PARAMETERS_KEY = "pathParameters";
static final String REQUEST_COOKIES_KEY = "requestCookies";
static final String STATUS_KEY = "Status";
static final String SERVER_CONFIG = "server";
static final String SERVICEID_KEY = "serviceId";

private AuditConfig auditConfig;

private volatile HttpHandler next;

public AuditHandler() {
if(logger.isInfoEnabled()) logger.info("AuditHandler is loaded.");
if (logger.isInfoEnabled()) logger.info("AuditHandler is loaded.");
auditConfig = AuditConfig.load();
}

Expand All @@ -94,38 +101,43 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {

// dump audit info fields according to config
boolean needAuditData = auditInfo != null && auditConfig.hasAuditList();
if(needAuditData) {
if (needAuditData) {
auditFields(auditInfo, auditMap);
}

// dump headers field according to config
if(auditConfig.hasHeaderList()) {
auditHeader(exchange, auditMap);
// dump request header, request body, path parameters, query parameters and request cookies according to config
auditRequest(exchange, auditMap, auditConfig);

// dump serviceId from server.yml
if (auditConfig.hasAuditList() && auditConfig.getAuditList().contains(SERVICEID_KEY)) {
auditServiceId(auditMap);
}

if(auditConfig.isStatusCode() || auditConfig.isResponseTime()) {
if (auditConfig.isStatusCode() || auditConfig.isResponseTime()) {
exchange.addExchangeCompleteListener((exchange1, nextListener) -> {
if (auditConfig.isStatusCode()) {
auditMap.put(STATUS_CODE, exchange1.getStatusCode());
}
if (auditConfig.isResponseTime()) {
auditMap.put(RESPONSE_TIME, System.currentTimeMillis() - start);
}

// add additional fields accumulated during the microservice execution
// according to the config
Map<String, Object> auditInfo1 = exchange.getAttachment(AttachmentConstants.AUDIT_INFO);
if(auditInfo1 != null) {
if(auditConfig.getAuditList() != null && auditConfig.getAuditList().size() > 0) {
for(String name: auditConfig.getAuditList()) {
if (auditInfo1 != null) {
if (auditConfig.getAuditList() != null && auditConfig.getAuditList().size() > 0) {
for (String name : auditConfig.getAuditList()) {
if (name.equals(RESPONSE_BODY_KEY)) {
auditResponseOnError(exchange, auditMap);
}
auditMap.putIfAbsent(name, auditInfo1.get(name));
}
}
}

try {
// audit entries only is it is an error, if auditOnError flag is set
if(auditConfig.isAuditOnError()) {
if (auditConfig.isAuditOnError()) {
if (exchange1.getStatusCode() >= 400)
auditConfig.getAuditFunc().accept(Config.getInstance().getMapper().writeValueAsString(auditMap));
} else {
Expand All @@ -144,7 +156,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
}

private void auditHeader(HttpServerExchange exchange, Map<String, Object> auditMap) {
for(String name: auditConfig.getHeaderList()) {
for (String name : auditConfig.getHeaderList()) {
String value = exchange.getRequestHeaders().getFirst(name);
auditMap.put(name, auditConfig.isMaskEnabled() ? Mask.maskRegex(value, "requestHeader", name) : value);
}
Expand All @@ -155,13 +167,113 @@ protected void next(HttpServerExchange exchange) throws Exception {
}

private void auditFields(Map<String, Object> auditInfo, Map<String, Object> auditMap) {
for(String name: auditConfig.getAuditList()) {
for (String name : auditConfig.getAuditList()) {
Object value = auditInfo.get(name);
boolean needApplyMask = auditConfig.isMaskEnabled() && value instanceof String;
auditMap.put(name, needApplyMask ? Mask.maskRegex((String) value, MASK_KEY, name) : value);
}
}

private void auditRequest(HttpServerExchange exchange, Map<String, Object> auditMap, AuditConfig auditConfig) {
if (auditConfig.hasHeaderList()) {
auditHeader(exchange, auditMap);
}
if (!auditConfig.hasAuditList()) {
return;
}
for (String key : auditConfig.getAuditList()) {
switch (key) {
case REQUEST_BODY_KEY:
auditRequestBody(exchange, auditMap);
break;
case REQUEST_COOKIES_KEY:
auditRequestCookies(exchange, auditMap);
break;
case QUERY_PARAMETERS_KEY:
auditQueryParameters(exchange, auditMap);
break;
case PATH_PARAMETERS_KEY:
auditPathParameters(exchange, auditMap);
break;
}
}
}

// Audit request body automatically if body handler enabled
private void auditRequestBody(HttpServerExchange exchange, Map<String, Object> auditMap) {
// Try to get BodyHandler cached request body string first to prevent unnecessary decoding
String requestBodyString = exchange.getAttachment(AttachmentConstants.REQUEST_BODY_STRING);
if (requestBodyString == null && exchange.getAttachment(AttachmentConstants.REQUEST_BODY) != null) {
requestBodyString = exchange.getAttachment(AttachmentConstants.REQUEST_BODY).toString();
}
// Mask requestBody json string if mask enabled
if (requestBodyString != null) {
auditMap.put(REQUEST_BODY_KEY, auditConfig.isMaskEnabled() ? Mask.maskJson(requestBodyString, REQUEST_BODY_KEY) : requestBodyString);
}
}

// Audit response body only if auditOnError is enabled
private void auditResponseOnError(HttpServerExchange exchange, Map<String, Object> auditMap) {
if (!auditOnError) {
return;
}
String responseBodyString = null;
Map<String, Object> auditInfo = exchange.getAttachment(AttachmentConstants.AUDIT_INFO);
if (auditInfo != null && auditInfo.get(STATUS_KEY) != null) {
responseBodyString = auditInfo.get(STATUS_KEY).toString();
}
if (responseBodyString != null) {
auditMap.put(RESPONSE_BODY_KEY, auditConfig.isMaskEnabled() ? Mask.maskJson(responseBodyString, RESPONSE_BODY_KEY) : responseBodyString);
}
}

// Audit query parameters
private void auditQueryParameters(HttpServerExchange exchange, Map<String, Object> auditMap) {
Map<String, String> res = new HashMap<>();
Map<String, Deque<String>> queryParameters = exchange.getQueryParameters();
if (queryParameters != null && queryParameters.size() > 0) {
for (String query : queryParameters.keySet()) {
String value = queryParameters.get(query).toString();
String mask = auditConfig.isMaskEnabled() ? Mask.maskRegex(value, QUERY_PARAMETERS_KEY, query) : value;
res.put(query, mask);
}
auditMap.put(QUERY_PARAMETERS_KEY, res.toString());
}
}

private void auditPathParameters(HttpServerExchange exchange, Map<String, Object> auditMap) {
Map<String, String> res = new HashMap<>();
Map<String, Deque<String>> pathParameters = exchange.getPathParameters();
if (pathParameters != null && pathParameters.size() > 0) {
for (String name : pathParameters.keySet()) {
String value = pathParameters.get(name).toString();
String mask = auditConfig.isMaskEnabled() ? Mask.maskRegex(value, PATH_PARAMETERS_KEY, name) : value;
res.put(name, mask);
}
auditMap.put(PATH_PARAMETERS_KEY, res.toString());
}
}

private void auditRequestCookies(HttpServerExchange exchange, Map<String, Object> auditMap) {
Map<String, String> res = new HashMap<>();
Map<String, Cookie> cookieMap = exchange.getRequestCookies();
if (cookieMap != null && cookieMap.size() > 0) {
for (String name : cookieMap.keySet()) {
String cookieString = cookieMap.get(name).getValue();
String mask = auditConfig.isMaskEnabled() ? Mask.maskRegex(cookieString, REQUEST_COOKIES_KEY, name) : cookieString;
res.put(name, mask);
}
auditMap.put(REQUEST_COOKIES_KEY, res.toString());
}
}

private void auditServiceId(Map<String, Object> auditMap) {
Map<String, Object> serverConfig = Config.getInstance().getJsonMapConfig(SERVER_CONFIG);
if (serverConfig != null && serverConfig.get(SERVICEID_KEY) != null) {
auditMap.put(SERVICEID_KEY, serverConfig.get(SERVICEID_KEY));
}
}

@Override
public HttpHandler getNext() {
return next;
Expand Down
4 changes: 2 additions & 2 deletions audit/src/main/resources/config/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ audit:
- serviceId

# Request Body, this is optional and must be set by the service in its implementation
- request
- requestBody

# Response payload, this is optional and must be set by the service in its implementation
- response
- responseBody

# Response error status payload
- Status
Loading