Skip to content

Commit b025d9a

Browse files
author
amvanbaren
committed
Check if can login
1 parent 08da93d commit b025d9a

17 files changed

+103
-126
lines changed

server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,4 @@ public interface IExtensionRegistry {
4646
String getPublicKey(String publicId);
4747

4848
RegistryVersionJson getRegistryVersion();
49-
50-
boolean isOAuth2Enabled();
5149
}

server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@
2626
import org.eclipse.openvsx.util.*;
2727
import org.slf4j.Logger;
2828
import org.slf4j.LoggerFactory;
29-
import org.springframework.beans.factory.annotation.Autowired;
3029
import org.springframework.beans.factory.annotation.Value;
3130
import org.springframework.cache.annotation.Cacheable;
3231
import org.springframework.data.domain.PageRequest;
3332
import org.springframework.data.elasticsearch.core.SearchHits;
3433
import org.springframework.http.HttpStatus;
3534
import org.springframework.http.ResponseEntity;
36-
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
3735
import org.springframework.stereotype.Component;
3836
import org.springframework.web.server.ResponseStatusException;
3937
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@@ -65,7 +63,6 @@ public class LocalRegistryService implements IExtensionRegistry {
6563
private final EclipseService eclipse;
6664
private final CacheService cache;
6765
private final ExtensionVersionIntegrityService integrityService;
68-
private final ClientRegistrationRepository clientRegistrationRepository;
6966

7067
public LocalRegistryService(
7168
EntityManager entityManager,
@@ -78,8 +75,7 @@ public LocalRegistryService(
7875
StorageUtilService storageUtil,
7976
EclipseService eclipse,
8077
CacheService cache,
81-
ExtensionVersionIntegrityService integrityService,
82-
@Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository
78+
ExtensionVersionIntegrityService integrityService
8379
) {
8480
this.entityManager = entityManager;
8581
this.repositories = repositories;
@@ -92,7 +88,6 @@ public LocalRegistryService(
9288
this.eclipse = eclipse;
9389
this.cache = cache;
9490
this.integrityService = integrityService;
95-
this.clientRegistrationRepository = clientRegistrationRepository;
9691
}
9792

9893
@Value("${ovsx.webui.url:}")
@@ -1147,9 +1142,4 @@ public RegistryVersionJson getRegistryVersion() {
11471142
registryVersion.setVersion(this.registryVersion);
11481143
return registryVersion;
11491144
}
1150-
1151-
@Override
1152-
public boolean isOAuth2Enabled() {
1153-
return this.clientRegistrationRepository == null ? false : true;
1154-
}
11551145
}

server/src/main/java/org/eclipse/openvsx/RegistryAPI.java

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,38 +1404,4 @@ public ResponseEntity<RegistryVersionJson> getServerVersion() {
14041404
return exc.toResponseEntity(RegistryVersionJson.class);
14051405
}
14061406
}
1407-
1408-
@GetMapping(path = "/api/oauth2/enabled", produces = MediaType.APPLICATION_JSON_VALUE)
1409-
@CrossOrigin
1410-
@Operation(summary = "Check if OAuth2 is enabled")
1411-
@ApiResponse(
1412-
responseCode = "200",
1413-
description = "Returns true if OAuth2 is enabled, false otherwise"
1414-
)
1415-
@ApiResponse(
1416-
responseCode = "429",
1417-
description = "A client has sent too many requests in a given amount of time",
1418-
headers = {
1419-
@Header(
1420-
name = "X-Rate-Limit-Retry-After-Seconds",
1421-
description = "Number of seconds to wait before retrying after receiving a 429 response",
1422-
schema = @Schema(type = "integer", format = "int32")
1423-
),
1424-
@Header(
1425-
name = "X-Rate-Limit-Remaining",
1426-
description = "Number of remaining requests available",
1427-
schema = @Schema(type = "integer", format = "int32")
1428-
)
1429-
}
1430-
)
1431-
public ResponseEntity<Boolean> isOAuth2Enabled() {
1432-
try {
1433-
boolean enabled = local.isOAuth2Enabled();
1434-
return ResponseEntity.ok()
1435-
.cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES).cachePublic())
1436-
.body(enabled);
1437-
} catch (Exception exc) {
1438-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(false);
1439-
}
1440-
}
14411407
}

server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -428,17 +428,6 @@ public RegistryVersionJson getRegistryVersion() {
428428
}
429429
}
430430

431-
/**
432-
* For the upstream registry, it is assumed that OAuth2 is always configured and required.
433-
* This method consistently returns {@code true} to reflect that assumption.
434-
*
435-
* @return {@code true}, indicating that OAuth2 is enabled and expected to be configured.
436-
*/
437-
@Override
438-
public boolean isOAuth2Enabled() {
439-
return true;
440-
}
441-
442431
private void handleError(Throwable exc) throws RuntimeException {
443432
if (exc instanceof HttpStatusCodeException) {
444433
var status = ((HttpStatusCodeException) exc).getStatusCode();

server/src/main/java/org/eclipse/openvsx/UserAPI.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
import org.springframework.web.bind.annotation.*;
3434
import org.springframework.web.multipart.MultipartFile;
3535
import org.springframework.web.server.ResponseStatusException;
36-
import org.springframework.web.servlet.ModelAndView;
3736

37+
import java.net.URI;
3838
import java.util.LinkedHashMap;
3939
import java.util.List;
4040
import java.util.concurrent.TimeUnit;
@@ -66,14 +66,30 @@ public UserAPI(
6666
this.storageUtil = storageUtil;
6767
}
6868

69+
@GetMapping(
70+
path = "/can-login",
71+
produces = MediaType.APPLICATION_JSON_VALUE
72+
)
73+
public ResponseEntity<Boolean> canLogin() {
74+
return ResponseEntity.ok()
75+
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
76+
.body(users.canLogin());
77+
}
78+
6979
/**
7080
* Redirect to GitHub Oauth2 login as default login provider.
7181
*/
7282
@GetMapping(
7383
path = "/login"
7484
)
75-
public ModelAndView login(ModelMap model) {
76-
return new ModelAndView("redirect:/oauth2/authorization/github", model);
85+
public ResponseEntity<Void> login(ModelMap model) {
86+
if(users.canLogin()) {
87+
return ResponseEntity.status(HttpStatus.FOUND)
88+
.location(URI.create(UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "oauth2", "authorization", "github")))
89+
.build();
90+
} else {
91+
return ResponseEntity.notFound().build();
92+
}
7793
}
7894

7995
/**
@@ -84,7 +100,7 @@ public ModelAndView login(ModelMap model) {
84100
produces = MediaType.APPLICATION_JSON_VALUE
85101
)
86102
public ErrorJson getAuthError(HttpServletRequest request) {
87-
var authException = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
103+
var authException = users.canLogin() ? request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) : null;
88104
if (!(authException instanceof AuthenticationException))
89105
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
90106

@@ -241,6 +257,11 @@ public List<NamespaceJson> getOwnNamespaces() {
241257
produces = MediaType.APPLICATION_JSON_VALUE
242258
)
243259
public ResponseEntity<ResultJson> updateNamespaceDetails(@RequestBody NamespaceDetailsJson details) {
260+
var user = users.findLoggedInUser();
261+
if (user == null) {
262+
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
263+
}
264+
244265
try {
245266
return ResponseEntity.ok()
246267
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
@@ -262,6 +283,11 @@ public ResponseEntity<ResultJson> updateNamespaceDetailsLogo(
262283
@PathVariable String namespace,
263284
@RequestParam MultipartFile file
264285
) {
286+
var user = users.findLoggedInUser();
287+
if (user == null) {
288+
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
289+
}
290+
265291
try {
266292
return ResponseEntity.ok()
267293
.body(users.updateNamespaceDetailsLogo(namespace, file));

server/src/main/java/org/eclipse/openvsx/UserService.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
import org.eclipse.openvsx.security.IdPrincipal;
3131
import org.eclipse.openvsx.storage.StorageUtilService;
3232
import org.eclipse.openvsx.util.*;
33+
import org.springframework.beans.factory.annotation.Autowired;
3334
import org.springframework.cache.annotation.CacheEvict;
3435
import org.springframework.security.core.context.SecurityContextHolder;
36+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
3537
import org.springframework.security.oauth2.core.user.OAuth2User;
3638
import org.springframework.stereotype.Component;
3739
import org.springframework.web.multipart.MultipartFile;
@@ -53,22 +55,29 @@ public class UserService {
5355
private final StorageUtilService storageUtil;
5456
private final CacheService cache;
5557
private final ExtensionValidator validator;
58+
private final ClientRegistrationRepository clientRegistrationRepository;
5659

5760
public UserService(
5861
EntityManager entityManager,
5962
RepositoryService repositories,
6063
StorageUtilService storageUtil,
6164
CacheService cache,
62-
ExtensionValidator validator
65+
ExtensionValidator validator,
66+
@Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository
6367
) {
6468
this.entityManager = entityManager;
6569
this.repositories = repositories;
6670
this.storageUtil = storageUtil;
6771
this.cache = cache;
6872
this.validator = validator;
73+
this.clientRegistrationRepository = clientRegistrationRepository;
6974
}
7075

7176
public UserData findLoggedInUser() {
77+
if(!canLogin()) {
78+
return null;
79+
}
80+
7281
var authentication = SecurityContextHolder.getContext().getAuthentication();
7382
if (authentication != null) {
7483
if (authentication.getPrincipal() instanceof IdPrincipal) {
@@ -315,4 +324,8 @@ public ResultJson deleteAccessToken(UserData user, long id) {
315324
token.setActive(false);
316325
return ResultJson.success("Deleted access token for user " + user.getLoginName() + ".");
317326
}
327+
328+
public boolean canLogin() {
329+
return clientRegistrationRepository != null && clientRegistrationRepository.findByRegistrationId("github") != null;
330+
}
318331
}

server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ public IdPrincipal loadUser(OAuth2UserRequest userRequest) {
110110
}
111111
}
112112

113+
public boolean canLogin() {
114+
return users.canLogin();
115+
}
116+
113117
private IdPrincipal loadGitHubUser(OAuth2UserRequest userRequest) {
114118
var authUser = delegate.loadUser(userRequest);
115119
String loginName = authUser.getAttribute("login");

server/src/main/java/org/eclipse/openvsx/security/SecurityConfig.java

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,9 @@ public SecurityConfig(@Autowired(required = false) ClientRegistrationRepository
4141

4242
@Bean
4343
public SecurityFilterChain filterChain(HttpSecurity http, OAuth2UserServices userServices) throws Exception {
44-
var redirectUrl = StringUtils.isEmpty(webuiUrl) ? "/" : webuiUrl;
45-
46-
if (clientRegistrationRepository == null) {
47-
// Minimal security configuration when OAuth2 is not available
48-
return http.authorizeHttpRequests(
44+
var filterChain = http.authorizeHttpRequests(
4945
registry -> registry
50-
.anyRequest()
51-
.permitAll())
52-
.cors(configurer -> configurer.configure(http))
53-
.csrf(configurer -> {
54-
configurer.ignoringRequestMatchers(antMatchers("/api/-/publish", "/api/-/namespace/create", "/api/-/query", "/vscode/**"));
55-
})
56-
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new Http403ForbiddenEntryPoint()))
57-
.build();
58-
}
59-
60-
return http.authorizeHttpRequests(
61-
registry -> registry
62-
.requestMatchers(antMatchers("/*", "/login/**", "/oauth2/**", "/user", "/user/auth-error", "/logout", "/actuator/health/**", "/actuator/metrics", "/actuator/metrics/**", "/actuator/prometheus", "/v3/api-docs/**", "/swagger-resources/**", "/swagger-ui/**", "/webjars/**"))
46+
.requestMatchers(antMatchers("/*", "/login/**", "/oauth2/**", "/can-login", "/user", "/user/auth-error", "/logout", "/actuator/health/**", "/actuator/metrics", "/actuator/metrics/**", "/actuator/prometheus", "/v3/api-docs/**", "/swagger-resources/**", "/swagger-ui/**", "/webjars/**"))
6347
.permitAll()
6448
.requestMatchers(antMatchers("/api/*/*/review", "/api/*/*/review/delete", "/api/user/publish", "/api/user/namespace/create"))
6549
.authenticated()
@@ -76,15 +60,20 @@ public SecurityFilterChain filterChain(HttpSecurity http, OAuth2UserServices use
7660
.csrf(configurer -> {
7761
configurer.ignoringRequestMatchers(antMatchers("/api/-/publish", "/api/-/namespace/create", "/api/-/query", "/vscode/**"));
7862
})
79-
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new Http403ForbiddenEntryPoint()))
80-
.oauth2Login(configurer -> {
81-
configurer.defaultSuccessUrl(redirectUrl);
82-
configurer.successHandler(new CustomAuthenticationSuccessHandler(redirectUrl));
83-
configurer.failureUrl(redirectUrl + "?auth-error");
84-
configurer.userInfoEndpoint(customizer -> customizer.oidcUserService(userServices.getOidc()).userService(userServices.getOauth2()));
85-
})
86-
.logout(configurer -> configurer.logoutSuccessUrl(redirectUrl))
87-
.build();
63+
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new Http403ForbiddenEntryPoint()));
64+
65+
if(userServices.canLogin()) {
66+
var redirectUrl = StringUtils.isEmpty(webuiUrl) ? "/" : webuiUrl;
67+
filterChain.oauth2Login(configurer -> {
68+
configurer.defaultSuccessUrl(redirectUrl);
69+
configurer.successHandler(new CustomAuthenticationSuccessHandler(redirectUrl));
70+
configurer.failureUrl(redirectUrl + "?auth-error");
71+
configurer.userInfoEndpoint(customizer -> customizer.oidcUserService(userServices.getOidc()).userService(userServices.getOauth2()));
72+
})
73+
.logout(configurer -> configurer.logoutSuccessUrl(redirectUrl));
74+
}
75+
76+
return filterChain.build();
8877
}
8978

9079
private RequestMatcher[] antMatchers(String... patterns)

server/src/main/java/org/eclipse/openvsx/security/TokenService.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@ public TokenService(
5252
this.clientRegistrationRepository = clientRegistrationRepository;
5353
}
5454

55+
private boolean isEnabled() {
56+
return clientRegistrationRepository != null;
57+
}
58+
5559
public AuthToken updateTokens(long userId, String registrationId, OAuth2AccessToken accessToken,
5660
OAuth2RefreshToken refreshToken) {
57-
var userData = entityManager.find(UserData.class, userId);
61+
var userData = isEnabled() ? entityManager.find(UserData.class, userId) : null;
5862
if (userData == null) {
5963
return null;
6064
}
6165

6266
switch (registrationId) {
6367
case "github": {
64-
if (clientRegistrationRepository == null) {
65-
// Handle the case where GitHub OAuth2 is not configured
66-
return updateGitHubToken(userData, null);
67-
}
6868
if (accessToken == null) {
6969
return updateGitHubToken(userData, null);
7070
}
@@ -124,6 +124,10 @@ private AuthToken updateEclipseToken(UserData userData, AuthToken token) {
124124
}
125125

126126
public AuthToken getActiveToken(UserData userData, String registrationId) {
127+
if(!isEnabled()) {
128+
return null;
129+
}
130+
127131
switch (registrationId) {
128132
case "github": {
129133
return userData.getGithubToken();
@@ -153,7 +157,7 @@ private boolean isExpired(Instant instant) {
153157
return instant != null && Instant.now().isAfter(instant);
154158
}
155159

156-
protected Pair<OAuth2AccessToken, OAuth2RefreshToken> refreshEclipseToken(AuthToken token) {
160+
private Pair<OAuth2AccessToken, OAuth2RefreshToken> refreshEclipseToken(AuthToken token) {
157161
if(token.refreshToken() == null || isExpired(token.refreshExpiresAt())) {
158162
return null;
159163
}

server/src/main/java/org/eclipse/openvsx/web/WebConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public void addCorsMappings(CorsRegistry registry) {
5353
.allowedOrigins(webuiUrl)
5454
.allowCredentials(true);
5555
}
56+
registry.addMapping("/can-login")
57+
.allowedOrigins(webuiUrl);
5658
registry.addMapping("/documents/**")
5759
.allowedOrigins("*");
5860
registry.addMapping("/api/**")

0 commit comments

Comments
 (0)