Skip to content
Open
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
45 changes: 44 additions & 1 deletion sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@
<localTeamServerUrl>http://localhost:19080/Contrast/api</localTeamServerUrl>
<versions.junit-jupiter>5.7.1</versions.junit-jupiter>
<versions.auto-value>1.8.2</versions.auto-value>
<versions.lombok>1.18.18</versions.lombok>
<versions.lombok>1.18.30</versions.lombok>
<copyrightYear>2022</copyrightYear>
<versions.openapi-generator>7.9.0</versions.openapi-generator>
</properties>

<dependencies>
Expand Down Expand Up @@ -109,6 +110,18 @@
<version>${versions.auto-value}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<optional>true</optional>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -219,6 +232,36 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${versions.openapi-generator}</version>
<executions>
<execution>
<id>generate-contrast-graph-models</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/openapi/contrast-graph.yaml</inputSpec>
<generatorName>java</generatorName>
<library>okhttp-gson</library>
<generateApis>false</generateApis>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<generateSupportingFiles>false</generateSupportingFiles>
<modelPackage>com.contrastsecurity.sdk.graph</modelPackage>
<configOptions>
<openApiNullable>false</openApiNullable>
<hideGenerationTimestamp>true</hideGenerationTimestamp>
<java8>true</java8>
<useJakartaEe>false</useJakartaEe>
<dateLibrary>string</dateLibrary>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
Expand Down
40 changes: 40 additions & 0 deletions sdk/src/main/java/com/contrastsecurity/sdk/ContrastSDK.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
import com.contrastsecurity.models.VulnerabilityTrend;
import com.contrastsecurity.models.dtm.ApplicationCreateRequest;
import com.contrastsecurity.models.dtm.AttestationCreateRequest;
import com.contrastsecurity.sdk.graph.ContrastGraphApi;
import com.contrastsecurity.sdk.graph.ContrastGraphApiImpl;
import com.contrastsecurity.sdk.internal.GsonFactory;
import com.contrastsecurity.sdk.scan.ScanManager;
import com.contrastsecurity.sdk.scan.ScanManagerImpl;
Expand Down Expand Up @@ -221,6 +223,10 @@ public ScanManager scan(final String organizationId) {
return new ScanManagerImpl(this, gson, organizationId);
}

public ContrastGraphApi graphApi() {
return new ContrastGraphApiImpl(this, this.gson);
}

/**
* Gets the global properties from TeamServer.
*
Expand Down Expand Up @@ -1587,6 +1593,40 @@ public InputStream makeRequest(HttpMethod method, String path)
return makeRequestWithResponse(method, path).is;
}

public InputStream makeRequestToUrl(HttpMethod method, String url)
throws IOException, UnauthorizedException {
HttpURLConnection connection = makeConnection(url, method.toString());
int rc = connection.getResponseCode();
if (rc >= HttpURLConnection.HTTP_BAD_REQUEST) {
throw HttpResponseException.fromConnection(
connection, "Received unexpected status code from Contrast");
}
return connection.getInputStream();
}

public InputStream makeRequestWithBodyToUrl(
HttpMethod method, String url, String body, MediaType mediaType)
throws IOException, UnauthorizedException {
HttpURLConnection connection = makeConnection(url, method.toString());
if (mediaType != null
&& body != null
&& (method.equals(HttpMethod.PUT)
|| method.equals(HttpMethod.POST)
|| method.equals(HttpMethod.DELETE))) {
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", mediaType.getType());
try (OutputStream os = connection.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}
}
int rc = connection.getResponseCode();
if (rc >= HttpURLConnection.HTTP_BAD_REQUEST) {
throw HttpResponseException.fromConnection(
connection, "Received unexpected status code from Contrast");
}
return connection.getInputStream();
}

public MakeRequestResponse makeRequestWithResponse(HttpMethod method, String path)
throws IOException, UnauthorizedException {
String url = restApiURL + path;
Expand Down
39 changes: 39 additions & 0 deletions sdk/src/main/java/com/contrastsecurity/sdk/JSON.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*-
Comment thread
johnament marked this conversation as resolved.
* #%L
* Contrast Java SDK
* %%
* Copyright (C) 2022 - 2026 Contrast Security, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

package com.contrastsecurity.sdk;

import com.contrastsecurity.sdk.internal.GsonFactory;
import com.google.gson.Gson;

/**
* Gson utility required by generated graph model classes (openapi-generator okhttp-gson output).
*
* <p>Delegates to {@link GsonFactory} so generated models share the same Gson configuration as the
* rest of the SDK.
*/
public final class JSON {

private JSON() {}

public static Gson getGson() {
return GsonFactory.create();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*-
* #%L
* Contrast Java SDK
* %%
* Copyright (C) 2022 - 2026 Contrast Security, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

package com.contrastsecurity.sdk.graph;

import java.io.IOException;

public interface ContrastGraphApi {

ContrastGraphResponse searchGraph(String organizationId, ContrastGraphRequest request)
throws IOException;

ContrastGraphResponse getIncidentGraph(String organizationId, String incidentId)
throws IOException;

FacetsResponse getFacets(String organizationId, String filterName, RequestFilters filters)
throws IOException;

ApplicationLibrariesResponse getApplicationLibraries(
String organizationId,
String applicationId,
String agentReportingInstanceId,
ApplicationLibrariesRequest request)
throws IOException;

LibraryDetailsResponse getApplicationLibraryDetails(
String organizationId, String applicationId, String libraryHash) throws IOException;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: possibility to use a helper method to reuse code in this class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm ok with this as is, I think any refactor to not duplicate code affects readability

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did end up cleaning up the calls as it was ignoring proxy settings the way written.

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*-
* #%L
* Contrast Java SDK
* %%
* Copyright (C) 2022 - 2026 Contrast Security, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package com.contrastsecurity.sdk.graph;

import com.contrastsecurity.exceptions.ServerResponseException;
import com.contrastsecurity.http.HttpMethod;
import com.contrastsecurity.http.MediaType;
import com.contrastsecurity.sdk.ContrastSDK;
import com.contrastsecurity.sdk.internal.URIBuilder;
import com.contrastsecurity.utils.ContrastSDKUtils;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;

public final class ContrastGraphApiImpl implements ContrastGraphApi {

private final ContrastSDK contrast;
private final Gson gson;
private final String graphApiBase;

public ContrastGraphApiImpl(final ContrastSDK contrast, final Gson gson) {
this.contrast = contrast;
this.gson = gson;
this.graphApiBase = ContrastSDKUtils.getServerUrl(contrast.getRestApiURL()) + "/api";
}

@Override
public ContrastGraphResponse searchGraph(
final String organizationId, final ContrastGraphRequest request) throws IOException {
final String url =
graphApiBase
+ new URIBuilder()
.appendPathSegments("v2", "organizations", organizationId, "contrast-graph")
.toURIString();
return post(url, gson.toJson(request), ContrastGraphResponse.class);
}

@Override
public ContrastGraphResponse getIncidentGraph(
final String organizationId, final String incidentId) throws IOException {
final String url =
graphApiBase
+ new URIBuilder()
.appendPathSegments(
"v2",
"organizations",
organizationId,
"contrast-graph",
"incidents",
incidentId)
.toURIString();
return get(url, ContrastGraphResponse.class);
}

@Override
public FacetsResponse getFacets(
final String organizationId, final String filterName, final RequestFilters filters)
throws IOException {
final String url =
graphApiBase
+ new URIBuilder()
.appendPathSegments(
"v2", "organizations", organizationId, "contrast-graph", "facets", filterName)
.toURIString();
return post(url, gson.toJson(filters), FacetsResponse.class);
}

@Override
public ApplicationLibrariesResponse getApplicationLibraries(
final String organizationId,
final String applicationId,
final String agentReportingInstanceId,
final ApplicationLibrariesRequest request)
throws IOException {
final String url =
graphApiBase
+ new URIBuilder()
.appendPathSegments(
"v2",
"organizations",
organizationId,
"contrast-graph",
"applications",
applicationId,
"libraries")
.appendQueryParam("agentReportingInstanceId", agentReportingInstanceId)
.toURIString();
return post(
url, request != null ? gson.toJson(request) : null, ApplicationLibrariesResponse.class);
}

@Override
public LibraryDetailsResponse getApplicationLibraryDetails(
final String organizationId, final String applicationId, final String libraryHash)
throws IOException {
final String url =
graphApiBase
+ new URIBuilder()
.appendPathSegments(
"v2",
"organizations",
organizationId,
"contrast-graph",
"applications",
applicationId,
"libraries",
libraryHash)
.toURIString();
return get(url, LibraryDetailsResponse.class);
}

private <T> T get(final String url, final Class<T> type) throws IOException {
try (InputStream is = contrast.makeRequestToUrl(HttpMethod.GET, url)) {
return parse(is, type);
}
}

private <T> T post(final String url, final String body, final Class<T> type) throws IOException {
try (InputStream is =
contrast.makeRequestWithBodyToUrl(HttpMethod.POST, url, body, MediaType.JSON)) {
return parse(is, type);
}
}

private <T> T parse(final InputStream is, final Class<T> type) throws IOException {
try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
return gson.fromJson(reader, type);
} catch (JsonParseException e) {
throw new ServerResponseException("Failed to parse Contrast Graph API response", e);
}
}
}
13 changes: 13 additions & 0 deletions sdk/src/main/java/com/contrastsecurity/utils/ContrastSDKUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ public static String ensureApi(String url) {
return url;
}

public static String getServerUrl(String url) {
if (url != null) {
if (url.endsWith("/Contrast/api")) {
return url.substring(0, url.length() - "/Contrast/api".length());
} else if (url.endsWith("/Contrast/")) {
return url.substring(0, url.length() - "/Contrast/".length());
} else if (url.endsWith("/Contrast")) {
return url.substring(0, url.length() - "/Contrast".length());
}
}
return url;
}

public static String buildExpand(String... values) {
if (values == null || values.length == 0) {
return "";
Expand Down
Loading
Loading