Skip to content
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()
}
}