Skip to content

Commit 7fa2f9b

Browse files
committed
appsec/resteasy: capture arbitrary body objects too
1 parent 9ef1537 commit 7fa2f9b

3 files changed

Lines changed: 120 additions & 5 deletions

File tree

dd-java-agent/instrumentation/resteasy-appsec/resteasy-appsec.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies {
2222
compileOnly group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.0.0.Final'
2323

2424
testFixturesApi group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.0.0.Final'
25+
testFixturesApi group: 'org.jboss.resteasy', name: 'resteasy-jackson-provider', version: '3.0.0.Final'
2526
testFixturesImplementation project(':dd-java-agent:testing'), {
2627
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
2728
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package datadog.trace.instrumentation.resteasy;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameEndsWith;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
5+
import static datadog.trace.api.gateway.Events.EVENTS;
6+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
7+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
8+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
9+
10+
import com.google.auto.service.AutoService;
11+
import datadog.trace.agent.tooling.Instrumenter;
12+
import datadog.trace.api.function.BiFunction;
13+
import datadog.trace.api.gateway.CallbackProvider;
14+
import datadog.trace.api.gateway.Flow;
15+
import datadog.trace.api.gateway.RequestContext;
16+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
17+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
18+
import net.bytebuddy.asm.Advice;
19+
20+
@AutoService(Instrumenter.class)
21+
public class MessageBodyReaderInvocationInstrumentation extends Instrumenter.AppSec
22+
implements Instrumenter.ForKnownTypes {
23+
24+
public MessageBodyReaderInvocationInstrumentation() {
25+
super("resteasy");
26+
}
27+
28+
@Override
29+
public String[] knownMatchingTypes() {
30+
return new String[] {"org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext"};
31+
}
32+
33+
@Override
34+
public void adviceTransformations(AdviceTransformation transformation) {
35+
transformation.applyAdvice(
36+
named("readFrom")
37+
.and(takesArguments(1))
38+
.and(takesArgument(0, nameEndsWith(".MessageBodyReader"))),
39+
MessageBodyReaderInvocationInstrumentation.class.getName()
40+
+ "$AbstractReaderInterceptorAdvice");
41+
}
42+
43+
public static class AbstractReaderInterceptorAdvice {
44+
@Advice.OnMethodExit(suppress = Throwable.class)
45+
static void after(@Advice.Return final Object ret) {
46+
if (ret == null) {
47+
return;
48+
}
49+
AgentSpan agentSpan = activeSpan();
50+
if (agentSpan == null) {
51+
return;
52+
}
53+
54+
CallbackProvider cbp = AgentTracer.get().instrumentationGateway();
55+
BiFunction<RequestContext<Object>, Object, Flow<Void>> callback =
56+
cbp.getCallback(EVENTS.requestBodyProcessed());
57+
RequestContext<Object> requestContext = agentSpan.getRequestContext();
58+
if (requestContext == null || callback == null) {
59+
return;
60+
}
61+
callback.apply(requestContext, ret);
62+
}
63+
}
64+
}

dd-java-agent/instrumentation/resteasy-appsec/src/testFixtures/groovy/datadog/trace/instrumentation/resteasy/AbstractResteasyAppsecTest.groovy

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,22 @@ import okhttp3.HttpUrl
1313
import okhttp3.OkHttpClient
1414
import okhttp3.Request
1515
import okhttp3.RequestBody
16+
import org.codehaus.jackson.map.ObjectMapper
1617
import spock.lang.Shared
1718

1819
import javax.ws.rs.Consumes
1920
import javax.ws.rs.FormParam
2021
import javax.ws.rs.POST
2122
import javax.ws.rs.Path
23+
import javax.ws.rs.Produces
2224
import javax.ws.rs.core.Application
2325
import javax.ws.rs.core.MediaType
2426
import javax.ws.rs.core.Response
27+
import javax.ws.rs.ext.ContextResolver
2528
import java.util.concurrent.TimeUnit
2629

30+
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_JSON
2731
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_URLENCODED
28-
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan
2932

3033
abstract class AbstractResteasyAppsecTest extends AgentTestRunner {
3134

@@ -88,17 +91,64 @@ abstract class AbstractResteasyAppsecTest extends AgentTestRunner {
8891
}
8992
}
9093

91-
@Path("/body-urlencoded")
92-
static class BodyUrlEncodedResource {
94+
def 'test instrumentation gateway json request body'() {
95+
setup:
96+
def request = request(
97+
BODY_JSON, 'POST',
98+
RequestBody.create(okhttp3.MediaType.get('application/json'), '{"a":"x"}\n'))
99+
.build()
100+
def response = client.newCall(request).execute()
101+
102+
expect:
103+
response.body().charStream().text == '{"a":"x"}'
104+
105+
when:
106+
TEST_WRITER.waitForTraces(1)
107+
108+
then:
109+
TEST_WRITER.get(0).any {
110+
it.getTag('request.body.converted') == '[a:[x]]'
111+
}
112+
}
113+
114+
@Path("/")
115+
static class BodyResource {
93116
@POST
117+
@Path("body-urlencoded")
94118
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
95119
Response bodyUrlencoded(@FormParam("a") List<String> a) {
96120
Response.status(BODY_URLENCODED.status).entity([a: a] as String).build()
97121
}
122+
123+
@POST
124+
@Path("body-json")
125+
@Consumes(MediaType.APPLICATION_JSON)
126+
@Produces(MediaType.APPLICATION_JSON)
127+
Response bodyJson(ClassForBodyToBeConvertedTo obj) {
128+
Response.status(BODY_JSON.status).entity(obj).build()
129+
}
130+
}
131+
132+
static class ClassForBodyToBeConvertedTo {
133+
String a
134+
135+
@Override
136+
String toString() {
137+
"[a:[$a]]"
138+
}
139+
}
140+
141+
/* avoid the jackson provider looking for jaxb annotations */
142+
static class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
143+
@Override
144+
ObjectMapper getContext(Class<?> type) {
145+
new ObjectMapper()
146+
}
98147
}
99148

100149
static class TestJaxRsApplication extends Application {
101-
private static final RESOURCE = new AbstractResteasyAppsecTest.BodyUrlEncodedResource()
102-
final Set<Object> singletons = [RESOURCE] as Set
150+
private static final RESOURCE = new BodyResource()
151+
private static final OBJECT_MAPPER_CONTEXT_RESOLVER = new ObjectMapperContextResolver()
152+
final Set<Object> singletons = [RESOURCE, OBJECT_MAPPER_CONTEXT_RESOLVER] as Set
103153
}
104154
}

0 commit comments

Comments
 (0)