Skip to content

Hostname protocol fix #8

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@
import com.contrastsecurity.sdk.ContrastSDK;
import com.contrastsecurity.sdk.internal.GsonFactory;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -46,6 +51,7 @@ public class SDKExtension {
private final ContrastSDK contrastSDK;
private final UrlBuilder urlBuilder;
private final Gson gson;
private static final Logger logger = LoggerFactory.getLogger(SDKExtension.class);

public SDKExtension(ContrastSDK contrastSDK) {
this.contrastSDK = contrastSDK;
Expand Down Expand Up @@ -241,12 +247,36 @@ private String getRouteDetailsUrl(String organizationId, String applicationId, S
public ApplicationsResponse getApplications(String organizationId)
throws UnauthorizedException, IOException {
String url = urlBuilder.getApplicationsUrl(organizationId)+"&expand=metadata,technologies,skip_links";
try (InputStream is =
contrastSDK.makeRequest(HttpMethod.GET, url);
Reader reader = new InputStreamReader(is)) {
try (InputStream is = contrastSDK.makeRequest(HttpMethod.GET, url)) {
// Read the entire input stream into a string for logging
String responseContent = convertStreamToString(is);

// Log the response content
logger.debug("Applications API response: {}", responseContent);

// Convert the string back to a reader for GSON
Reader reader = new StringReader(responseContent);
return this.gson.fromJson(reader, ApplicationsResponse.class);
}
}

/**
* Converts an InputStream to String without closing the stream.
*/
private String convertStreamToString(InputStream is) throws IOException {
if (is == null) {
return "";
}

StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
}
return sb.toString();
}

public Traces getTraces(String organizationId, String appId, TraceFilterBody filters)
throws IOException, UnauthorizedException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
public class SDKHelper {

private static final String MCP_SERVER_NAME = "contrast-mcp";
private static final String MCP_VERSION = "0.0.9";
private static final String MCP_VERSION = "0.0.10";

private static final Logger logger = LoggerFactory.getLogger(SDKHelper.class);

Expand Down Expand Up @@ -150,18 +150,41 @@ public static List<LibraryObservation> getLibraryObservationsWithCache(
return getLibraryObservationsWithCache(libraryId, appId, orgId, 25, extendedSDK);
}

/**
* Constructs a URL with protocol and server.
* If the hostname already contains a protocol (e.g., "https://host.com"),
* it returns the hostname as is. Otherwise, it prepends the protocol from properties.
*
* @param hostName The hostname, which may or may not include a protocol
* @return A URL with protocol and hostname
*/
public static String getProtocolAndServer(String hostName) {
if (hostName == null) {
return null;
}

if (hostName.startsWith("http://") || hostName.startsWith("https://")) {
return hostName;
}

String protocol = SDKHelper.environment.getProperty("contrast.api.protocol", "https");
return protocol + "://" + hostName;
}

// The withUserAgentProduct will generate a user agent header that looks like
// User-Agent: contrast-mcp/1.0 contrast-sdk-java/3.4.2 Java/19.0.2+7
public static ContrastSDK getSDK(String hostName, String apiKey, String serviceKey, String userName, String httpProxyHost, String httpProxyPort) {
logger.info("Initializing ContrastSDK with username: {}, host: {}", userName, hostName);

String baseUrl = getProtocolAndServer(hostName);
String apiUrl = baseUrl + "/Contrast/api";
logger.info("API URL will be : {}", apiUrl);
ContrastSDK.Builder builder = new ContrastSDK.Builder(userName, serviceKey, apiKey)
.withApiUrl(SDKHelper.environment.getProperty("contrast.api.protocol", "https") + "://" + hostName + "/Contrast/api")
.withApiUrl(apiUrl)
.withUserAgentProduct(UserAgentProduct.of(MCP_SERVER_NAME, MCP_VERSION));

if (httpProxyHost != null && !httpProxyHost.isEmpty()) {
int port = httpProxyPort != null && !httpProxyPort.isEmpty() ? Integer.parseInt(httpProxyPort) : 80;
logger.debug("Configuring HTTP proxy: {}:{}", httpProxyHost, port);
logger.info("Configuring HTTP proxy: {}:{}", httpProxyHost, port);

java.net.Proxy proxy = new java.net.Proxy(
java.net.Proxy.Type.HTTP,
Expand Down Expand Up @@ -278,4 +301,4 @@ public static long clearAllCaches() {

return totalCleared;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.contrast.labs.ai.mcp.contrast.sdkexstension;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.core.env.Environment;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

class SDKHelperTest {

@Mock
private Environment environment;

@BeforeEach
void setUp() throws Exception {
MockitoAnnotations.openMocks(this);

// Inject mocked Environment into SDKHelper's static field using reflection
Field envField = SDKHelper.class.getDeclaredField("environment");
envField.setAccessible(true);
envField.set(null, environment);

// Set up default property
when(environment.getProperty("contrast.api.protocol", "https")).thenReturn("https");
}

@Test
void testGetProtocolAndServer_WithNull() {
assertNull(SDKHelper.getProtocolAndServer(null));
}

@Test
void testGetProtocolAndServer_WithHttpProtocol() {
String result = SDKHelper.getProtocolAndServer("http://example.com");
assertEquals("http://example.com", result);
}

@Test
void testGetProtocolAndServer_WithHttpsProtocol() {
String result = SDKHelper.getProtocolAndServer("https://example.com");
assertEquals("https://example.com", result);
}

@Test
void testGetProtocolAndServer_WithoutProtocol() {
String result = SDKHelper.getProtocolAndServer("example.com");
assertEquals("https://example.com", result);
}

@Test
void testGetProtocolAndServer_WithCustomProtocol() {
when(environment.getProperty("contrast.api.protocol", "https")).thenReturn("http");

String result = SDKHelper.getProtocolAndServer("example.com");
assertEquals("http://example.com", result);
}

@Test
void testGetSDK_WithHttpsUrl() {
String hostWithProtocol = "https://custom.example.com";

// Use reflection to access the private method that builds the API URL
try {
Method method = SDKHelper.class.getDeclaredMethod("getSDK", String.class, String.class, String.class, String.class, String.class, String.class);
method.setAccessible(true);

Object sdk = method.invoke(null, hostWithProtocol, "apiKey", "serviceKey", "username", null, null);

assertNotNull(sdk);
// The actual URL validation would require accessing ContrastSDK's internal state,
// which is beyond the scope of a unit test.
// In a real application, you would use integration tests to verify this behavior.
} catch (Exception e) {
fail("Exception occurred: " + e.getMessage());
}
}
}