From 471112285d7b06adef0fe25aeb4694a45ac5b2cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 7 May 2026 12:15:28 +0200 Subject: [PATCH 1/4] fix(Rate limit): using servlet decoded path over request URI --- .../filter/RateLimitServletFilter.java | 2 +- .../ratelimit/RateLimitIntegrationTest.java | 105 ++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java diff --git a/server/src/main/java/org/eclipse/openvsx/ratelimit/filter/RateLimitServletFilter.java b/server/src/main/java/org/eclipse/openvsx/ratelimit/filter/RateLimitServletFilter.java index 0875a636a..d4775d97a 100644 --- a/server/src/main/java/org/eclipse/openvsx/ratelimit/filter/RateLimitServletFilter.java +++ b/server/src/main/java/org/eclipse/openvsx/ratelimit/filter/RateLimitServletFilter.java @@ -56,7 +56,7 @@ public RateLimitServletFilter( @Override protected boolean shouldNotFilter(HttpServletRequest request) { - return !request.getRequestURI().matches(filterProperties.getUrl()); + return !request.getServletPath().matches(filterProperties.getUrl()); } @Override diff --git a/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java b/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java new file mode 100644 index 000000000..968f27091 --- /dev/null +++ b/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java @@ -0,0 +1,105 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ +package org.eclipse.openvsx.ratelimit; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.distributed.proxy.ProxyManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import redis.clients.jedis.JedisCluster; + +import java.net.URI; +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { + "ovsx.rate-limit.enabled=true", + "ovsx.rate-limit.filters[0].url=/(api|vscode)/.*" +}) +@ActiveProfiles("test") +class RateLimitIntegrationTest { + + @LocalServerPort + int port; + + @Autowired + TestRestTemplate restTemplate; + + @MockitoBean + JedisCluster jedisCluster; + + @MockitoBean + ProxyManager proxyManager; + + @MockitoBean + RateLimitService rateLimitService; + + @MockitoBean + IdentityService identityService; + + @BeforeEach + void setUp() { + var bucket = Bucket.builder() + .addLimit(Bandwidth.builder().capacity(100).refillGreedy(100, Duration.ofHours(1)).build()) + .build(); + Mockito.when(rateLimitService.getBucket(any())).thenReturn(RateLimitService.BucketPair.of(bucket, 100)); + + var identity = new ResolvedIdentity("1.2.3.4", "ip_1.2.3.4", null, null, null); + Mockito.when(identityService.resolveIdentity(any())).thenReturn(identity); + } + + @Test + void normalApiPath_isRateLimited() { + var response = restTemplate.getForEntity(uri("/api/-/search"), String.class); + assertThat(response.getHeaders().getFirst("X-RateLimit-Limit")).isEqualTo("100"); + } + + @Test + void normalVscodePath_isRateLimited() { + var response = restTemplate.getForEntity(uri("/vscode/gallery/extensionquery"), String.class); + assertThat(response.getHeaders().getFirst("X-RateLimit-Limit")).isEqualTo("100"); + } + + @Test + void percentEncodedApiPath_isRateLimited() { + var response = restTemplate.getForEntity(uri("/%61pi/-/search"), String.class); + assertThat(response.getHeaders().getFirst("X-RateLimit-Limit")).isEqualTo("100"); + } + + @Test + void percentEncodedVscodePath_isRateLimited() { + var response = restTemplate.getForEntity(uri("/%76scode/gallery/extensionquery"), String.class); + assertThat(response.getHeaders().getFirst("X-RateLimit-Limit")).isEqualTo("100"); + } + + @Test + void nonApiPath_isNotRateLimited() { + var response = restTemplate.getForEntity(uri("/login"), String.class); + assertThat(response.getHeaders().getFirst("X-RateLimit-Limit")).isNull(); + } + + private URI uri(String path) { + return URI.create("http://localhost:" + port + path); + } +} From deadd446a31568debdf6e3811ac6f0c5d0b1fa60 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Fri, 8 May 2026 09:10:44 +0200 Subject: [PATCH 2/4] fix test after rebase --- .../eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java b/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java index 968f27091..4b6c46623 100644 --- a/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java +++ b/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java @@ -63,7 +63,8 @@ void setUp() { var bucket = Bucket.builder() .addLimit(Bandwidth.builder().capacity(100).refillGreedy(100, Duration.ofHours(1)).build()) .build(); - Mockito.when(rateLimitService.getBucket(any())).thenReturn(RateLimitService.BucketPair.of(bucket, 100)); + Mockito.when(rateLimitService.getBucket(any())) + .thenReturn(RateLimitService.BucketPair.of(bucket, new RateLimitService.MinimumBandwidth(100, 0))); var identity = new ResolvedIdentity("1.2.3.4", "ip_1.2.3.4", null, null, null); Mockito.when(identityService.resolveIdentity(any())).thenReturn(identity); From 482f70dd3ee57a4f4c0eff667de9456e33f68aa3 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Fri, 8 May 2026 10:31:07 +0200 Subject: [PATCH 3/4] remove test profile from rate limit test as its not needed and disable elasticsearch --- .../eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java b/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java index 4b6c46623..f7a9470ac 100644 --- a/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java +++ b/server/src/test/java/org/eclipse/openvsx/ratelimit/RateLimitIntegrationTest.java @@ -23,7 +23,6 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import redis.clients.jedis.JedisCluster; @@ -35,9 +34,9 @@ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "ovsx.rate-limit.enabled=true", - "ovsx.rate-limit.filters[0].url=/(api|vscode)/.*" + "ovsx.rate-limit.filters[0].url=/(api|vscode)/.*", + "ovsx.elasticsearch.enabled=false" }) -@ActiveProfiles("test") class RateLimitIntegrationTest { @LocalServerPort From 55a9e23c03d35b87a0af746e937b906e46fac853 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Fri, 8 May 2026 11:06:27 +0200 Subject: [PATCH 4/4] try higher max memory setting --- server/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index b2c321f82..12f770a14 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -231,7 +231,7 @@ tasks.register('runServer', JavaExec) { } test { - jvmArgs = ['--enable-native-access=ALL-UNNAMED'] // due to https://github.com/netty/netty/issues/15161 + jvmArgs = ['--enable-native-access=ALL-UNNAMED', '-Xmx4096m'] // due to https://github.com/netty/netty/issues/15161 useJUnitPlatform() }