Skip to content

Extract RestEasy json body response schemas #9015

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

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
2 changes: 2 additions & 0 deletions dd-smoke-tests/resteasy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
implementation group: 'org.jboss.resteasy', name: 'resteasy-undertow', version:'3.1.0.Final'
implementation group: 'org.jboss.resteasy', name: 'resteasy-cdi', version:'3.1.0.Final'
implementation group: 'org.jboss.weld.servlet', name: 'weld-servlet', version: '2.4.8.Final'
implementation group: 'org.jboss.resteasy', name: 'resteasy-jackson2-provider', version: '3.1.0.Final'

implementation group: 'javax.el', name: 'javax.el-api', version:'3.0.0'

Expand All @@ -24,6 +25,7 @@ dependencies {

testImplementation project(':dd-smoke-tests')
testImplementation(testFixtures(project(":dd-smoke-tests:iast-util")))
testImplementation project(':dd-smoke-tests:appsec')
}

tasks.withType(Test).configureEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package smoketest.resteasy;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.jboss.resteasy.plugins.providers.StringTextStar;

public class App extends Application {

private Set<Object> singletons = new HashSet<Object>();

public App() {
singletons.add(new Resource());
singletons.add(new StringTextStar()); // Writer for String
singletons.add(new JacksonJsonProvider()); // Writer for json
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package smoketest.resteasy;

import java.util.List;

public class RequestBody {
private List<KeyValue> main;
private Object nullable;

public List<KeyValue> getMain() {
return main;
}

public void setMain(List<KeyValue> main) {
this.main = main;
}

public Object getNullable() {
return nullable;
}

public void setNullable(Object nullable) {
this.nullable = nullable;
}

public static class KeyValue {
private String key;
private Double value;

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public Double getValue() {
return value;
}

public void setValue(Double value) {
this.value = value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
Expand Down Expand Up @@ -94,4 +96,18 @@ public Response responseLocation(@QueryParam("param") String param) throws URISy
public Response getCookie() throws SQLException {
return Response.ok().cookie(new NewCookie("user-id", "7")).build();
}

@Path("/api_security/response")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response apiSecurityResponse(RequestBody input) {
return Response.ok(input).build();
}

@GET
@Path("/api_security/sampling/{i}")
public Response apiSecuritySamplingWithStatus(@PathParam("i") int i) {
return Response.status(i).header("content-type", "text/plain").entity("Hello!\n").build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package smoketest

import datadog.smoketest.appsec.AbstractAppSecServerSmokeTest
import datadog.trace.api.Platform
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response

import java.util.zip.GZIPInputStream


class ResteasyAppsecSmokeTest extends AbstractAppSecServerSmokeTest {

@Override
ProcessBuilder createProcessBuilder() {
String jarPath = System.getProperty("datadog.smoketest.resteasy.jar.path")

List<String> command = new ArrayList<>()
command.add(javaPath())
command.addAll(defaultJavaProperties)
command.addAll(defaultAppSecProperties)
if (Platform.isJavaVersionAtLeast(17)) {
command.addAll(["--add-opens", "java.base/java.lang=ALL-UNNAMED"])
}
command.addAll(["-jar", jarPath, Integer.toString(httpPort)])
ProcessBuilder processBuilder = new ProcessBuilder(command)
processBuilder.directory(new File(buildDirectory))
}

void 'API Security samples only one request per endpoint'() {
given:
def url = "http://localhost:${httpPort}/hello/api_security/sampling/200?test=value"
def request = new Request.Builder()
.url(url)
.addHeader('X-My-Header', "value")
.get()
.build()

when:
List<Response> responses = (1..3).collect {
client.newCall(request).execute()
}

then:
responses.each {
assert it.code() == 200
}
waitForTraceCount(3)
def spans = rootSpans.toList().toSorted { it.span.duration }
spans.size() == 3
def sampledSpans = spans.findAll {
it.meta.keySet().any {
it.startsWith('_dd.appsec.s.req.')
}
}
sampledSpans.size() == 1
def span = sampledSpans[0]
span.meta.containsKey('_dd.appsec.s.req.query')
span.meta.containsKey('_dd.appsec.s.req.params')
span.meta.containsKey('_dd.appsec.s.req.headers')
}


void 'test response schema extraction'() {
given:
def url = "http://localhost:${httpPort}/hello/api_security/response"
def body = [
"main" : [["key": "id001", "value": 1345.67], ["value": 1567.89, "key": "id002"]],
"nullable": null,
]
def request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.get('application/json'), JsonOutput.toJson(body)))
.build()

when:
final response = client.newCall(request).execute()
waitForTraceCount(1)

then:
response.code() == 200
def span = rootSpans.first()
span.meta.containsKey('_dd.appsec.s.res.headers')
span.meta.containsKey('_dd.appsec.s.res.body')
final schema = new JsonSlurper().parse(unzip(span.meta.get('_dd.appsec.s.res.body')))
assert schema == [["main": [[[["key": [8], "value": [16]]]], ["len": 2]], "nullable": [1]]]
}

private static byte[] unzip(final String text) {
final inflaterStream = new GZIPInputStream(new ByteArrayInputStream(text.decodeBase64()))
return inflaterStream.getBytes()
}
}