This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 231e9c4d [ISSUE #1511] Restore SettingsService tests after SSRF 
hardening (#1512)
231e9c4d is described below

commit 231e9c4dbe1b56436ca1e3661a84b6f45ecfd862
Author: youngkermit8-coder <[email protected]>
AuthorDate: Tue Aug 11 20:19:59 2026 +0800

    [ISSUE #1511] Restore SettingsService tests after SSRF hardening (#1512)
    
    Signed-off-by: youngkermit8-coder <[email protected]>
---
 .../rocketmq/studio/settings/SettingsService.java  |  17 ++-
 .../studio/settings/SettingsServiceTest.java       | 123 +++++++++------------
 2 files changed, 67 insertions(+), 73 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
index 80873331..432e3bee 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
@@ -73,15 +73,24 @@ public class SettingsService {
 
     public SettingsService(SettingsRepository settingsRepository, 
RestClient.Builder restClientBuilder,
                            ObjectMapper objectMapper, OperationAuditService 
operationAuditService) {
-        SimpleClientHttpRequestFactory requestFactory = new 
SimpleClientHttpRequestFactory();
-        requestFactory.setConnectTimeout(DATA_SOURCE_TEST_CONNECT_TIMEOUT);
-        requestFactory.setReadTimeout(DATA_SOURCE_TEST_READ_TIMEOUT);
+        this(settingsRepository, buildDataSourceRestClient(restClientBuilder), 
objectMapper, operationAuditService);
+    }
+
+    SettingsService(SettingsRepository settingsRepository, RestClient 
restClient,
+                    ObjectMapper objectMapper, OperationAuditService 
operationAuditService) {
         this.settingsRepository = settingsRepository;
-        this.restClient = 
restClientBuilder.requestFactory(requestFactory).build();
+        this.restClient = restClient;
         this.objectMapper = objectMapper;
         this.operationAuditService = operationAuditService;
     }
 
+    private static RestClient buildDataSourceRestClient(RestClient.Builder 
restClientBuilder) {
+        SimpleClientHttpRequestFactory requestFactory = new 
SimpleClientHttpRequestFactory();
+        requestFactory.setConnectTimeout(DATA_SOURCE_TEST_CONNECT_TIMEOUT);
+        requestFactory.setReadTimeout(DATA_SOURCE_TEST_READ_TIMEOUT);
+        return restClientBuilder.requestFactory(requestFactory).build();
+    }
+
 
     public GeneralSettingsVO getGeneralSettings() {
         log.debug("Loading general settings");
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
index ee111fcf..1b3d9ba9 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
@@ -17,8 +17,6 @@
 package org.apache.rocketmq.studio.settings;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpServer;
 import org.apache.rocketmq.studio.audit.OperationAuditService;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.AfterEach;
@@ -27,17 +25,19 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.client.MockRestServiceServer;
 import org.springframework.web.client.RestClient;
 
-import java.io.IOException;
-import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
-import java.util.concurrent.atomic.AtomicReference;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
@@ -47,10 +47,20 @@ import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
+import static 
org.springframework.test.web.client.match.MockRestRequestMatchers.header;
+import static 
org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static 
org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static 
org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static 
org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
 
 @ExtendWith(MockitoExtension.class)
 class SettingsServiceTest {
 
+    private static final String PROMETHEUS_BASE_URL = "http://192.0.2.1:9090";;
+    private static final String PROMETHEUS_QUERY_URL = PROMETHEUS_BASE_URL + 
"/api/v1/query?query=up";
+    private static final String PROMETHEUS_SUCCESS_BODY =
+            
"{\"status\":\"success\",\"data\":{\"resultType\":\"vector\",\"result\":[]}}";
+
     @Mock
     private SettingsRepository settingsRepository;
 
@@ -59,31 +69,20 @@ class SettingsServiceTest {
 
     private SettingsService settingsService;
 
-    private HttpServer prometheusServer;
-    private String prometheusBaseUrl;
+    private MockRestServiceServer prometheusServer;
 
     @BeforeEach
-    void setUp() throws IOException {
-        prometheusServer = HttpServer.create(new 
InetSocketAddress("127.0.0.1", 0), 0);
-        prometheusBaseUrl = "http://127.0.0.1:"; + 
prometheusServer.getAddress().getPort();
-        prometheusServer.start();
-        settingsService = new SettingsService(settingsRepository, 
RestClient.builder(), new ObjectMapper(), operationAuditService) {
-            @Override
-            boolean isAllowedDataSourceHost(String host) {
-                // The embedded test server listens on loopback, which the 
production SSRF
-                // guard blocks; admit it here and keep the real policy for 
every other host.
-                if ("127.0.0.1".equals(host)) {
-                    return true;
-                }
-                return super.isAllowedDataSourceHost(host);
-            }
-        };
+    void setUp() {
+        RestClient.Builder restClientBuilder = RestClient.builder();
+        prometheusServer = 
MockRestServiceServer.bindTo(restClientBuilder).build();
+        settingsService = new SettingsService(settingsRepository, 
restClientBuilder.build(),
+                new ObjectMapper(), operationAuditService);
     }
 
 
     @AfterEach
     void tearDown() {
-        prometheusServer.stop(0);
+        prometheusServer.verify();
     }
 
     @Test
@@ -343,15 +342,11 @@ class SettingsServiceTest {
 
     @Test
     void testConnectionShouldQueryPrometheusEndpoint() {
-        AtomicReference<String> requestPath = new AtomicReference<>();
-        AtomicReference<String> requestQuery = new AtomicReference<>();
-        prometheusServer.createContext("/api/v1/query", exchange -> {
-            requestPath.set(exchange.getRequestURI().getPath());
-            requestQuery.set(exchange.getRequestURI().getRawQuery());
-            respond(exchange, 200, 
"{\"status\":\"success\",\"data\":{\"resultType\":\"vector\",\"result\":[]}}");
-        });
+        prometheusServer.expect(requestTo(PROMETHEUS_QUERY_URL))
+                .andExpect(method(HttpMethod.GET))
+                .andRespond(withSuccess(PROMETHEUS_SUCCESS_BODY, 
MediaType.APPLICATION_JSON));
         DataSourceTestDTO request = DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("Prometheus")
                 .build();
 
@@ -359,19 +354,18 @@ class SettingsServiceTest {
 
         assertThat(result.isSuccess()).isTrue();
         assertThat(result.getMessage()).isEqualTo("Connection successful");
-        assertThat(requestPath.get()).isEqualTo("/api/v1/query");
-        assertThat(requestQuery.get()).isEqualTo("query=up");
     }
 
     @Test
     void testConnectionShouldApplyBasicAuthentication() {
-        AtomicReference<String> authorization = new AtomicReference<>();
-        prometheusServer.createContext("/api/v1/query", exchange -> {
-            
authorization.set(exchange.getRequestHeaders().getFirst("Authorization"));
-            respond(exchange, 200, 
"{\"status\":\"success\",\"data\":{\"resultType\":\"vector\",\"result\":[]}}");
-        });
+        String expectedAuthorization = "Basic "
+                + 
Base64.getEncoder().encodeToString("prom:secret".getBytes(StandardCharsets.UTF_8));
+        prometheusServer.expect(requestTo(PROMETHEUS_QUERY_URL))
+                .andExpect(method(HttpMethod.GET))
+                .andExpect(header(HttpHeaders.AUTHORIZATION, 
expectedAuthorization))
+                .andRespond(withSuccess(PROMETHEUS_SUCCESS_BODY, 
MediaType.APPLICATION_JSON));
         DataSourceTestDTO request = DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("Prometheus")
                 .auth("Basic Auth")
                 .username("prom")
@@ -381,19 +375,18 @@ class SettingsServiceTest {
         DataSourceTestResultVO result = 
settingsService.testDataSource(request);
 
         assertThat(result.isSuccess()).isTrue();
-        assertThat(authorization.get()).isEqualTo("Basic "
-                + 
Base64.getEncoder().encodeToString("prom:secret".getBytes(StandardCharsets.UTF_8)));
     }
 
     @Test
     void 
testConnectionShouldNormalizeIdentifiersIndependentlyOfDefaultLocale() {
-        AtomicReference<String> authorization = new AtomicReference<>();
-        prometheusServer.createContext("/api/v1/query", exchange -> {
-            
authorization.set(exchange.getRequestHeaders().getFirst("Authorization"));
-            respond(exchange, 200, 
"{\"status\":\"success\",\"data\":{\"resultType\":\"vector\",\"result\":[]}}");
-        });
+        String expectedAuthorization = "Basic "
+                + 
Base64.getEncoder().encodeToString("prom:secret".getBytes(StandardCharsets.UTF_8));
+        prometheusServer.expect(requestTo(PROMETHEUS_QUERY_URL))
+                .andExpect(method(HttpMethod.GET))
+                .andExpect(header(HttpHeaders.AUTHORIZATION, 
expectedAuthorization))
+                .andRespond(withSuccess(PROMETHEUS_SUCCESS_BODY, 
MediaType.APPLICATION_JSON));
         DataSourceTestDTO request = DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("MIMIR")
                 .auth("Basic Auth")
                 .username("prom")
@@ -410,19 +403,16 @@ class SettingsServiceTest {
         }
 
         assertThat(result.isSuccess()).isTrue();
-        assertThat(authorization.get()).isEqualTo("Basic "
-                + 
Base64.getEncoder().encodeToString("prom:secret".getBytes(StandardCharsets.UTF_8)));
     }
 
     @Test
     void testConnectionShouldApplyBearerAuthentication() {
-        AtomicReference<String> authorization = new AtomicReference<>();
-        prometheusServer.createContext("/api/v1/query", exchange -> {
-            
authorization.set(exchange.getRequestHeaders().getFirst("Authorization"));
-            respond(exchange, 200, 
"{\"status\":\"success\",\"data\":{\"resultType\":\"vector\",\"result\":[]}}");
-        });
+        prometheusServer.expect(requestTo(PROMETHEUS_QUERY_URL))
+                .andExpect(method(HttpMethod.GET))
+                .andExpect(header(HttpHeaders.AUTHORIZATION, "Bearer token-1"))
+                .andRespond(withSuccess(PROMETHEUS_SUCCESS_BODY, 
MediaType.APPLICATION_JSON));
         DataSourceTestDTO request = DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("Prometheus")
                 .auth("Bearer Token")
                 .bearerToken("token-1")
@@ -431,7 +421,6 @@ class SettingsServiceTest {
         DataSourceTestResultVO result = 
settingsService.testDataSource(request);
 
         assertThat(result.isSuccess()).isTrue();
-        assertThat(authorization.get()).isEqualTo("Bearer token-1");
     }
 
     @Test
@@ -503,7 +492,7 @@ class SettingsServiceTest {
     @Test
     void testConnectionShouldRejectIncompleteBasicAuthentication() {
         DataSourceTestResultVO result = 
settingsService.testDataSource(DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("Prometheus")
                 .auth("Basic Auth")
                 .username("prom")
@@ -517,7 +506,7 @@ class SettingsServiceTest {
     @Test
     void testConnectionShouldRejectMissingBearerToken() {
         DataSourceTestResultVO result = 
settingsService.testDataSource(DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("Prometheus")
                 .auth("Bearer Token")
                 .build());
@@ -528,10 +517,13 @@ class SettingsServiceTest {
 
     @Test
     void testConnectionShouldReturnPrometheusErrorDetails() {
-        prometheusServer.createContext("/api/v1/query", exchange -> 
respond(exchange, 422,
-                
"{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid 
query\"}"));
+        prometheusServer.expect(requestTo(PROMETHEUS_QUERY_URL))
+                .andExpect(method(HttpMethod.GET))
+                .andRespond(withStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        
.body("{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid 
query\"}"));
         DataSourceTestDTO request = DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("VictoriaMetrics")
                 .build();
 
@@ -557,7 +549,7 @@ class SettingsServiceTest {
     @Test
     void testConnectionShouldRejectUnsupportedType() {
         DataSourceTestResultVO result = 
settingsService.testDataSource(DataSourceTestDTO.builder()
-                .url(prometheusBaseUrl)
+                .url(PROMETHEUS_BASE_URL)
                 .type("rocketmq")
                 .build());
 
@@ -565,11 +557,4 @@ class SettingsServiceTest {
         assertThat(result.getMessage()).isEqualTo("Unsupported data source 
type: rocketmq");
     }
 
-    private void respond(HttpExchange exchange, int statusCode, String body) 
throws IOException {
-        byte[] response = body.getBytes(StandardCharsets.UTF_8);
-        exchange.getResponseHeaders().set("Content-Type", "application/json");
-        exchange.sendResponseHeaders(statusCode, response.length);
-        exchange.getResponseBody().write(response);
-        exchange.close();
-    }
 }

Reply via email to