From 0f9ef71a8eb5995cb53ed9bc5f1095b7cb989443 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 24 Oct 2025 16:31:02 +0300 Subject: [PATCH 1/6] Bump commons-lang3 version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5adc8420..3853dfc9 100644 --- a/build.gradle +++ b/build.gradle @@ -61,7 +61,7 @@ dependencies { implementation "org.aspectj:aspectjweaver:${project.aspectj_version}" implementation "org.slf4j:slf4j-api:${slf4j_version}" - implementation 'org.apache.commons:commons-lang3:3.18.0' + implementation 'org.apache.commons:commons-lang3:3.19.0' testImplementation "org.slf4j:jul-to-slf4j:${slf4j_version}" testImplementation("org.junit.platform:junit-platform-runner:${project.junit_runner_version}") { From 6ad02c3b12c41b396327be1cb3860614be3440e8 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 24 Oct 2025 17:19:05 +0300 Subject: [PATCH 2/6] Add test for logging --- .../service/LaunchMicrosecondsTest.java | 72 ++++++++++++++++++- .../info_response_no_microseconds2.txt | 11 +++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/files/responses/info_response_no_microseconds2.txt diff --git a/src/test/java/com/epam/reportportal/service/LaunchMicrosecondsTest.java b/src/test/java/com/epam/reportportal/service/LaunchMicrosecondsTest.java index 5340ab8b..964baf79 100644 --- a/src/test/java/com/epam/reportportal/service/LaunchMicrosecondsTest.java +++ b/src/test/java/com/epam/reportportal/service/LaunchMicrosecondsTest.java @@ -17,12 +17,14 @@ package com.epam.reportportal.service; import com.epam.reportportal.listeners.ListenerParameters; +import com.epam.reportportal.listeners.LogLevel; import com.epam.reportportal.test.TestUtils; import com.epam.reportportal.util.test.SocketUtils; import com.epam.ta.reportportal.ws.model.FinishExecutionRQ; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; import com.epam.ta.reportportal.ws.model.StartTestItemRQ; import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ; +import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; import io.reactivex.Maybe; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.AfterEach; @@ -106,6 +108,14 @@ private static FinishExecutionRQ buildFinishLaunchRq(Comparable> logTime) { + SaveLogRQ rq = new SaveLogRQ(); + rq.setLogTime(logTime); + rq.setLevel(LogLevel.INFO.name()); + rq.setMessage("some message"); + return rq; + } + private static Comparable> dateOrInstant(boolean instant) { Instant testInstant = Instant.ofEpochSecond(START_TIME_SECONDS_BASE, START_TIME_NANO_ADJUSTMENT); if (!instant) { @@ -163,7 +173,7 @@ private static SocketUtils.ServerCallable buildServerCallableForStartItem(Server private static SocketUtils.ServerCallable buildServerCallableForFinishItem(ServerSocket ss, boolean micro) { List responses = new ArrayList<>(); - responses.add(micro ? "files/responses/info_response_microseconds.txt" : "files/responses/info_response_no_microseconds.txt"); + responses.add(micro ? "files/responses/info_response_microseconds.txt" : "files/responses/info_response_no_microseconds2.txt"); responses.add("files/responses/start_launch_response.txt"); responses.add("files/responses/simple_response.txt"); // finish item response return new SocketUtils.ServerCallable(ss, Collections.emptyMap(), responses); @@ -178,6 +188,14 @@ private static SocketUtils.ServerCallable buildServerCallableForFinishLaunch(Ser return new SocketUtils.ServerCallable(ss, Collections.emptyMap(), responses); } + private static SocketUtils.ServerCallable buildServerCallableForLog(ServerSocket ss, boolean micro) { + List responses = new ArrayList<>(); + responses.add(micro ? "files/responses/info_response_microseconds.txt" : "files/responses/info_response_no_microseconds2.txt"); + responses.add("files/responses/start_launch_response.txt"); + responses.add("files/responses/simple_response.txt"); // finish item response + return new SocketUtils.ServerCallable(ss, Collections.emptyMap(), responses); + } + /* ---------------------- Launch start ---------------------- */ @Test public void start_launch_useMicroseconds_false_Date_sends_numeric_time() throws Exception { @@ -426,4 +444,56 @@ private void finishLaunchTimeCase(boolean micro, boolean instant) throws Excepti assertTimeNumeric(json, "endTime"); } } + + /* ---------------------- log ---------------------- */ + + @Test + public void log_useMicroseconds_false_Date_sends_numeric_time() throws Exception { + logTimeCase(false, false); + } + + @Test + public void log_launch_useMicroseconds_false_Instant_sends_numeric_time() throws Exception { + logTimeCase(false, true); + } + + @Test + public void log_launch_useMicroseconds_true_Instant_sends_iso_micro_time() throws Exception { + logTimeCase(true, true); + } + + @Test + public void log_launch_useMicroseconds_true_Date_sends_numeric_time() throws Exception { + logTimeCase(true, false); + } + + private void logTimeCase(boolean micro, boolean instant) throws Exception { + ServerSocket ss = SocketUtils.getServerSocketOnFreePort(); + String baseUrl = "http://localhost:" + ss.getLocalPort(); + ListenerParameters parameters = baseParameters(baseUrl); + parameters.setBatchLogsSize(1); + + ReportPortalClient rpClient = Objects.requireNonNull(ReportPortal.builder() + .buildClient(ReportPortalClient.class, parameters, clientExecutor)); + StartLaunchRQ launchRq = buildStartLaunchRq(new Date()); + ReportPortal rp = ReportPortal.create(rpClient, parameters, clientExecutor); + Launch launch = rp.newLaunch(launchRq); + SaveLogRQ rq = buildSaveLogRq(dateOrInstant(instant)); + + SocketUtils.ServerCallable serverCallable = buildServerCallableForLog(ss, micro); + + Pair, ?> result = executeWithClosing( + ss, serverCallable, () -> { + launch.log(rq); + return Boolean.TRUE; + } + ); + + String json = findLastJsonWithKey(result, "time"); + if (expectString(micro, instant)) { + assertTimeIsoMicro(json, "time"); + } else { + assertTimeNumeric(json, "time"); + } + } } diff --git a/src/test/resources/files/responses/info_response_no_microseconds2.txt b/src/test/resources/files/responses/info_response_no_microseconds2.txt new file mode 100644 index 00000000..ab08c68b --- /dev/null +++ b/src/test/resources/files/responses/info_response_no_microseconds2.txt @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Content-Type: application/json +Expires: 0 +Pragma: no-cache +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +X-Xss-Protection: 1; mode=block +Content-Length: 30 + +{"build":{"version":"5.12.0"}} \ No newline at end of file From 7bc5e329f0552522c8f1a17b03539276cac180f1 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 3 Nov 2025 12:06:18 +0300 Subject: [PATCH 3/6] Micro refactoring to speed up BearerAuthInterceptor slightly --- .../reportportal/service/BearerAuthInterceptor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/epam/reportportal/service/BearerAuthInterceptor.java b/src/main/java/com/epam/reportportal/service/BearerAuthInterceptor.java index 81938e3e..d9618532 100644 --- a/src/main/java/com/epam/reportportal/service/BearerAuthInterceptor.java +++ b/src/main/java/com/epam/reportportal/service/BearerAuthInterceptor.java @@ -1,11 +1,11 @@ /* - * Copyright 2019 EPAM Systems + * Copyright 2025 EPAM Systems * * 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 + * https://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, @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.epam.reportportal.service; import jakarta.annotation.Nonnull; @@ -27,16 +28,16 @@ */ public class BearerAuthInterceptor implements Interceptor { - private final String apiKey; + private final String authHeaderValue; public BearerAuthInterceptor(String apiKey) { - this.apiKey = apiKey; + this.authHeaderValue = "Bearer " + apiKey; } @Override @Nonnull public Response intercept(Chain chain) throws IOException { - Request rq = chain.request().newBuilder().addHeader("Authorization", "Bearer " + apiKey).build(); + Request rq = chain.request().newBuilder().addHeader("Authorization", authHeaderValue).build(); return chain.proceed(rq); } } From 5b1311b8e0ab13d05a14e21f9fea46b688c446f7 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 12 Nov 2025 17:19:09 +0300 Subject: [PATCH 4/6] HTTP logging support for OAuth 2.0 Password Grant authentication --- CHANGELOG.md | 2 ++ .../service/OAuth2PasswordGrantAuthInterceptor.java | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aed4757..4284ec1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- HTTP logging support for OAuth 2.0 Password Grant authentication, by @HardNorth ## [5.4.6] ### Added diff --git a/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java b/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java index e15bee72..6e7e18e3 100644 --- a/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java +++ b/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java @@ -101,6 +101,7 @@ public OAuth2PasswordGrantAuthInterceptor(@Nonnull ListenerParameters parameters URL tokenUrl = parseTokenUri(parameters); OkHttpClient.Builder clientBuilder = ClientUtils.setupSsl(new OkHttpClient.Builder(), tokenUrl, parameters); + ClientUtils.setupHttpLoggingInterceptor(clientBuilder, parameters); if (parameters.isOauthUseProxy()) { ClientUtils.setupProxy(clientBuilder, parameters); From a62c9b3c6d32b7b543c2eb6d63317749af60e847 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 13 Nov 2025 12:41:19 +0300 Subject: [PATCH 5/6] Force not using proxy for OAuth authentication in case `rp.oauth.use.proxy=false` --- CHANGELOG.md | 2 + build.gradle | 1 + .../listeners/ListenerParameters.java | 1 + .../OAuth2PasswordGrantAuthInterceptor.java | 4 ++ ...Auth2PasswordGrantAuthInterceptorTest.java | 68 +++++++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4284ec1c..b4703704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [Unreleased] ### Added - HTTP logging support for OAuth 2.0 Password Grant authentication, by @HardNorth +### Fixed +- Force not using proxy for OAuth authentication in case `rp.oauth.use.proxy=false` to avoid issues when proxy is set through system properties, by @HardNorth ## [5.4.6] ### Added diff --git a/build.gradle b/build.gradle index 3853dfc9..a8aac7a1 100644 --- a/build.gradle +++ b/build.gradle @@ -80,6 +80,7 @@ dependencies { } testImplementation 'commons-io:commons-io:2.17.0' testImplementation 'com.epam.reportportal:agent-java-test-utils:0.1.0' + testImplementation "com.squareup.okhttp3:mockwebserver:${project.okhttp_version}" } test { diff --git a/src/main/java/com/epam/reportportal/listeners/ListenerParameters.java b/src/main/java/com/epam/reportportal/listeners/ListenerParameters.java index 848acf43..a698c9ab 100644 --- a/src/main/java/com/epam/reportportal/listeners/ListenerParameters.java +++ b/src/main/java/com/epam/reportportal/listeners/ListenerParameters.java @@ -197,6 +197,7 @@ public ListenerParameters() { this.convertImage = DEFAULT_CONVERT_IMAGE; this.reportingTimeout = DEFAULT_REPORTING_TIMEOUT; this.httpLogging = DEFAULT_HTTP_LOGGING; + this.oauthUseProxy = DEFAULT_OAUTH_USE_PROXY; this.keystoreType = DEFAULT_KEYSTORE_TYPE; this.truststoreType = DEFAULT_KEYSTORE_TYPE; diff --git a/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java b/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java index 6e7e18e3..aa5c8d41 100644 --- a/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java +++ b/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; import java.util.Map; import java.util.Objects; @@ -105,6 +106,9 @@ public OAuth2PasswordGrantAuthInterceptor(@Nonnull ListenerParameters parameters if (parameters.isOauthUseProxy()) { ClientUtils.setupProxy(clientBuilder, parameters); + } else { + // Explicitly disable proxy to override system proxy settings + clientBuilder.proxy(Proxy.NO_PROXY); } ofNullable(parameters.getHttpConnectTimeout()).ifPresent(d -> clientBuilder.connectTimeout(d.toMillis(), TimeUnit.MILLISECONDS)); diff --git a/src/test/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptorTest.java b/src/test/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptorTest.java index 639bfd05..5106f332 100644 --- a/src/test/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptorTest.java +++ b/src/test/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptorTest.java @@ -17,7 +17,9 @@ package com.epam.reportportal.service; import com.epam.reportportal.listeners.ListenerParameters; +import jakarta.annotation.Nonnull; import okhttp3.*; +import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -547,4 +549,70 @@ public void test401And403ThrottledSimultaneously() throws Exception { assertEquals(3, capturedRequests.size(), "Token refresh should have been attempted after throttling period"); assertEquals(6, apiRequestCount.size(), "API should have been called twice (initial + retry after refresh)"); } + + @Nonnull + private static OAuth2PasswordGrantAuthInterceptor getOAuth2PasswordGrantAuthInterceptor() { + ListenerParameters params = new ListenerParameters(); + params.setOauthTokenUri("https://oauth.example.com/token"); + params.setOauthUsername("test-user"); + params.setOauthPassword("test-password"); + params.setOauthClientId("test-client-id"); + params.setOauthClientSecret("test-client-secret"); + params.setOauthScope("test-scope"); + params.setOauthUseProxy(false); // This is the key setting + + // Create interceptor using constructor that creates its own client + // This will respect the oauthUseProxy setting + return new OAuth2PasswordGrantAuthInterceptor(params); + } + + @Test + public void testOAuthAvoidsProxyWhenOauthUseProxyIsFalse() throws Exception { + // Save original system properties + String originalHttpsProxyHost = System.getProperty("https.proxyHost"); + String originalHttpsProxyPort = System.getProperty("https.proxyPort"); + + try (MockWebServer mockProxyServer = new MockWebServer()) { + // Start mock proxy server on localhost + mockProxyServer.start(); + + // Set system proxy properties to point to our mock proxy + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", String.valueOf(mockProxyServer.getPort())); + + // Configure parameters with oauthUseProxy = false and a non-localhost token URL + OAuth2PasswordGrantAuthInterceptor interceptor = getOAuth2PasswordGrantAuthInterceptor(); + + // Create API client with OAuth interceptor + OkHttpClient apiClient = createMockApiClient(interceptor); + + // Execute API request - this will trigger OAuth token request + Request apiRequest = new Request.Builder().url("http://localhost/api/test").get().build(); + + try (Response response = apiClient.newCall(apiRequest).execute()) { + // Verify API response is successful (from our mock API client) + assertTrue(response.isSuccessful()); + assertEquals(200, response.code()); + + // Wait a bit to ensure no request comes to proxy + Thread.sleep(100); + + // Verify that NO requests were made to the proxy server + // because oauthUseProxy=false means OAuth client should bypass proxy + assertEquals(0, mockProxyServer.getRequestCount(), "Proxy server should NOT receive any requests when oauthUseProxy=false"); + } + } finally { + // Restore original system properties + if (originalHttpsProxyHost != null) { + System.setProperty("https.proxyHost", originalHttpsProxyHost); + } else { + System.clearProperty("https.proxyHost"); + } + if (originalHttpsProxyPort != null) { + System.setProperty("https.proxyPort", originalHttpsProxyPort); + } else { + System.clearProperty("https.proxyPort"); + } + } + } } From 5d4c566bae635d479c227370bfaeaaa0b2b6ca28 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 13 Nov 2025 12:51:04 +0300 Subject: [PATCH 6/6] Update CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4703704..1dc76890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added - HTTP logging support for OAuth 2.0 Password Grant authentication, by @HardNorth ### Fixed -- Force not using proxy for OAuth authentication in case `rp.oauth.use.proxy=false` to avoid issues when proxy is set through system properties, by @HardNorth +- Explicitly disable proxy for OAuth authentication when `rp.oauth.use.proxy=false` to avoid issues when proxy is set through system properties, by @HardNorth ## [5.4.6] ### Added