diff --git a/instrumentation-security/jersey-2.16/build.gradle b/instrumentation-security/jersey-2.16/build.gradle new file mode 100644 index 000000000..d10b64445 --- /dev/null +++ b/instrumentation-security/jersey-2.16/build.gradle @@ -0,0 +1,24 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("org.glassfish.jersey.core:jersey-server:2.16") + +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.jersey-2-16' } +} + +// org.glassfish.jersey.core 2.28 version starts pulling in jakarata jar named dependencies. +// Version 3.0.0-M1 starts pulling in jakarata with renamed jar and packages +verifyInstrumentation { + passesOnly 'org.glassfish.jersey.core:jersey-server:[2.16,3.0)' + exclude 'org.glassfish.jersey.core:jersey-server:[2.0-m05-2,2.0)' + excludeRegex '.*-(M|RC)[0-9]*' +} + +site { + title 'Jersey' + type 'Framework' +} \ No newline at end of file diff --git a/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ApplicationHandler_Handler.java b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ApplicationHandler_Handler.java new file mode 100644 index 000000000..09844d08b --- /dev/null +++ b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ApplicationHandler_Handler.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.glassfish.jersey.server.ContainerRequest; + +import java.io.OutputStream; + +@Weave(type = MatchType.ExactClass, originalName = "org.glassfish.jersey.server.ApplicationHandler") +public abstract class ApplicationHandler_Handler { + + public void handle(ContainerRequest requestContext) { + boolean isRequestLockAcquired = false; + try { + if (requestContext != null) { + isRequestLockAcquired = HttpRequestHelper.acquireRequestLockIfPossible(); + if (isRequestLockAcquired) { + HttpRequestHelper.preprocessSecurityHook(requestContext); + HttpRequestHelper.registerUserLevelCode("JERSEY"); + } + } + Weaver.callOriginal(); + } finally { + if(isRequestLockAcquired){ + HttpRequestHelper.releaseRequestLock(); + } + } + } + +} diff --git a/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java new file mode 100644 index 000000000..aa015732b --- /dev/null +++ b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java @@ -0,0 +1,53 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weaver; +import org.glassfish.jersey.message.internal.OutboundJaxrsResponse; + +import com.newrelic.api.agent.weaver.Weave; +import org.glassfish.jersey.message.internal.OutboundMessageContext; +import org.glassfish.jersey.server.ContainerRequest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper.SERVLET_GET_IS_OPERATION_LOCK; + +@Weave(type = MatchType.ExactClass, originalName = "org.glassfish.jersey.server.ContainerResponse") +public abstract class ContainerResponse_Instrumentation { + + ContainerResponse_Instrumentation(final ContainerRequest requestContext, final OutboundJaxrsResponse response) { + if(response != null && response.getContext() != null && response.getContext().hasEntity()){ + Object responseObject = response.getContext().getEntity(); + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseBody(new StringBuilder(String.valueOf(responseObject))); + } + } + + public abstract OutboundMessageContext getWrappedMessageContext(); + + public void close() { + boolean isLockAcquired = false; + try { + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); + if(isLockAcquired) { + HttpRequestHelper.postProcessSecurityHook(this.getClass().getName(), getWrappedMessageContext()); + } + Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + GenericHelper.releaseLock(SERVLET_GET_IS_OPERATION_LOCK); + } + } + } +} diff --git a/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/EntityInputStream_Instrumentation.java b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/EntityInputStream_Instrumentation.java new file mode 100644 index 000000000..b82213cb4 --- /dev/null +++ b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/EntityInputStream_Instrumentation.java @@ -0,0 +1,33 @@ +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.io.InputStream; + +import static com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper.SERVLET_GET_IS_OPERATION_LOCK; + +@Weave(type = MatchType.ExactClass, originalName = "org.glassfish.jersey.message.internal.EntityInputStream") +public class EntityInputStream_Instrumentation { + + public final InputStream getWrappedStream() { + InputStream retunObject; + boolean isLockAcquired = false; + try { + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); + retunObject = Weaver.callOriginal(); + if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && retunObject != null) { + HttpRequestHelper.registerInputStreamHashIfNeeded(retunObject.hashCode()); + } + } finally { + if(isLockAcquired) { + GenericHelper.releaseLock(SERVLET_GET_IS_OPERATION_LOCK); + } + } + return retunObject; + } + +} diff --git a/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/HttpRequestHelper.java b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/HttpRequestHelper.java new file mode 100644 index 000000000..3d9ffe1c1 --- /dev/null +++ b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/HttpRequestHelper.java @@ -0,0 +1,304 @@ +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.LowSeverityHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import com.newrelic.api.agent.security.schema.policy.AgentPolicy; +import org.glassfish.jersey.internal.PropertiesDelegate; +import org.glassfish.jersey.message.internal.OutboundMessageContext; +import org.glassfish.jersey.server.ContainerRequest; + +import javax.ws.rs.core.MultivaluedMap; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +public class HttpRequestHelper { + + private static final String X_FORWARDED_FOR = "x-forwarded-for"; + private static final String EMPTY = ""; + public static final String QUESTION_MARK = "?"; + public static final String CONTAINER_RESPONSE_METHOD_NAME = "ContainerResponse"; + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "REQUEST_LOCK-"; + private static final String WILDCARD = "*"; + private static final String SEPARATOR = "/"; + public static final String HEADER_SEPARATOR = ";"; + public static final String GRIZZLY_REQUEST_PROPERTIES_DELEGATE = "GRIZZLY_REQUEST_PROPERTIES_DELEGATE"; + public static final String GRIZZLY_REQUEST = "GRIZZLY_REQUEST"; + public static final String ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_GRIZZLY_REQUEST_PROPERTIES_DELEGATE = "org.glassfish.jersey.grizzly2.httpserver.GrizzlyRequestPropertiesDelegate"; + public static final String ORG_GLASSFISH_GRIZZLY_HTTP_SERVER_REQUEST = "org.glassfish.grizzly.http.server.Request"; + public static final String FIELD_REQUEST = "request"; + public static final String METHOD_GET_REMOTE_ADDR = "getRemoteAddr"; + public static final String METHOD_GET_REMOTE_PORT = "getRemotePort"; + public static final String METHOD_GET_LOCAL_PORT = "getLocalPort"; + public static final String METHOD_GET_SCHEME = "getScheme"; + public static final String METHOD_GET_CONTENT_TYPE = "getContentType"; + public static final String ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_TRACING_AWARE_PROPERTIES_DELEGATE = "org.glassfish.jersey.message.internal.TracingAwarePropertiesDelegate"; + public static final String TRACING_AWARE_PROPERTIES_DELEGATE = "TRACING_AWARE_PROPERTIES_DELEGATE"; + public static final String FIELD_PROPERTIES_DELEGATE = "propertiesDelegate"; + + private static final String REQUEST_INPUTSTREAM_HASH = "REQUEST_INPUTSTREAM_HASH"; + + public static Class grizzlyRequestPropertiesDelegateKlass = null; + + public static Class grizzlyRequest = null; + + public static Class tracingAwarePropertiesDelegateKlass = null; + + public static void preprocessSecurityHook(ContainerRequest requestContext) { + try { + if (!NewRelicSecurity.isHookProcessingActive()) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + + HttpRequest securityRequest = securityMetaData.getRequest(); + if (securityRequest.isRequestParsed()) { + return; + } + AgentMetaData securityAgentMetaData = securityMetaData.getMetaData(); + securityRequest.setMethod(requestContext.getMethod()); + HttpRequestHelper.processPropertiesDelegate(requestContext.getPropertiesDelegate(), securityRequest); + + if (securityRequest.getClientIP() != null && !securityRequest.getClientIP().trim().isEmpty()) { + securityAgentMetaData.getIps().add(securityRequest.getClientIP()); + } + HttpRequestHelper.processHttpRequestHeader(requestContext, securityRequest); + + securityMetaData.setTracingHeaderValue(HttpRequestHelper.getTraceHeader(securityRequest.getHeaders())); + securityRequest.setUrl(requestContext.getRequestUri().toString()); + + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + securityRequest.setRequestParsed(true); + } catch (Throwable ignored) { + } + } + + public static void postProcessSecurityHook(String className, OutboundMessageContext wrappedMessageContext) { + try { + if (!NewRelicSecurity.isHookProcessingActive() + ) { + return; + } + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setHeaders(getHeaders(wrappedMessageContext)); + //Add request URI hash to low severity event filter + LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); + + RXSSOperation rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(), + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse(), + className, HttpRequestHelper.CONTAINER_RESPONSE_METHOD_NAME); + NewRelicSecurity.getAgent().registerOperation(rxssOperation); + ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles()); + } catch (Throwable e) { + if(e instanceof NewRelicSecurityException){ + e.printStackTrace(); + throw e; + } + } + } + + private static Map getHeaders(OutboundMessageContext outboundMessageContext) { + Map headers = new HashMap<>(); + if(outboundMessageContext == null || outboundMessageContext.getHeaders() == null){ + return headers; + } + for (String key : outboundMessageContext.getStringHeaders().keySet()) { + headers.put(key, outboundMessageContext.getHeaderString(key)); + if(StringUtils.equalsAny(StringUtils.lowerCase(key), "content-type", "contenttype")){ + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseContentType(outboundMessageContext.getHeaderString(key)); + } + } + return headers; + + } + + public static void processHttpRequestHeader(ContainerRequest request, HttpRequest securityRequest){ + MultivaluedMap headers = request.getHeaders(); + for (Map.Entry> header : headers.entrySet()) { + boolean takeNextValue = false; + String headerKey = header.getKey(); + String headerFullValue = getHeaderValue(header.getValue()); + if(headerKey != null){ + headerKey = headerKey.toLowerCase(); + } + AgentPolicy agentPolicy = NewRelicSecurity.getAgent().getCurrentPolicy(); + AgentMetaData agentMetaData = NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData(); + if (agentPolicy != null + && agentPolicy.getProtectionMode().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getIpDetectViaXFF() + && X_FORWARDED_FOR.equals(headerKey)) { + takeNextValue = true; + } else if (ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID.equals(headerKey)) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent().getSecurityMetaData() + .setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerFullValue)); + } else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerFullValue); + } + + for (String headerValue : header.getValue()) { + if (headerValue != null && !headerValue.trim().isEmpty()) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true); + securityRequest.setClientIP(headerValue); + agentMetaData.getIps() + .add(securityRequest.getClientIP()); + securityRequest.setClientPort(EMPTY); + takeNextValue = false; + } + } + } + securityRequest.getHeaders().put(headerKey, headerFullValue); + } + } + + private static String getHeaderValue(List values) { + StringBuilder finalValue = new StringBuilder(); + for (String value : values) { + if (finalValue.length() > 0) { + finalValue.append(HEADER_SEPARATOR); + } + finalValue.append(value); + } + return finalValue.toString(); + } + + public static String getTraceHeader(Map headers) { + String data = EMPTY; + if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER); + if (data == null || data.trim().isEmpty()) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()); + } + } + return data; + } + + public static boolean isRequestLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireRequestLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && + !isRequestLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseRequestLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return NR_SEC_CUSTOM_ATTRIB_NAME + Thread.currentThread().getId(); + } + + public static void processPropertiesDelegate(PropertiesDelegate propertiesDelegate, HttpRequest securityRequest) { + if(StringUtils.equals(propertiesDelegate.getClass().getName(), ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_GRIZZLY_REQUEST_PROPERTIES_DELEGATE)){ + try { + Class grizzlyRequestPropertiesDelegateKlass = getClass(GRIZZLY_REQUEST_PROPERTIES_DELEGATE); + Field requestField = grizzlyRequestPropertiesDelegateKlass.getDeclaredField(FIELD_REQUEST); + requestField.setAccessible(true); + Object requestObject = requestField.get(propertiesDelegate); + Class requestClass = getClass(GRIZZLY_REQUEST); + Method getRemoteAddr = requestClass.getDeclaredMethod(METHOD_GET_REMOTE_ADDR); + Method getRemotePort = requestClass.getDeclaredMethod(METHOD_GET_REMOTE_PORT); + Method getLocalPort = requestClass.getDeclaredMethod(METHOD_GET_LOCAL_PORT); + Method getScheme = requestClass.getDeclaredMethod(METHOD_GET_SCHEME); + Method getContentType = requestClass.getDeclaredMethod(METHOD_GET_CONTENT_TYPE); + securityRequest.setClientIP(String.valueOf(getRemoteAddr.invoke(requestObject))); + securityRequest.setClientPort(String.valueOf(getRemotePort.invoke(requestObject))); + securityRequest.setServerPort((int) getLocalPort.invoke(requestObject)); + securityRequest.setProtocol((String) getScheme.invoke(requestObject)); + securityRequest.setContentType((String) getContentType.invoke(requestObject)); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + } + + } else if (StringUtils.equals(propertiesDelegate.getClass().getName(), ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_TRACING_AWARE_PROPERTIES_DELEGATE)){ + try { + Class tracingAwarePropertiesDelegateKlass = getClass(TRACING_AWARE_PROPERTIES_DELEGATE); + Field propertiesDelegateField = tracingAwarePropertiesDelegateKlass.getDeclaredField(FIELD_PROPERTIES_DELEGATE); + propertiesDelegateField.setAccessible(true); + Object propertiesDelegateObject = propertiesDelegateField.get(propertiesDelegate); + processPropertiesDelegate((PropertiesDelegate) propertiesDelegateObject, securityRequest); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + } + } else { +// System.out.println(propertiesDelegate + " : " + propertiesDelegate.getClass().getName()); + } + } + + private static Class getClass(String klassName) throws ClassNotFoundException { + switch (klassName) { + case GRIZZLY_REQUEST_PROPERTIES_DELEGATE: + if (grizzlyRequestPropertiesDelegateKlass == null) { + grizzlyRequestPropertiesDelegateKlass = Class.forName(ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_GRIZZLY_REQUEST_PROPERTIES_DELEGATE); + } + return grizzlyRequestPropertiesDelegateKlass; + case GRIZZLY_REQUEST: + if (grizzlyRequest == null) { + grizzlyRequest = Class.forName(ORG_GLASSFISH_GRIZZLY_HTTP_SERVER_REQUEST); + } + return grizzlyRequest; + case TRACING_AWARE_PROPERTIES_DELEGATE: + if (tracingAwarePropertiesDelegateKlass == null) { + tracingAwarePropertiesDelegateKlass = Class.forName(ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_TRACING_AWARE_PROPERTIES_DELEGATE); + } + return tracingAwarePropertiesDelegateKlass; + default: + throw new ClassNotFoundException(klassName); + } + } + + public static void registerInputStreamHashIfNeeded(int inputStreamHash){ + try { + Set hashSet = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(REQUEST_INPUTSTREAM_HASH, Set.class); + if(hashSet == null){ + hashSet = new HashSet<>(); + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(REQUEST_INPUTSTREAM_HASH, hashSet); + } + hashSet.add(inputStreamHash); + } catch (Throwable ignored) {} + } + + public static void registerUserLevelCode(String frameworkName) { + try { + if (!NewRelicSecurity.isHookProcessingActive() || NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() + ) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + if (!securityMetaData.getMetaData().isUserLevelServiceMethodEncountered(frameworkName)) { + securityMetaData.getMetaData().setUserLevelServiceMethodEncountered(true); + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + } + } catch (Throwable ignored) { + } + } +} diff --git a/instrumentation-security/jersey-2/build.gradle b/instrumentation-security/jersey-2/build.gradle new file mode 100644 index 000000000..6b8971009 --- /dev/null +++ b/instrumentation-security/jersey-2/build.gradle @@ -0,0 +1,24 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("org.glassfish.jersey.core:jersey-server:2.0") + +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.jersey-2' } +} + +// org.glassfish.jersey.core 2.28 version starts pulling in jakarata jar named dependencies. +// Version 3.0.0-M1 starts pulling in jakarata with renamed jar and packages +verifyInstrumentation { + passesOnly 'org.glassfish.jersey.core:jersey-server:[2.0,2.16)' + exclude 'org.glassfish.jersey.core:jersey-server:[2.0-m05-2,2.0)' + excludeRegex '.*-(M|RC)[0-9]*' +} + +site { + title 'Jersey' + type 'Framework' +} \ No newline at end of file diff --git a/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ApplicationHandler_Handler.java b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ApplicationHandler_Handler.java new file mode 100644 index 000000000..09844d08b --- /dev/null +++ b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ApplicationHandler_Handler.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.glassfish.jersey.server.ContainerRequest; + +import java.io.OutputStream; + +@Weave(type = MatchType.ExactClass, originalName = "org.glassfish.jersey.server.ApplicationHandler") +public abstract class ApplicationHandler_Handler { + + public void handle(ContainerRequest requestContext) { + boolean isRequestLockAcquired = false; + try { + if (requestContext != null) { + isRequestLockAcquired = HttpRequestHelper.acquireRequestLockIfPossible(); + if (isRequestLockAcquired) { + HttpRequestHelper.preprocessSecurityHook(requestContext); + HttpRequestHelper.registerUserLevelCode("JERSEY"); + } + } + Weaver.callOriginal(); + } finally { + if(isRequestLockAcquired){ + HttpRequestHelper.releaseRequestLock(); + } + } + } + +} diff --git a/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java new file mode 100644 index 000000000..cf33416ab --- /dev/null +++ b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weaver; +import org.glassfish.jersey.message.internal.OutboundJaxrsResponse; + +import com.newrelic.api.agent.weaver.Weave; +import org.glassfish.jersey.message.internal.OutboundMessageContext; +import org.glassfish.jersey.server.ContainerRequest; + +import static com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper.SERVLET_GET_IS_OPERATION_LOCK; + +@Weave(type = MatchType.ExactClass, originalName = "org.glassfish.jersey.server.ContainerResponse") +public abstract class ContainerResponse_Instrumentation { + + ContainerResponse_Instrumentation(final ContainerRequest requestContext, final OutboundJaxrsResponse response) { + if(response != null && response.getContext() != null && response.getContext().hasEntity()){ + Object responseObject = response.getContext().getEntity(); + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseBody(new StringBuilder(String.valueOf(responseObject))); + } + } + + public abstract OutboundMessageContext getWrappedMessageContext(); + + public void close() { + boolean isLockAcquired = false; + try { + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); + if(isLockAcquired) { + HttpRequestHelper.postProcessSecurityHook(this.getClass().getName(), getWrappedMessageContext()); + } + Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + GenericHelper.releaseLock(SERVLET_GET_IS_OPERATION_LOCK); + } + } + } +} diff --git a/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/EntityInputStream_Instrumentation.java b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/EntityInputStream_Instrumentation.java new file mode 100644 index 000000000..c4c553d3f --- /dev/null +++ b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/EntityInputStream_Instrumentation.java @@ -0,0 +1,33 @@ +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.io.InputStream; + +import static com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper.SERVLET_GET_IS_OPERATION_LOCK; + +@Weave(type = MatchType.ExactClass, originalName = "org.glassfish.jersey.message.internal.EntityInputStream") +class EntityInputStream_Instrumentation { + + protected final InputStream getWrappedStream() { + InputStream retunObject; + boolean isLockAcquired = false; + try { + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); + retunObject = Weaver.callOriginal(); + if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && retunObject != null) { + HttpRequestHelper.registerInputStreamHashIfNeeded(retunObject.hashCode()); + } + } finally { + if(isLockAcquired) { + GenericHelper.releaseLock(SERVLET_GET_IS_OPERATION_LOCK); + } + } + return retunObject; + } + +} diff --git a/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/HttpRequestHelper.java b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/HttpRequestHelper.java new file mode 100644 index 000000000..3b56fc9ec --- /dev/null +++ b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/HttpRequestHelper.java @@ -0,0 +1,306 @@ +package com.newrelic.agent.security.instrumentation.jersey2; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.LowSeverityHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import com.newrelic.api.agent.security.schema.policy.AgentPolicy; +import org.glassfish.jersey.internal.PropertiesDelegate; +import org.glassfish.jersey.message.internal.OutboundMessageContext; +import org.glassfish.jersey.server.ContainerRequest; + +import javax.ws.rs.core.MultivaluedMap; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +public class HttpRequestHelper { + + private static final String X_FORWARDED_FOR = "x-forwarded-for"; + private static final String EMPTY = ""; + public static final String QUESTION_MARK = "?"; + public static final String CONTAINER_RESPONSE_METHOD_NAME = "ContainerResponse"; + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "REQUEST_LOCK-"; + private static final String WILDCARD = "*"; + private static final String SEPARATOR = "/"; + public static final String HEADER_SEPARATOR = ";"; + public static final String GRIZZLY_REQUEST_PROPERTIES_DELEGATE = "GRIZZLY_REQUEST_PROPERTIES_DELEGATE"; + public static final String GRIZZLY_REQUEST = "GRIZZLY_REQUEST"; + public static final String ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_GRIZZLY_REQUEST_PROPERTIES_DELEGATE = "org.glassfish.jersey.grizzly2.httpserver.GrizzlyRequestPropertiesDelegate"; + public static final String ORG_GLASSFISH_GRIZZLY_HTTP_SERVER_REQUEST = "org.glassfish.grizzly.http.server.Request"; + public static final String FIELD_REQUEST = "request"; + public static final String METHOD_GET_REMOTE_ADDR = "getRemoteAddr"; + public static final String METHOD_GET_REMOTE_PORT = "getRemotePort"; + public static final String METHOD_GET_LOCAL_PORT = "getLocalPort"; + public static final String METHOD_GET_SCHEME = "getScheme"; + public static final String METHOD_GET_CONTENT_TYPE = "getContentType"; + public static final String ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_TRACING_AWARE_PROPERTIES_DELEGATE = "org.glassfish.jersey.message.internal.TracingAwarePropertiesDelegate"; + public static final String TRACING_AWARE_PROPERTIES_DELEGATE = "TRACING_AWARE_PROPERTIES_DELEGATE"; + public static final String FIELD_PROPERTIES_DELEGATE = "propertiesDelegate"; + + private static final String REQUEST_INPUTSTREAM_HASH = "REQUEST_INPUTSTREAM_HASH"; + public static final String CONTENT_TYPE = "content-type"; + public static final String HEADER_CONTENT_TYPE = "contenttype"; + + public static Class grizzlyRequestPropertiesDelegateKlass = null; + + public static Class grizzlyRequest = null; + + public static Class tracingAwarePropertiesDelegateKlass = null; + + public static void preprocessSecurityHook(ContainerRequest requestContext) { + try { + if (!NewRelicSecurity.isHookProcessingActive()) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + + HttpRequest securityRequest = securityMetaData.getRequest(); + if (securityRequest.isRequestParsed()) { + return; + } + AgentMetaData securityAgentMetaData = securityMetaData.getMetaData(); + securityRequest.setMethod(requestContext.getMethod()); + HttpRequestHelper.processPropertiesDelegate(requestContext.getPropertiesDelegate(), securityRequest); + + if (securityRequest.getClientIP() != null && !securityRequest.getClientIP().trim().isEmpty()) { + securityAgentMetaData.getIps().add(securityRequest.getClientIP()); + } + HttpRequestHelper.processHttpRequestHeader(requestContext, securityRequest); + + securityMetaData.setTracingHeaderValue(HttpRequestHelper.getTraceHeader(securityRequest.getHeaders())); + securityRequest.setUrl(requestContext.getRequestUri().toString()); + + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + securityRequest.setRequestParsed(true); + } catch (Throwable ignored) { + } + } + + public static void postProcessSecurityHook(String className, OutboundMessageContext wrappedMessageContext) { + try { + if (!NewRelicSecurity.isHookProcessingActive() + ) { + return; + } + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setHeaders(getHeaders(wrappedMessageContext)); + //Add request URI hash to low severity event filter + LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); + + RXSSOperation rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(), + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse(), + className, HttpRequestHelper.CONTAINER_RESPONSE_METHOD_NAME); + NewRelicSecurity.getAgent().registerOperation(rxssOperation); + ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles()); + } catch (Throwable e) { + if(e instanceof NewRelicSecurityException){ + e.printStackTrace(); + throw e; + } + } + } + + private static Map getHeaders(OutboundMessageContext outboundMessageContext) { + Map headers = new HashMap<>(); + if(outboundMessageContext == null || outboundMessageContext.getHeaders() == null){ + return headers; + } + for (String key : outboundMessageContext.getStringHeaders().keySet()) { + headers.put(key, outboundMessageContext.getHeaderString(key)); + if(StringUtils.equalsAny(StringUtils.lowerCase(key), CONTENT_TYPE, HEADER_CONTENT_TYPE)){ + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseContentType(outboundMessageContext.getHeaderString(key)); + } + } + return headers; + + } + + public static void processHttpRequestHeader(ContainerRequest request, HttpRequest securityRequest){ + MultivaluedMap headers = request.getHeaders(); + for (Map.Entry> header : headers.entrySet()) { + boolean takeNextValue = false; + String headerKey = header.getKey(); + String headerFullValue = getHeaderValue(header.getValue()); + if(headerKey != null){ + headerKey = headerKey.toLowerCase(); + } + AgentPolicy agentPolicy = NewRelicSecurity.getAgent().getCurrentPolicy(); + AgentMetaData agentMetaData = NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData(); + if (agentPolicy != null + && agentPolicy.getProtectionMode().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getIpDetectViaXFF() + && X_FORWARDED_FOR.equals(headerKey)) { + takeNextValue = true; + } else if (ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID.equals(headerKey)) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent().getSecurityMetaData() + .setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerFullValue)); + } else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerFullValue); + } + + for (String headerValue : header.getValue()) { + if (headerValue != null && !headerValue.trim().isEmpty()) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true); + securityRequest.setClientIP(headerValue); + agentMetaData.getIps() + .add(securityRequest.getClientIP()); + securityRequest.setClientPort(EMPTY); + takeNextValue = false; + } + } + } + securityRequest.getHeaders().put(headerKey, headerFullValue); + } + } + + private static String getHeaderValue(List values) { + StringBuilder finalValue = new StringBuilder(); + for (String value : values) { + if (finalValue.length() > 0) { + finalValue.append(HEADER_SEPARATOR); + } + finalValue.append(value); + } + return finalValue.toString(); + } + + public static String getTraceHeader(Map headers) { + String data = EMPTY; + if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER); + if (data == null || data.trim().isEmpty()) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()); + } + } + return data; + } + + public static boolean isRequestLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireRequestLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && + !isRequestLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseRequestLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return NR_SEC_CUSTOM_ATTRIB_NAME + Thread.currentThread().getId(); + } + + public static void processPropertiesDelegate(PropertiesDelegate propertiesDelegate, HttpRequest securityRequest) { + if(StringUtils.equals(propertiesDelegate.getClass().getName(), ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_GRIZZLY_REQUEST_PROPERTIES_DELEGATE)){ + try { + Class grizzlyRequestPropertiesDelegateKlass = getClass(GRIZZLY_REQUEST_PROPERTIES_DELEGATE); + Field requestField = grizzlyRequestPropertiesDelegateKlass.getDeclaredField(FIELD_REQUEST); + requestField.setAccessible(true); + Object requestObject = requestField.get(propertiesDelegate); + Class requestClass = getClass(GRIZZLY_REQUEST); + Method getRemoteAddr = requestClass.getDeclaredMethod(METHOD_GET_REMOTE_ADDR); + Method getRemotePort = requestClass.getDeclaredMethod(METHOD_GET_REMOTE_PORT); + Method getLocalPort = requestClass.getDeclaredMethod(METHOD_GET_LOCAL_PORT); + Method getScheme = requestClass.getDeclaredMethod(METHOD_GET_SCHEME); + Method getContentType = requestClass.getDeclaredMethod(METHOD_GET_CONTENT_TYPE); + securityRequest.setClientIP(String.valueOf(getRemoteAddr.invoke(requestObject))); + securityRequest.setClientPort(String.valueOf(getRemotePort.invoke(requestObject))); + securityRequest.setServerPort((int) getLocalPort.invoke(requestObject)); + securityRequest.setProtocol((String) getScheme.invoke(requestObject)); + securityRequest.setContentType((String) getContentType.invoke(requestObject)); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + } + + } else if (StringUtils.equals(propertiesDelegate.getClass().getName(), ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_TRACING_AWARE_PROPERTIES_DELEGATE)){ + try { + Class tracingAwarePropertiesDelegateKlass = getClass(TRACING_AWARE_PROPERTIES_DELEGATE); + Field propertiesDelegateField = tracingAwarePropertiesDelegateKlass.getDeclaredField(FIELD_PROPERTIES_DELEGATE); + propertiesDelegateField.setAccessible(true); + Object propertiesDelegateObject = propertiesDelegateField.get(propertiesDelegate); + processPropertiesDelegate((PropertiesDelegate) propertiesDelegateObject, securityRequest); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + } + } else { +// System.out.println(propertiesDelegate + " : " + propertiesDelegate.getClass().getName()); + } + } + + private static Class getClass(String klassName) throws ClassNotFoundException { + switch (klassName) { + case GRIZZLY_REQUEST_PROPERTIES_DELEGATE: + if (grizzlyRequestPropertiesDelegateKlass == null) { + grizzlyRequestPropertiesDelegateKlass = Class.forName(ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_GRIZZLY_REQUEST_PROPERTIES_DELEGATE); + } + return grizzlyRequestPropertiesDelegateKlass; + case GRIZZLY_REQUEST: + if (grizzlyRequest == null) { + grizzlyRequest = Class.forName(ORG_GLASSFISH_GRIZZLY_HTTP_SERVER_REQUEST); + } + return grizzlyRequest; + case TRACING_AWARE_PROPERTIES_DELEGATE: + if (tracingAwarePropertiesDelegateKlass == null) { + tracingAwarePropertiesDelegateKlass = Class.forName(ORG_GLASSFISH_JERSEY_GRIZZLY_2_HTTPSERVER_TRACING_AWARE_PROPERTIES_DELEGATE); + } + return tracingAwarePropertiesDelegateKlass; + default: + throw new ClassNotFoundException(klassName); + } + } + + public static void registerInputStreamHashIfNeeded(int inputStreamHash){ + try { + Set hashSet = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(REQUEST_INPUTSTREAM_HASH, Set.class); + if(hashSet == null){ + hashSet = new HashSet<>(); + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(REQUEST_INPUTSTREAM_HASH, hashSet); + } + hashSet.add(inputStreamHash); + } catch (Throwable ignored) {} + } + + public static void registerUserLevelCode(String frameworkName) { + try { + if (!NewRelicSecurity.isHookProcessingActive() || NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() + ) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + if (!securityMetaData.getMetaData().isUserLevelServiceMethodEncountered(frameworkName)) { + securityMetaData.getMetaData().setUserLevelServiceMethodEncountered(true); + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + } + } catch (Throwable ignored) { + } + } +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java index b1da8fc98..08cfc96a5 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Objects; import java.util.StringJoiner; +import java.util.Locale; public class StringUtils { public static final String EMPTY = ""; @@ -450,7 +451,7 @@ private static boolean endsWith(final CharSequence str, final CharSequence suffi * @return whether the region matched */ private static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, - final CharSequence substring, final int start, final int length) { + final CharSequence substring, final int start, final int length) { if (cs instanceof String && substring instanceof String) { return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); } @@ -1014,6 +1015,31 @@ public static boolean startsWithIgnoreCase(final CharSequence str, final CharSeq return startsWith(str, prefix, true); } + /** + *

Converts a String to lower case as per {@link String#toLowerCase()}.

+ * + *

A {@code null} input String returns {@code null}.

+ * + *
+     * StringUtils.lowerCase(null)  = null
+     * StringUtils.lowerCase("")    = ""
+     * StringUtils.lowerCase("aBc") = "abc"
+     * 
+ * + *

Note: As described in the documentation for {@link String#toLowerCase()}, + * the result of this method is affected by the current locale. + * should be used with a specific locale (e.g. {@link Locale#ENGLISH}).

+ * + * @param str the String to lower case, may be null + * @return the lower cased String, {@code null} if null String input + */ + public static String lowerCase(final String str) { + if (str == null) { + return null; + } + return str.toLowerCase(); + } + /** *

Joins the elements of the provided array into a single String * containing the provided list of elements.

diff --git a/settings.gradle b/settings.gradle index 371118e29..e8e126cd6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -153,6 +153,8 @@ include 'instrumentation:commons-jxpath' //include 'instrumentation:apache-wicket-6.4' //include 'instrumentation:apache-wicket-7.0' //include 'instrumentation:apache-wicket-8.0' +include 'instrumentation:jersey-2' +include 'instrumentation:jersey-2.16' include 'instrumentation:spring-data-redis' include 'instrumentation:spymemcached-2.12.0' include 'instrumentation:jetty-12'