Skip to content

Commit a51042f

Browse files
authored
Merge pull request #148 from Contrast-Security-OSS/AIML-223
AIML-223: Add SESSION_METADATA expansion support for trace queries
2 parents 2ee4240 + 8638748 commit a51042f

File tree

11 files changed

+335
-2
lines changed

11 files changed

+335
-2
lines changed

sdk/src/main/java/com/contrastsecurity/http/TraceFilterForm.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ public enum TraceExpandValue {
6565
REQUEST,
6666
APPLICATION,
6767
SERVERS,
68-
SERVER_ENVIRONMENTS;
68+
SERVER_ENVIRONMENTS,
69+
SESSION_METADATA;
6970

7071
public String toURIString() {
7172
return name().toLowerCase();

sdk/src/main/java/com/contrastsecurity/http/UrlBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ public String getTracesWithBodyUrl(String organizationId, String appId)
207207
return String.format("/ng/%s/traces/%s/filter", organizationId, appId);
208208
}
209209

210+
public String getTracesWithBodyUrl(
211+
String organizationId, String appId, EnumSet<TraceFilterForm.TraceExpandValue> expandValues) {
212+
return String.format(
213+
"/ng/%s/traces/%s/filter%s", organizationId, appId, buildExpand(expandValues));
214+
}
215+
210216
public String getSessionMetadataForApplicationUrl(
211217
String organizationId, String appId, TraceFilterForm form)
212218
throws UnsupportedEncodingException {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.contrastsecurity.models;
2+
3+
/*-
4+
* #%L
5+
* Contrast Java SDK
6+
* %%
7+
* Copyright (C) 2022 - 2025 Contrast Security, Inc.
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* #L%
21+
*/
22+
23+
import com.google.gson.annotations.SerializedName;
24+
25+
/** A single metadata item within session metadata. */
26+
public class MetadataItem {
27+
28+
/**
29+
* Return the value of this metadata item.
30+
*
31+
* @return the metadata value
32+
*/
33+
public String getValue() {
34+
return value;
35+
}
36+
37+
private String value;
38+
39+
/**
40+
* Return the display label for this metadata item. This is the human-readable label shown in the
41+
* Contrast UI.
42+
*
43+
* @return the display label
44+
*/
45+
public String getDisplayLabel() {
46+
return displayLabel;
47+
}
48+
49+
@SerializedName("display_label")
50+
private String displayLabel;
51+
52+
/**
53+
* Return the agent label for this metadata item. This is the internal label used by the Contrast
54+
* agent.
55+
*
56+
* @return the agent label
57+
*/
58+
public String getAgentLabel() {
59+
return agentLabel;
60+
}
61+
62+
@SerializedName("agent_label")
63+
private String agentLabel;
64+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.contrastsecurity.models;
2+
3+
/*-
4+
* #%L
5+
* Contrast Java SDK
6+
* %%
7+
* Copyright (C) 2022 - 2025 Contrast Security, Inc.
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* #L%
21+
*/
22+
23+
import com.google.gson.annotations.SerializedName;
24+
import java.util.List;
25+
26+
/**
27+
* Session metadata associated with a trace. Contains metadata items collected during the session
28+
* when the vulnerability was detected.
29+
*/
30+
public class SessionMetadata {
31+
32+
/**
33+
* Return the session ID for this metadata.
34+
*
35+
* @return the session identifier
36+
*/
37+
public String getSessionId() {
38+
return sessionId;
39+
}
40+
41+
@SerializedName("session_id")
42+
private String sessionId;
43+
44+
/**
45+
* Return the list of metadata items for this session.
46+
*
47+
* @return list of metadata items
48+
*/
49+
public List<MetadataItem> getMetadata() {
50+
return metadata;
51+
}
52+
53+
private List<MetadataItem> metadata;
54+
}

sdk/src/main/java/com/contrastsecurity/models/Trace.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,24 @@ public List<String> getTags() {
343343
@SerializedName("tags")
344344
private List<String> tags;
345345

346+
/**
347+
* Return the session metadata for this trace. Session metadata contains custom key-value pairs
348+
* collected by the Contrast agent during the session when this vulnerability was detected.
349+
*
350+
* <p>Note: This field requires the expand parameter to be set. Use {@link
351+
* com.contrastsecurity.http.TraceFilterForm.TraceExpandValue#SESSION_METADATA} when querying
352+
* traces to populate this field.
353+
*
354+
* @return list of session metadata objects containing session IDs and metadata items, or null if
355+
* not expanded
356+
*/
357+
public List<SessionMetadata> getSessionMetadata() {
358+
return sessionMetadata;
359+
}
360+
361+
@SerializedName("session_metadata")
362+
private List<SessionMetadata> sessionMetadata;
363+
346364
@Override
347365
public boolean equals(Object o) {
348366
if (this == o) return true;

sdk/src/main/java/com/contrastsecurity/models/TraceFilterBody.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ public class TraceFilterBody {
4747
private boolean untracked;
4848
private List<String> urls;
4949
private List<String> vulnTypes;
50+
private String agentSessionId;
5051
}

sdk/src/main/java/com/contrastsecurity/sdk/ContrastSDK.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,10 +800,31 @@ public Traces getTraces(String organizationId, String appId, TraceFilterForm for
800800
*/
801801
public Traces getTraces(String organizationId, String appId, TraceFilterBody filters)
802802
throws IOException, UnauthorizedException {
803+
return getTraces(organizationId, appId, filters, null);
804+
}
805+
806+
/**
807+
* Get the vulnerabilities in the application that match specific metadata filters with expanded
808+
* fields.
809+
*
810+
* @param organizationId the ID of the organization
811+
* @param appId the ID of the application
812+
* @param filters TraceMetadataFilters filters to query on
813+
* @param expand the fields to expand (e.g., SESSION_METADATA, SERVER_ENVIRONMENTS)
814+
* @return Traces object that contains the list of Trace's
815+
* @throws UnauthorizedException if the Contrast account failed to authorize
816+
* @throws IOException if there was a communication problem
817+
*/
818+
public Traces getTraces(
819+
String organizationId,
820+
String appId,
821+
TraceFilterBody filters,
822+
EnumSet<TraceFilterForm.TraceExpandValue> expand)
823+
throws IOException, UnauthorizedException {
803824
try (InputStream is =
804825
makeRequestWithBody(
805826
HttpMethod.POST,
806-
urlBuilder.getTracesWithBodyUrl(organizationId, appId),
827+
urlBuilder.getTracesWithBodyUrl(organizationId, appId, expand),
807828
this.gson.toJson(filters),
808829
MediaType.JSON);
809830
Reader reader = new InputStreamReader(is)) {

sdk/src/test/java/com/contrastsecurity/GsonTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.contrastsecurity.models.Rules;
3737
import com.contrastsecurity.models.SecurityCheck;
3838
import com.contrastsecurity.models.Servers;
39+
import com.contrastsecurity.models.SessionMetadata;
3940
import com.contrastsecurity.models.Story;
4041
import com.contrastsecurity.models.StoryResponse;
4142
import com.contrastsecurity.models.Tags;
@@ -334,4 +335,34 @@ public void testGetTracesWithServerEnvironmentsAndTags() {
334335
assertThat(trace.getTags()).hasSize(3);
335336
assertThat(trace.getTags()).containsExactly("SmartFix Remediated", "Custom Tag", "test-tag");
336337
}
338+
339+
@Test
340+
public void testGetTracesWithSessionMetadata() {
341+
342+
String tracesString =
343+
"{\"count\":1,\"traces\":[{\"title\":\"Test Vulnerability\",\"language\":\"Java\",\"status\":\"Reported\",\"uuid\":\"TEST-1234-5678-ABCD\",\"rule_name\":\"test-rule\",\"severity\":\"High\",\"likelihood\":\"Medium\",\"impact\":\"High\",\"confidence\":\"High\",\"first_time_seen\":1461600923859,\"last_time_seen\":1461601039100,\"category\":\"Injection\",\"platform\":\"Oracle Corporation\",\"total_traces_received\":3,\"visible\":true,\"session_metadata\":[{\"session_id\":\"session-123\",\"metadata\":[{\"value\":\"prod\",\"display_label\":\"Environment\",\"agent_label\":\"env\"},{\"value\":\"user123\",\"display_label\":\"User ID\",\"agent_label\":\"user_id\"}]}]}]}";
344+
345+
Traces traces = gson.fromJson(tracesString, Traces.class);
346+
347+
assertThat(traces).isNotNull();
348+
assertThat(traces.getTraces()).isNotNull();
349+
assertThat(traces.getCount()).isEqualTo(1);
350+
351+
Trace trace = traces.getTraces().get(0);
352+
assertThat(trace.getSessionMetadata()).isNotNull();
353+
assertThat(trace.getSessionMetadata()).hasSize(1);
354+
355+
SessionMetadata sessionMetadata = trace.getSessionMetadata().get(0);
356+
assertThat(sessionMetadata.getSessionId()).isEqualTo("session-123");
357+
assertThat(sessionMetadata.getMetadata()).isNotNull();
358+
assertThat(sessionMetadata.getMetadata()).hasSize(2);
359+
360+
assertThat(sessionMetadata.getMetadata().get(0).getValue()).isEqualTo("prod");
361+
assertThat(sessionMetadata.getMetadata().get(0).getDisplayLabel()).isEqualTo("Environment");
362+
assertThat(sessionMetadata.getMetadata().get(0).getAgentLabel()).isEqualTo("env");
363+
364+
assertThat(sessionMetadata.getMetadata().get(1).getValue()).isEqualTo("user123");
365+
assertThat(sessionMetadata.getMetadata().get(1).getDisplayLabel()).isEqualTo("User ID");
366+
assertThat(sessionMetadata.getMetadata().get(1).getAgentLabel()).isEqualTo("user_id");
367+
}
337368
}

sdk/src/test/java/com/contrastsecurity/http/TraceFilterFormTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ public void toQuery_should_include_multiple_expand_parameters()
8181
assertThat(actual).isEqualTo(expected);
8282
}
8383

84+
@Test
85+
public void toQuery_should_include_session_metadata_expand_parameter()
86+
throws UnsupportedEncodingException {
87+
final String expected = "?expand=session_metadata&tracked=true&untracked=false";
88+
form.setExpand(EnumSet.of(TraceFilterForm.TraceExpandValue.SESSION_METADATA));
89+
final String actual = form.toQuery();
90+
91+
assertThat(actual).isEqualTo(expected);
92+
}
93+
94+
@Test
95+
public void toQuery_should_include_multiple_expand_parameters_with_session_metadata()
96+
throws UnsupportedEncodingException {
97+
final String expected = "?expand=servers,session_metadata&tracked=true&untracked=false";
98+
form.setExpand(
99+
EnumSet.of(
100+
TraceFilterForm.TraceExpandValue.SERVERS,
101+
TraceFilterForm.TraceExpandValue.SESSION_METADATA));
102+
final String actual = form.toQuery();
103+
104+
assertThat(actual).isEqualTo(expected);
105+
}
106+
84107
@Test
85108
public void toQuery_should_url_encode_filterTags_with_special_characters()
86109
throws UnsupportedEncodingException {

sdk/src/test/java/com/contrastsecurity/http/UrlBuilderTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,38 @@ public void testGetTracesWithBodyUrl() throws UnsupportedEncodingException {
153153
.isEqualTo(expectedUrl);
154154
}
155155

156+
@Test
157+
public void testGetTracesWithBodyUrlWithSingleExpand() {
158+
String expectedUrl = "/ng/test-org/traces/test-app/filter?expand=session_metadata";
159+
160+
EnumSet<TraceFilterForm.TraceExpandValue> expand =
161+
EnumSet.of(TraceFilterForm.TraceExpandValue.SESSION_METADATA);
162+
163+
assertThat(urlBuilder.getTracesWithBodyUrl(organizationId, applicationId, expand))
164+
.isEqualTo(expectedUrl);
165+
}
166+
167+
@Test
168+
public void testGetTracesWithBodyUrlWithMultipleExpand() {
169+
String expectedUrl = "/ng/test-org/traces/test-app/filter?expand=servers,session_metadata";
170+
171+
EnumSet<TraceFilterForm.TraceExpandValue> expand =
172+
EnumSet.of(
173+
TraceFilterForm.TraceExpandValue.SESSION_METADATA,
174+
TraceFilterForm.TraceExpandValue.SERVERS);
175+
176+
assertThat(urlBuilder.getTracesWithBodyUrl(organizationId, applicationId, expand))
177+
.isEqualTo(expectedUrl);
178+
}
179+
180+
@Test
181+
public void testGetTracesWithBodyUrlWithNullExpand() {
182+
String expectedUrl = "/ng/test-org/traces/test-app/filter";
183+
184+
assertThat(urlBuilder.getTracesWithBodyUrl(organizationId, applicationId, null))
185+
.isEqualTo(expectedUrl);
186+
}
187+
156188
@Test
157189
public void testSecurityCheckUrl() {
158190
String expectedSecurityCheckUrl = "/ng/test-org/securityChecks";

0 commit comments

Comments
 (0)