All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.newrelic.agent.security.intcodeagent.utils.RestrictionUtility Maven / Gradle / Ivy

package com.newrelic.agent.security.intcodeagent.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.newrelic.agent.security.intcodeagent.exceptions.RestrictionModeException;
import com.newrelic.agent.security.intcodeagent.filelogging.FileLoggerThreadPool;
import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper;
import com.newrelic.api.agent.security.schema.HttpRequest;
import com.newrelic.api.agent.security.schema.policy.RestrictionCriteria;
import com.newrelic.api.agent.security.schema.policy.SkipScan;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.util.*;
import java.util.regex.Pattern;

public class RestrictionUtility {

    public static final String SEPARATOR_CHARS_QUESTION_MARK = "?";
    public static final String SEPARATOR_CHARS_SEMICOLON = ";";
    public static final String FORWARD_SLASH = "/";
    public static final String AND = "&";
    public static final String SEPARATOR_EQUALS = "=";
    public static final String EQUAL = "=";
    public static final String CONTENT_TYPE_TEXT_JSON = "text/json";
    public static final String CONTENT_TYPE_TEXT_XML = "text/xml";
    public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";
    public static final String CONTENT_TYPE_APPLICATION_XML = "application/xml";
    public static final String CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";

    private static final FileLoggerThreadPool logger = FileLoggerThreadPool.getInstance();

    public static boolean skippedApiDetected(SkipScan skipScan, HttpRequest httpRequest) {
        if (skipScan == null) {
            return false;
        }
        if (httpRequest == null) {
            return false;
        }

        if(skipScan.getApiRoutes().isEmpty()) {
            return false;
        }

        for (Pattern pattern : skipScan.getApiRoutes()) {
            if (pattern.matcher(httpRequest.getUrl()).matches()) {
                return true;
            }
        }

        return false;
    }

    public static boolean hasValidAccountId(RestrictionCriteria restrictionCriteria, HttpRequest request) {
        List accountIds = restrictionCriteria.getAccountInfo().getAccountIds();
        if (request == null) {
            return false;
        }
        if(!request.isRequestParametersParsed()){
            parseHttpRequestParameters(request);
        }

        if(restrictionCriteria.getMappingParameters().getHeader().isEnabled()) {
            Set headerValues = getHeaderParameters(restrictionCriteria.getMappingParameters().getHeader().getLocations(), request.getRequestHeaderParameters());
            if(matcher(accountIds, headerValues)){
                return true;
            }
        }
        if(restrictionCriteria.getMappingParameters().getQuery().isEnabled()) {
            Set queryValues = getQueryString(restrictionCriteria.getMappingParameters().getHeader().getLocations(), request.getQueryParameters());
            if(matcher(accountIds, queryValues)){
                return true;
            }
        }
        if(restrictionCriteria.getMappingParameters().getPath().isEnabled()) {
            if(matcher(accountIds, request.getPathParameters())){
                return true;
            }
        }
        if(restrictionCriteria.getMappingParameters().getBody().isEnabled()) {
            Set bodyValues = getBodyParameters(restrictionCriteria.getMappingParameters().getBody().getLocations(), request.getRequestBodyParameters());
            return matcher(accountIds, bodyValues);
        }

        return false;
    }

    private static Set getBodyParameters(List accountIds, Map> requestBodyParameters) {
        if (requestBodyParameters == null || requestBodyParameters.isEmpty()) {
            return Collections.emptySet();
        }

        Set values = new HashSet<>();
        for (String accountId : accountIds) {
            String lowerCaseAccountId = accountId.toLowerCase();
            if(requestBodyParameters.containsKey(lowerCaseAccountId)) {
                values.addAll(requestBodyParameters.get(lowerCaseAccountId));
            }
        }

        return values;
    }

    private static Set getHeaderParameters(List accountIds, Map> requestHeaderParameters) {
        if (requestHeaderParameters == null || requestHeaderParameters.isEmpty()) {
            return Collections.emptySet();
        }
        Set values = new HashSet<>();
        for (String accountId : accountIds) {
            String lowerCaseAccountId = accountId.toLowerCase();
            if(requestHeaderParameters.containsKey(lowerCaseAccountId)) {
                values.addAll(requestHeaderParameters.get(lowerCaseAccountId));
            }
        }
        return values;
    }

    private static Set getQueryString(List accountIds, Map> queryParameters) {
        if(queryParameters == null || queryParameters.isEmpty()) {
            return Collections.emptySet();
        }
        Set values = new HashSet<>();
        for (String accountId : accountIds) {
            String lowerCaseAccountId = accountId.toLowerCase();
            if(queryParameters.containsKey(lowerCaseAccountId)) {
                values.addAll(queryParameters.get(lowerCaseAccountId));
            }
        }
        return values;
    }

    private static boolean matcher(List accountIds, Set values) {
        for (String accountId : accountIds) {
            if(values == null || values.isEmpty() || StringUtils.isBlank(accountId)) {
                continue;
            }
            String lowerCaseAccountId = accountId.toLowerCase();
            boolean contains = values.contains(lowerCaseAccountId);
            if(contains){
                return true;
            }
        }
        return false;
    }

    private static void parseHttpRequestParameters(HttpRequest request) {
        request.setPathParameters(parsePathParameters(StringUtils.substringBefore(request.getUrl(),
                SEPARATOR_CHARS_QUESTION_MARK)));
        request.setQueryParameters(parseQueryParameters(request.getUrl()));
        request.setRequestHeaderParameters(parseRequestHeaders(request.getHeaders()));
        try {
            request.setRequestBodyParameters(parseRequestBody(request.getBody(), request.getContentType(), request.getRequestBodyParameters()));
        } catch (RestrictionModeException e) {
            logger.log(LogLevel.WARNING, String.format("Request Body parsing failed reason %s", e.getMessage()), RestrictionUtility.class.getName());
        }
        request.setRequestBodyParameters(parseRequestParameterMap(request.getParameterMap(), request.getRequestBodyParameters()));
        request.setRequestParametersParsed(true);
    }

    private static Map> parseRequestParameterMap(Map parameterMap, Map> requestBodyParameters) {
        if(parameterMap == null) {
            return requestBodyParameters;
        }
        if(requestBodyParameters == null) {
            requestBodyParameters = new HashMap<>();
        }

        for (Map.Entry entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();
            Set valuesList = new HashSet<>();
            for (String value : values) {
                valuesList.add(StringUtils.lowerCase(value));
            }
            if(requestBodyParameters.containsKey(key)){
                requestBodyParameters.get(key).addAll(valuesList);
            } else {
                requestBodyParameters.put(key, valuesList);
            }
        }
        return requestBodyParameters;
    }

    private static Map> parseRequestBody(StringBuilder body, String contentType, Map> requestBodyParameters) throws RestrictionModeException {
        if(StringUtils.isBlank(body.toString())) {
            return requestBodyParameters;
        }

        if(requestBodyParameters == null) {
            requestBodyParameters = new HashMap<>();
        }

        switch (contentType) {
            case CONTENT_TYPE_APPLICATION_JSON:
            case CONTENT_TYPE_TEXT_JSON:
                requestBodyParameters.putAll(parseJsonRequestBody(body.toString()));
                break;
            case CONTENT_TYPE_APPLICATION_XML:
            case CONTENT_TYPE_TEXT_XML:
                requestBodyParameters.putAll(parseXmlRequestBody(body.toString()));
                break;
            case CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED:
                requestBodyParameters.putAll(queryParamKeyValueGenerator(body.toString(),new HashMap<>()));
                break;
            default:
                break;
        }
        return requestBodyParameters;

    }

    private static Map> parseXmlRequestBody(String body) throws RestrictionModeException {
        //write logic to xml parsing
        Map> requestBodyParameters = new HashMap<>();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setExpandEntityReferences(false);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new InputSource(new StringReader(body)));
            document.getDocumentElement().normalize();
            Element root = document.getDocumentElement();
            parseXmlNode(root, StringUtils.EMPTY, requestBodyParameters);
        } catch (Exception e) {
            logger.log(LogLevel.FINER, String.format("JSON Request Body parsing failed for %s : reason %s", body, e.getMessage()), RestrictionUtility.class.getName());
            throw new RestrictionModeException(String.format("XML Request Body parsing failed : reason %s", e.getMessage()), e);
        }
        return requestBodyParameters;
    }

    private static void parseXmlNode(Node node, String baseKey, Map> requestBodyParameters) {
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            Element element = (Element) node;
            NodeList children = element.getChildNodes();
            String key = baseKey.isEmpty() ? element.getTagName() : baseKey + "." + element.getTagName();
            if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
                String value = children.item(0).getTextContent().trim();
                if (!value.isEmpty()) {
                    requestBodyParameters.computeIfAbsent(key, k -> new HashSet<>()).add(value);
                }
            } else {
                for (int i = 0; i < children.getLength(); i++) {
                    parseXmlNode(children.item(i), key, requestBodyParameters);
                }
            }
        }
    }

    private static Map> parseJsonRequestBody(String body) throws RestrictionModeException {
        JsonNode node;
        ObjectMapper mapper = new ObjectMapper();
        try {
            node = mapper.readValue(body, JsonNode.class);
            Map> requestBodyParameters = new HashMap<>();
            return parseJsonNode(node, StringUtils.EMPTY, requestBodyParameters);
        } catch (JsonProcessingException e) {
            logger.log(LogLevel.FINER, String.format("JSON Request Body parsing failed for %s : reason %s", body, e.getMessage()), RestrictionUtility.class.getName());
            throw new RestrictionModeException(String.format("JSON Request Body parsing failed : reason %s", e.getMessage())+ e.getMessage(), e);
        }
    }

    private static Map> parseJsonNode(JsonNode node, String baseKey, Map> requestBodyParameters) {
        if (node.isObject()) {
            Iterator> iterator = node.fields();
            while (iterator.hasNext()) {
                Map.Entry entry = iterator.next();
                String key = entry.getKey();
                String base = getBase(baseKey, key);
                JsonNode value = entry.getValue();
                if(value.isContainerNode()){
                    parseJsonNode(value, base, requestBodyParameters);
                } else if (StringUtils.isNotBlank(value.asText())) {
                    if(!requestBodyParameters.containsKey(base)){
                        requestBodyParameters.put(base, new HashSet<>());
                    }
                    requestBodyParameters.get(base).add(value.asText());
                }
            }
        } else if (node.isArray()) {
            ArrayNode arrayNode = (ArrayNode) node;
            for (int i = 0; i < arrayNode.size(); i++) {
                JsonNode jsonNode = arrayNode.get(i);
                String base = getBase(baseKey, i);
                if(jsonNode.isContainerNode()){
                    parseJsonNode(jsonNode, base, requestBodyParameters);
                } else if (StringUtils.isNotBlank(jsonNode.asText())) {
                    if(!requestBodyParameters.containsKey(base)){
                        requestBodyParameters.put(base, new HashSet<>());
                    }
                    requestBodyParameters.get(base).add(jsonNode.asText());
                }
            }
        }
        return requestBodyParameters;
    }

    private static String getBase(String baseKey, String key) {
        if(StringUtils.isBlank(baseKey)){
            return key;
        }
        return baseKey + "." + key;
    }

    private static String getBase(String baseKey, int index) {
        if(StringUtils.isBlank(baseKey)){
            return "[]";
        }
        return String.format("%s[]", baseKey);
    }

    private static Map> parseRequestHeaders(Map headers) {
        Map> requestHeaderParameters = new HashMap<>();
        for (Map.Entry header : headers.entrySet()) {
            String key = header.getKey();
            String value = header.getValue();
            putHeaderParameter(key, value, requestHeaderParameters);
            if (StringUtils.containsAny(value, SEPARATOR_CHARS_SEMICOLON, EQUAL)) {
                String[] headerKeyValues = value.split(SEPARATOR_CHARS_SEMICOLON);
                for (int i = 0; i < headerKeyValues.length; i++) {
                    if (StringUtils.contains(headerKeyValues[i], EQUAL)
                            && !StringUtils.endsWith(headerKeyValues[i], EQUAL)) {
                        String headerKey = StringUtils.substringBefore(headerKeyValues[i], EQUAL).trim();
                        String headerValue = StringUtils.substringAfter(headerKeyValues[i], EQUAL).trim();
                        putHeaderParameter(key, headerKey, headerValue, requestHeaderParameters);
                    } else {
                        putHeaderParameter(key, headerKeyValues[i], requestHeaderParameters);
                    }
                }
            }
        }
        return requestHeaderParameters;
    }

    private static void putHeaderParameter(String baseKey, String headerKey, String headerValue, Map> requestHeaderParameters) {
        if(StringUtils.isBlank(baseKey)){
            putHeaderParameter(headerKey, headerValue, requestHeaderParameters);
        }
        putHeaderParameter(String.format("%s.%s", baseKey, headerKey), headerValue, requestHeaderParameters);
    }

    private static void putHeaderParameter(String key, String value, Map> requestHeaderParameters) {
        Set headerValues = requestHeaderParameters.get(key);
        if (headerValues == null) {
            headerValues = new HashSet<>();
        }
        headerValues.add(StringUtils.lowerCase(value));
        headerValues.add(StringUtils.lowerCase(ServletHelper.urlDecode(value)));
        requestHeaderParameters.put(StringUtils.lowerCase(key), headerValues);
    }

    private static Map> parseQueryParameters(String url) {
        Map> queryParameters = new HashMap<>();
        String query = StringUtils.substringAfter(url, SEPARATOR_CHARS_QUESTION_MARK);
        if (StringUtils.isNotBlank(query)) {
            queryParamKeyValueGenerator(query, queryParameters);
        } else {
            query = StringUtils.substringAfter(url, SEPARATOR_CHARS_SEMICOLON);
            if (StringUtils.isNotBlank(query)) {
                queryParamKeyValueGenerator(query, queryParameters);
            }
        }
        return queryParameters;
    }

    private static Map> queryParamKeyValueGenerator(String query, Map> queryParameters) {
        String[] queryParams = StringUtils.split(query, AND);
        for (String queryParam : queryParams) {
            String key, value;
            key = StringUtils.substringBefore(queryParam, SEPARATOR_EQUALS);
            value = StringUtils.substringAfter(queryParam, SEPARATOR_EQUALS);
            Set values = new HashSet<>();
            values.add(StringUtils.lowerCase(value));
            values.add(StringUtils.lowerCase(ServletHelper.urlDecode(value)));
            queryParameters.put(StringUtils.lowerCase(key), values);
        }
        return queryParameters;
    }

    private static Set parsePathParameters(String uri) {
        Set pathParameters = new HashSet<>();
        String requestPath = StringUtils.substringBefore(uri,
                SEPARATOR_CHARS_SEMICOLON);
        if(StringUtils.isNotBlank(requestPath)) {
            String[] pathVariables = StringUtils.split(requestPath, FORWARD_SLASH);
            for (String pathVariable : pathVariables) {
                pathParameters.add(StringUtils.lowerCase(pathVariable));
                pathParameters.add(StringUtils.lowerCase(ServletHelper.urlDecode(pathVariable)));
            }
        }
        return pathParameters;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy