Skip to content

[🍒] [PLUGIN-1959] Add proxy support for ServiceNow plugins - #152

Merged
vikasrathee-cs merged 2 commits into
data-integrations:release/1.2from
cloudsufi:proxy-server-implementation-1.2
Jul 24, 2026
Merged

[🍒] [PLUGIN-1959] Add proxy support for ServiceNow plugins#152
vikasrathee-cs merged 2 commits into
data-integrations:release/1.2from
cloudsufi:proxy-server-implementation-1.2

Conversation

@vishwasvaidya-cloudsufi

Copy link
Copy Markdown

[🍒]

🍒 [cherrypick]

PR : (#151)

Description:

Route all ServiceNow API calls (table reads, schema fetch, batch sink writes, OAuth token requests) through an optional HTTP proxy, modeled on the http-plugins proxy support.

  • Add proxyUrl, proxyUsername, proxyPassword properties to ServiceNowConnectorConfig (nullable, macro-enabled) with validation

  • Build a proxy-aware Apache HttpClient in RestAPIClient used by all GET/POST/DELETE calls

  • Route the OAuth token call through the proxy via a new ProxyOltuHttpClient (Oltu HttpClient backed by Apache HttpClient); falls back to URLConnectionClient when no proxy is configured

  • Add Proxy fields and a "Proxy authentication" filter to the connector, source, multi-source and sink widgets

  • Document the new properties and add unit tests for proxy config

  • ProxyOltuHttpClientTest: covers the OAuth-over-proxy client (POST token parsing, GET path, IOException wrapped as OAuthSystemException)

  • RestAPIClientProxyTest: covers getHttpClientBuilder() proxy wiring (proxy host + credentials, proxy-only, no-proxy, invalid URL) by inspecting the real HttpClientBuilder's fields, since its setters are final

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces proxy support for ServiceNow connections, adding Proxy URL, Username, and Password configuration properties across batch sinks, sources, and connectors, along with a new ProxyOltuHttpClient to route OAuth token requests. The reviewer feedback identifies several improvement opportunities, including refining macro validation logic when no proxy is configured, ensuring a Proxy URL is provided if credentials are set, correctly handling HTTP PUT requests in ProxyOltuHttpClient, explicitly specifying UTF-8 encoding for entity parsing, and using a case-insensitive map for HTTP headers.

Comment on lines +115 to +117
!containsMacro(ServiceNowConstants.PROPERTY_PROXY_URL) &&
!containsMacro(ServiceNowConstants.PROPERTY_PROXY_USERNAME) &&
!containsMacro(ServiceNowConstants.PROPERTY_PROXY_PASSWORD);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the proxy is not configured (i.e., proxyUrl is empty), we should not prevent connection validation just because proxyUsername or proxyPassword contain macros. We should only check if the proxy credentials contain macros if a proxy is actually configured.

      !containsMacro(ServiceNowConstants.PROPERTY_PROXY_URL) &&
      (connection == null || !connection.hasProxyConfigured() ||
       (!containsMacro(ServiceNowConstants.PROPERTY_PROXY_USERNAME) &&
        !containsMacro(ServiceNowConstants.PROPERTY_PROXY_PASSWORD)));

Comment on lines +202 to +210
boolean hasUsername = !Util.isNullOrEmpty(proxyUsername);
boolean hasPassword = !Util.isNullOrEmpty(proxyPassword);
if (hasUsername && !hasPassword) {
collector.addFailure("Proxy password must be specified when a proxy username is provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_PASSWORD);
} else if (!hasUsername && hasPassword) {
collector.addFailure("Proxy username must be specified when a proxy password is provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_USERNAME);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If a user provides proxy credentials (username or password) but leaves the Proxy URL empty, the proxy settings will be silently ignored. We should validate that the Proxy URL is specified when proxy credentials are provided to prevent silent misconfigurations.

Suggested change
boolean hasUsername = !Util.isNullOrEmpty(proxyUsername);
boolean hasPassword = !Util.isNullOrEmpty(proxyPassword);
if (hasUsername && !hasPassword) {
collector.addFailure("Proxy password must be specified when a proxy username is provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_PASSWORD);
} else if (!hasUsername && hasPassword) {
collector.addFailure("Proxy username must be specified when a proxy password is provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_USERNAME);
}
boolean hasUsername = !Util.isNullOrEmpty(proxyUsername);
boolean hasPassword = !Util.isNullOrEmpty(proxyPassword);
if ((hasUsername || hasPassword) && Util.isNullOrEmpty(proxyUrl)) {
collector.addFailure("Proxy URL must be specified when proxy credentials are provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_URL);
} else if (hasUsername && !hasPassword) {
collector.addFailure("Proxy password must be specified when a proxy username is provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_PASSWORD);
} else if (!hasUsername && hasPassword) {
collector.addFailure("Proxy username must be specified when a proxy password is provided.", null)
.withConfigProperty(ServiceNowConstants.PROPERTY_PROXY_USERNAME);
}

Comment on lines +61 to +69
if (OAuth.HttpMethod.POST.equalsIgnoreCase(requestMethod) || OAuth.HttpMethod.PUT.equalsIgnoreCase(requestMethod)) {
HttpPost httpPost = new HttpPost(request.getLocationUri());
if (request.getBody() != null) {
httpPost.setEntity(new StringEntity(request.getBody(), StandardCharsets.UTF_8));
}
httpRequest = httpPost;
} else {
httpRequest = new HttpGet(request.getLocationUri());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using HttpPost for a PUT request is incorrect. If the request method is PUT, we should instantiate and use HttpPut instead of HttpPost.

    if (OAuth.HttpMethod.POST.equalsIgnoreCase(requestMethod)) {
      HttpPost httpPost = new HttpPost(request.getLocationUri());
      if (request.getBody() != null) {
        httpPost.setEntity(new StringEntity(request.getBody(), StandardCharsets.UTF_8));
      }
      httpRequest = httpPost;
    } else if (OAuth.HttpMethod.PUT.equalsIgnoreCase(requestMethod)) {
      org.apache.http.client.methods.HttpPut httpPut = new org.apache.http.client.methods.HttpPut(request.getLocationUri());
      if (request.getBody() != null) {
        httpPut.setEntity(new StringEntity(request.getBody(), StandardCharsets.UTF_8));
      }
      httpRequest = httpPut;
    } else {
      httpRequest = new HttpGet(request.getLocationUri());
    }

int responseCode = response.getStatusLine().getStatusCode();
String contentType = response.getEntity() != null && response.getEntity().getContentType() != null
? response.getEntity().getContentType().getValue() : null;
String body = response.getEntity() != null ? EntityUtils.toString(response.getEntity()) : "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Calling EntityUtils.toString without specifying a charset defaults to the system default charset, which can lead to platform-dependent encoding issues (e.g., on Windows environments). It is safer to explicitly specify StandardCharsets.UTF_8.

Suggested change
String body = response.getEntity() != null ? EntityUtils.toString(response.getEntity()) : "";
String body = response.getEntity() != null ? EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8) : "";

Comment on lines +100 to +105
Map<String, List<String>> responseHeaders = new java.util.HashMap<>();
for (Header header : response.getAllHeaders()) {
List<String> values = responseHeaders.computeIfAbsent(header.getName(), k -> new ArrayList<>());
values.add(header.getValue());
}
return Collections.unmodifiableMap(responseHeaders);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

HTTP header names are case-insensitive per RFC 7230. Using a standard HashMap can cause issues if headers are looked up with different casing (e.g., Content-Type vs content-type). It is safer to use a case-insensitive map, such as a TreeMap with String.CASE_INSENSITIVE_ORDER.

Suggested change
Map<String, List<String>> responseHeaders = new java.util.HashMap<>();
for (Header header : response.getAllHeaders()) {
List<String> values = responseHeaders.computeIfAbsent(header.getName(), k -> new ArrayList<>());
values.add(header.getValue());
}
return Collections.unmodifiableMap(responseHeaders);
Map<String, List<String>> responseHeaders = new java.util.TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (Header header : response.getAllHeaders()) {
List<String> values = responseHeaders.computeIfAbsent(header.getName(), k -> new ArrayList<>());
values.add(header.getValue());
}
return Collections.unmodifiableMap(responseHeaders);

Route all ServiceNow API calls (table reads, schema fetch, batch sink
writes, OAuth token requests) through an optional HTTP proxy, modeled on
the http-plugins proxy support.

- Add proxyUrl, proxyUsername, proxyPassword properties to
  ServiceNowConnectorConfig (nullable, macro-enabled) with validation
- Build a proxy-aware Apache HttpClient in RestAPIClient used by all
  GET/POST/DELETE calls
- Route the OAuth token call through the proxy via a new
  ProxyOltuHttpClient (Oltu HttpClient backed by Apache HttpClient);
  falls back to URLConnectionClient when no proxy is configured
- Add Proxy fields and a "Proxy authentication" filter to the connector,
  source, multi-source and sink widgets
- Document the new properties and add unit tests for proxy config

- ProxyOltuHttpClientTest: covers the OAuth-over-proxy client (POST token
  parsing, GET path, IOException wrapped as OAuthSystemException)
- RestAPIClientProxyTest: covers getHttpClientBuilder() proxy wiring
  (proxy host + credentials, proxy-only, no-proxy, invalid URL) by
  inspecting the real HttpClientBuilder's fields, since its setters are final

add commons-codec dependency for proxy authentication

Added validation checks for Proxy Properties

fixed errorMessage.properties

updated docs for proxyURL
@vikasrathee-cs
vikasrathee-cs force-pushed the proxy-server-implementation-1.2 branch from b0e5a33 to a6c8233 Compare July 21, 2026 07:40
@vikasrathee-cs
vikasrathee-cs merged commit d80b481 into data-integrations:release/1.2 Jul 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants