package alien4cloud.audit.rest; import io.swagger.annotations.ApiOperation; import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Resource; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; import alien4cloud.audit.AuditService; import alien4cloud.audit.annotation.Audit; import alien4cloud.audit.model.AuditConfiguration; import alien4cloud.audit.model.AuditTrace; import alien4cloud.security.AuthorizationUtil; import alien4cloud.security.model.User; import com.google.common.base.Charsets; import com.google.common.base.Strings; /** * This filter is used to intercept all rest call that need to be audited */ @Component @Slf4j public class AuditLogFilter extends OncePerRequestFilter implements Ordered { private static final ThreadLocal<Pattern> VERSION_DETECTION_PATTERN = new ThreadLocal<Pattern>() { @Override protected Pattern initialValue() { return Pattern.compile("/rest/(latest|[v|V]\\d+)/.+"); } }; private static final String A4C_UI_HEADER = "A4C-Agent"; @Resource private AuditService auditService; @Resource private List<HandlerMapping> handlerMappings; private HandlerMethod getHandlerMethod(HttpServletRequest request) { HandlerExecutionChain handlerChain; try { handlerChain = getHandler(request); } catch (Exception e) { logger.warn("Unable to get handler method", e); return null; } if (handlerChain == null) { return null; } if (!(handlerChain.getHandler() instanceof HandlerMethod)) { return null; } HandlerMethod handlerMethod = (HandlerMethod) handlerChain.getHandler(); return handlerMethod; } private HandlerExecutionChain getHandler(HttpServletRequest request) { for (HandlerMapping handlerMapping : handlerMappings) { try { HandlerExecutionChain handlerChain = handlerMapping.getHandler(request); if (handlerChain != null) { return handlerChain; } } catch (Exception e) { log.debug("Unable to get handler method", e); } } return null; } private ApiOperation getApiDoc(HandlerMethod method) { return method.getMethodAnnotation(ApiOperation.class); } private boolean isRequestContainingJson(HttpServletRequest request) { String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE); return contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON_VALUE); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 10; } private AuditTrace getAuditTrace(HttpServletRequest request, HttpServletResponse response, HandlerMethod method, User user, boolean requestContainsJson) throws IOException { Audit audit = auditService.getAuditAnnotation(method); // trace user info only when he is logged AuditTrace auditTrace = new AuditTrace(); auditTrace.setTimestamp(System.currentTimeMillis()); auditTrace.setAction(auditService.getAuditActionName(method, audit)); ApiOperation apiDoc = getApiDoc(method); if (apiDoc != null) { auditTrace.setActionDescription(apiDoc.value()); } auditTrace.setCategory(auditService.getAuditCategoryName(method, audit)); auditTrace.setUserName(user.getUsername()); auditTrace.setUserFirstName(user.getFirstName()); auditTrace.setUserLastName(user.getLastName()); auditTrace.setUserEmail(user.getEmail()); // request details auditTrace.setPath(request.getRequestURI()); auditTrace.setVersion(getApiVersion(request.getRequestURI())); auditTrace.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT)); auditTrace.setAlien4CloudUI(!Strings.isNullOrEmpty(request.getHeader(A4C_UI_HEADER))); auditTrace.setMethod(request.getMethod()); auditTrace.setRequestParameters(request.getParameterMap()); auditTrace.setSourceIp(request.getRemoteAddr()); // request body if (requestContainsJson) { auditTrace.setRequestBody(StreamUtils.copyToString(request.getInputStream(), Charsets.UTF_8)); } // response details auditTrace.setResponseStatus(response.getStatus()); return auditTrace; } private String getApiVersion(String uri) { Matcher matcher = VERSION_DETECTION_PATTERN.get().matcher(uri); String version = null; if (matcher.matches()) { version = matcher.group(1); } return version; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { AuditConfiguration configuration = auditService.getAuditConfiguration(); if (configuration == null || !configuration.isEnabled()) { filterChain.doFilter(request, response); return; } User user = AuthorizationUtil.getCurrentUser(); if (user == null) { filterChain.doFilter(request, response); return; } HandlerMethod method = getHandlerMethod(request); if (method == null) { filterChain.doFilter(request, response); return; } if (!auditService.isMethodAudited(configuration, method)) { filterChain.doFilter(request, response); return; } boolean requestContainsJson = isRequestContainingJson(request); if (requestContainsJson) { request = new MultiReadHttpServletRequest(request); } try { filterChain.doFilter(request, response); } finally { AuditTrace auditTrace = null; try { auditTrace = getAuditTrace(request, response, method, user, requestContainsJson); } catch (Exception e) { logger.warn("Unable to construct audit trace", e); } if (auditTrace != null) { if (logger.isDebugEnabled()) { logger.debug(auditTrace.toString()); } try { auditService.saveAuditTrace(auditTrace); } catch (Exception e) { logger.warn("Unable to save audit trace " + auditTrace, e); } } } } }