Skip to content

Commit b6d34eb

Browse files
committed
feat: Add logged in user identity cache key
To cover logged in user sessions more cleanly when resolving identities, a new cachekey prefix was added to IdentityService.resoveIdentity. This "user_" prefix is used when there is an identifiable user session for the current request. Resolves EclipseFdn/open-vsx.org#8954
1 parent 0b7bc22 commit b6d34eb

2 files changed

Lines changed: 131 additions & 4 deletions

File tree

server/src/main/java/org/eclipse/openvsx/ratelimit/IdentityService.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
*****************************************************************************/
1313
package org.eclipse.openvsx.ratelimit;
1414

15-
import com.giffing.bucket4j.spring.boot.starter.context.ExpressionParams;
16-
import jakarta.servlet.http.HttpServletRequest;
15+
import java.util.Map;
16+
17+
import org.apache.commons.lang3.StringUtils;
18+
import org.eclipse.openvsx.UserService;
1719
import org.eclipse.openvsx.accesstoken.AccessTokenService;
1820
import org.eclipse.openvsx.ratelimit.config.RateLimitConfig;
1921
import org.eclipse.openvsx.ratelimit.config.RateLimitProperties;
@@ -26,7 +28,9 @@
2628
import org.springframework.expression.spel.support.StandardEvaluationContext;
2729
import org.springframework.stereotype.Service;
2830

29-
import java.util.Map;
31+
import com.giffing.bucket4j.spring.boot.starter.context.ExpressionParams;
32+
33+
import jakarta.servlet.http.HttpServletRequest;
3034

3135
@Service
3236
@ConditionalOnBean(RateLimitConfig.class)
@@ -41,21 +45,24 @@ public class IdentityService {
4145
private final CustomerService customerService;
4246
private final AccessTokenService tokenService;
4347
private final RateLimitProperties rateLimitProperties;
48+
private final UserService userService;
4449

4550
public IdentityService(
4651
ExpressionParser expressionParser,
4752
ConfigurableBeanFactory beanFactory,
4853
TierService tierService,
4954
CustomerService customerService,
5055
AccessTokenService tokenService,
51-
RateLimitProperties rateLimitProperties
56+
RateLimitProperties rateLimitProperties,
57+
UserService userService
5258
) {
5359
this.expressionParser = expressionParser;
5460
this.beanFactory = beanFactory;
5561
this.tierService = tierService;
5662
this.customerService = customerService;
5763
this.tokenService = tokenService;
5864
this.rateLimitProperties = rateLimitProperties;
65+
this.userService = userService;
5966
}
6067

6168
public ResolvedIdentity resolveIdentity(HttpServletRequest request) {
@@ -74,6 +81,15 @@ public ResolvedIdentity resolveIdentity(HttpServletRequest request) {
7481
}
7582
}
7683

84+
// if we don't have a valid token, we check if the user is logged in to generate a cache key
85+
// we want to only do this if we don't have a valid token to avoid unnecessary database calls
86+
if (cacheKey == null) {
87+
var user = userService.findLoggedInUser();
88+
if (user != null && StringUtils.isNotBlank(user.getAuthId())) {
89+
cacheKey = "user_" + user.getAuthId();
90+
}
91+
}
92+
7793
var customer = customerService.getCustomerByIpAddress(ipAddress);
7894
if (customer.isPresent() && cacheKey == null) {
7995
cacheKey = "customer_" + customer.get().getName();
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Copyright (c) 2026 Eclipse Foundation AISBL
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package org.eclipse.openvsx.ratelimit;
11+
12+
import java.util.Optional;
13+
14+
import org.eclipse.openvsx.UserService;
15+
import org.eclipse.openvsx.accesstoken.AccessTokenService;
16+
import org.eclipse.openvsx.entities.UserData;
17+
import org.eclipse.openvsx.ratelimit.config.RateLimitProperties;
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.extension.ExtendWith;
22+
import org.mockito.ArgumentMatchers;
23+
import org.mockito.Mockito;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
26+
import org.springframework.boot.test.context.TestConfiguration;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.expression.ExpressionParser;
29+
import org.springframework.expression.spel.standard.SpelExpressionParser;
30+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
31+
import org.springframework.test.context.junit.jupiter.SpringExtension;
32+
33+
import jakarta.servlet.http.HttpServletRequest;
34+
35+
@ExtendWith(SpringExtension.class)
36+
@MockitoBean(types = {
37+
ConfigurableBeanFactory.class,
38+
AccessTokenService.class
39+
})
40+
public class IdentityServiceTest {
41+
42+
@MockitoBean
43+
CustomerService customerService;
44+
45+
@MockitoBean
46+
UserService users;
47+
48+
@MockitoBean
49+
TierService tierService;
50+
51+
@Autowired
52+
IdentityService service;
53+
54+
@Test
55+
public void testResolveIdentityAuthenticatedUser() {
56+
var request = mockRequest();
57+
var userData = mockUserData();
58+
59+
Mockito.when(customerService.getCustomerByIpAddress(ArgumentMatchers.anyString())).thenReturn(Optional.empty());
60+
Mockito.when(tierService.getFreeTier()).thenReturn(Optional.empty());
61+
Mockito.when(tierService.getSafetyTier()).thenReturn(Optional.empty());
62+
63+
var resolvedIdentity = service.resolveIdentity(request);
64+
65+
assertTrue(resolvedIdentity.cacheKey().startsWith("user_"), "Cache key should start with 'user_'");
66+
assertEquals("user_" + userData.getAuthId(), resolvedIdentity.cacheKey(), "Cache key should be based on user auth ID");
67+
}
68+
69+
private HttpServletRequest mockRequest() {
70+
var request = Mockito.mock(HttpServletRequest.class);
71+
Mockito.when(request.getParameter("token")).thenReturn(null);
72+
return request;
73+
}
74+
75+
private UserData mockUserData() {
76+
var userData = new UserData();
77+
userData.setLoginName("test_user");
78+
userData.setFullName("Test User");
79+
userData.setAuthId("test_auth_id");
80+
userData.setProviderUrl("http://example.com/test");
81+
Mockito.doReturn(userData).when(users).findLoggedInUser();
82+
return userData;
83+
}
84+
85+
@TestConfiguration
86+
static class TestConfig {
87+
88+
@Bean
89+
public IdentityService identityService(
90+
ExpressionParser expressionParser,
91+
ConfigurableBeanFactory beanFactory,
92+
TierService tierService,
93+
CustomerService customerService,
94+
AccessTokenService tokenService,
95+
RateLimitProperties rateLimitProperties,
96+
UserService userService
97+
) {
98+
return new IdentityService(expressionParser, beanFactory, tierService, customerService, tokenService, rateLimitProperties, userService);
99+
}
100+
101+
@Bean
102+
public ExpressionParser expressionParser() {
103+
return new SpelExpressionParser();
104+
}
105+
106+
@Bean
107+
public RateLimitProperties rateLimitProperties() {
108+
return new RateLimitProperties();
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)