From 0e07da9884292f090695c37faeb205a24c0b5ce9 Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Sat, 25 Nov 2023 03:07:57 -0500 Subject: [PATCH 1/9] refactor: introduce `AuthUser` interface Replace the references to `OAuth2User` by `AuthUser`. This allows downstream extenders to more easily contribute alternative OAuth2 providers: If the expected data is stored in different attributes it will be possible to bridge it by implementing the proper `AuthUser`. --- .../java/org/eclipse/openvsx/UserService.java | 63 +++++++------ .../eclipse/openvsx/security/AuthUser.java | 48 ++++++++++ .../openvsx/security/AuthUserFactory.java | 20 +++++ .../openvsx/security/DefaultAuthUser.java | 74 +++++++++++++++ .../openvsx/security/OAuth2UserServices.java | 89 ++++++++----------- .../org/eclipse/openvsx/RegistryAPITest.java | 13 ++- .../java/org/eclipse/openvsx/UserAPITest.java | 12 ++- .../openvsx/adapter/VSCodeAPITest.java | 12 ++- .../eclipse/openvsx/admin/AdminAPITest.java | 76 +++++++++++----- .../openvsx/cache/CacheServiceTest.java | 45 ++++++---- .../openvsx/web/SitemapControllerTest.java | 11 ++- 11 files changed, 334 insertions(+), 129 deletions(-) create mode 100644 server/src/main/java/org/eclipse/openvsx/security/AuthUser.java create mode 100644 server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java create mode 100644 server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java diff --git a/server/src/main/java/org/eclipse/openvsx/UserService.java b/server/src/main/java/org/eclipse/openvsx/UserService.java index fd733701c..7a2b5a67b 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserService.java +++ b/server/src/main/java/org/eclipse/openvsx/UserService.java @@ -9,9 +9,15 @@ ********************************************************************************/ package org.eclipse.openvsx; -import com.google.common.base.Joiner; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; +import static org.eclipse.openvsx.cache.CacheService.CACHE_NAMESPACE_DETAILS_JSON; +import static org.eclipse.openvsx.util.UrlUtil.createApiUrl; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.StringUtils; import org.apache.tika.Tika; @@ -27,23 +33,24 @@ import org.eclipse.openvsx.json.NamespaceDetailsJson; import org.eclipse.openvsx.json.ResultJson; import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.security.AuthUser; import org.eclipse.openvsx.security.IdPrincipal; import org.eclipse.openvsx.storage.StorageUtilService; -import org.eclipse.openvsx.util.*; +import org.eclipse.openvsx.util.ErrorResultException; +import org.eclipse.openvsx.util.NamingUtil; +import org.eclipse.openvsx.util.NotFoundException; +import org.eclipse.openvsx.util.TempFile; +import org.eclipse.openvsx.util.TimeUtil; +import org.eclipse.openvsx.util.UrlUtil; import org.springframework.cache.annotation.CacheEvict; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; -import java.util.Objects; -import java.util.UUID; +import com.google.common.base.Joiner; -import static org.eclipse.openvsx.cache.CacheService.CACHE_NAMESPACE_DETAILS_JSON; -import static org.eclipse.openvsx.util.UrlUtil.createApiUrl; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; @Component public class UserService { @@ -80,44 +87,44 @@ public UserData findLoggedInUser() { } @Transactional - public UserData registerNewUser(OAuth2User oauth2User) { + public UserData registerNewUser(AuthUser authUser) { var user = new UserData(); - user.setProvider("github"); - user.setAuthId(oauth2User.getName()); - user.setLoginName(oauth2User.getAttribute("login")); - user.setFullName(oauth2User.getAttribute("name")); - user.setEmail(oauth2User.getAttribute("email")); - user.setProviderUrl(oauth2User.getAttribute("html_url")); - user.setAvatarUrl(oauth2User.getAttribute("avatar_url")); + user.setProvider(authUser.getProviderId()); + user.setAuthId(authUser.getAuthId()); + user.setLoginName(authUser.getLoginName()); + user.setFullName(authUser.getFullName()); + user.setEmail(authUser.getEmail()); + user.setProviderUrl(authUser.getProviderUrl()); + user.setAvatarUrl(authUser.getAvatarUrl()); entityManager.persist(user); return user; } @Transactional - public UserData updateExistingUser(UserData user, OAuth2User oauth2User) { - if ("github".equals(user.getProvider())) { + public UserData updateExistingUser(UserData user, AuthUser authUser) { + if (authUser.getProviderId().equals(user.getProvider())) { var updated = false; - String loginName = oauth2User.getAttribute("login"); + String loginName = authUser.getLoginName(); if (loginName != null && !loginName.equals(user.getLoginName())) { user.setLoginName(loginName); updated = true; } - String fullName = oauth2User.getAttribute("name"); + String fullName = authUser.getFullName(); if (fullName != null && !fullName.equals(user.getFullName())) { user.setFullName(fullName); updated = true; } - String email = oauth2User.getAttribute("email"); + String email = authUser.getEmail(); if (email != null && !email.equals(user.getEmail())) { user.setEmail(email); updated = true; } - String providerUrl = oauth2User.getAttribute("html_url"); + String providerUrl = authUser.getProviderUrl(); if (providerUrl != null && !providerUrl.equals(user.getProviderUrl())) { user.setProviderUrl(providerUrl); updated = true; } - String avatarUrl = oauth2User.getAttribute("avatar_url"); + String avatarUrl = authUser.getAvatarUrl(); if (avatarUrl != null && !avatarUrl.equals(user.getAvatarUrl())) { user.setAvatarUrl(avatarUrl); updated = true; @@ -315,4 +322,4 @@ public ResultJson deleteAccessToken(UserData user, long id) { token.setActive(false); return ResultJson.success("Deleted access token for user " + user.getLoginName() + "."); } -} \ No newline at end of file +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUser.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUser.java new file mode 100644 index 000000000..5a1026434 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUser.java @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2023 Ericsson and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +package org.eclipse.openvsx.security; + +/** + * Encapsulate information about freshly authenticated users. + * + * Different OAuth2 providers may return the same information with different + * attribute keys. This interface allows bridging arbitrary providers. + */ +public interface AuthUser { + /** + * @return Non-human readable unique identifier. + */ + String getAuthId(); + /** + * @return The user's avatar URL. Some services require post-processing to get the actual value for it + * (the value returned is a template and you need to remplace variables). + */ + String getAvatarUrl(); + /** + * @return The user's email. + */ + String getEmail(); + /** + * @return The user's full name (first and last names). + */ + String getFullName(); + /** + * @return The login name for the user. Human-readable unique name. AKA username. + */ + String getLoginName(); + /** + * @return The authentication provider unique name, e.g. `github`, `eclipse`, etc. + */ + String getProviderId(); + /** + * @return The authentication provider URL. + */ + String getProviderUrl(); +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java new file mode 100644 index 000000000..2ea73db53 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java @@ -0,0 +1,20 @@ +package org.eclipse.openvsx.security; + +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +@Service +public class AuthUserFactory { + + public AuthUser createAuthUser(String providerId, OAuth2User oauth2User) { + return new DefaultAuthUser( + oauth2User.getName(), + oauth2User.getAttribute("avatar_url"), + oauth2User.getAttribute("email"), + oauth2User.getAttribute("name"), + oauth2User.getAttribute("login"), + providerId, + oauth2User.getAttribute("html_url") + ); + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java b/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java new file mode 100644 index 000000000..27d85d2cd --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2023 Ericsson and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +package org.eclipse.openvsx.security; + +public class DefaultAuthUser implements AuthUser { + + final String authId; + final String avatarUrl; + final String email; + final String fullName; + final String loginName; + final String providerId; + final String providerUrl; + + public DefaultAuthUser( + final String authId, + final String avatarUrl, + final String email, + final String fullName, + final String loginName, + final String providerId, + final String providerUrl + ) { + this.authId = authId; + this.avatarUrl = avatarUrl; + this.email = email; + this.fullName = fullName; + this.loginName = loginName; + this.providerId = providerId; + this.providerUrl = providerUrl; + } + + @Override + public String getAuthId() { + return authId; + } + + @Override + public String getAvatarUrl() { + return avatarUrl; + } + + @Override + public String getEmail() { + return email; + } + + @Override + public String getFullName() { + return fullName; + } + + @Override + public String getLoginName() { + return loginName; + } + + @Override + public String getProviderId() { + return providerId; + } + + @Override + public String getProviderUrl() { + return providerUrl; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java index f749e381f..137525166 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java +++ b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java @@ -9,7 +9,16 @@ ********************************************************************************/ package org.eclipse.openvsx.security; -import jakarta.persistence.EntityManager; +import static java.util.Collections.emptyList; +import static org.eclipse.openvsx.security.CodedAuthException.ECLIPSE_MISMATCH_GITHUB_ID; +import static org.eclipse.openvsx.security.CodedAuthException.ECLIPSE_MISSING_GITHUB_ID; +import static org.eclipse.openvsx.security.CodedAuthException.INVALID_GITHUB_USER; +import static org.eclipse.openvsx.security.CodedAuthException.NEED_MAIN_LOGIN; +import static org.eclipse.openvsx.security.CodedAuthException.UNSUPPORTED_REGISTRATION; +import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList; + +import java.util.Collection; + import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.eclipse.EclipseService; @@ -20,22 +29,17 @@ import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import java.util.Collection; -import java.util.Collections; - -import static org.eclipse.openvsx.security.CodedAuthException.*; +import jakarta.persistence.EntityManager; @Service public class OAuth2UserServices { @@ -45,43 +49,27 @@ public class OAuth2UserServices { private final RepositoryService repositories; private final EntityManager entityManager; private final EclipseService eclipse; + private final AuthUserFactory authUserFactory; private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); - private final OAuth2UserService oauth2; - private final OAuth2UserService oidc; public OAuth2UserServices( UserService users, TokenService tokens, RepositoryService repositories, EntityManager entityManager, - EclipseService eclipse + EclipseService eclipse, + AuthUserFactory authUserFactory ) { this.users = users; this.tokens = tokens; this.repositories = repositories; this.entityManager = entityManager; this.eclipse = eclipse; - this.oauth2 = new OAuth2UserService() { - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - return OAuth2UserServices.this.loadUser(userRequest); - } - }; - this.oidc = new OAuth2UserService() { - @Override - public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { - return OAuth2UserServices.this.loadUser(userRequest); - } - }; + this.authUserFactory = authUserFactory; } - public OAuth2UserService getOauth2() { - return oauth2; - } - - public OAuth2UserService getOidc() { - return oidc; - } + public OAuth2UserService getOauth2() { return this::loadUser; } + public OAuth2UserService getOidc() { return this::loadUser; } @EventListener public void authenticationSucceeded(AuthenticationSuccessEvent event) { @@ -100,27 +88,25 @@ public void authenticationSucceeded(AuthenticationSuccessEvent event) { public IdPrincipal loadUser(OAuth2UserRequest userRequest) { var registrationId = userRequest.getClientRegistration().getRegistrationId(); - switch (registrationId) { - case "github": - return loadGitHubUser(userRequest); - case "eclipse": - return loadEclipseUser(userRequest); - default: - throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION); - } + return switch (registrationId) { + case "github" -> loadGitHubUser(userRequest); + case "eclipse" -> loadEclipseUser(userRequest); + default -> throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION); + }; } private IdPrincipal loadGitHubUser(OAuth2UserRequest userRequest) { - var authUser = delegate.loadUser(userRequest); - String loginName = authUser.getAttribute("login"); + var authUser = authUserFactory.createAuthUser("github", delegate.loadUser(userRequest)); + String loginName = authUser.getLoginName(); if (StringUtils.isEmpty(loginName)) throw new CodedAuthException("Invalid login: missing 'login' field.", INVALID_GITHUB_USER); - var userData = repositories.findUserByLoginName("github", loginName); - if (userData == null) + var userData = repositories.findUserByLoginName(authUser.getProviderId(), loginName); + if (userData == null) { userData = users.registerNewUser(authUser); - else + } else { users.updateExistingUser(userData, authUser); - return new IdPrincipal(userData.getId(), authUser.getName(), getAuthorities(userData)); + } + return new IdPrincipal(userData.getId(), authUser.getAuthId(), getAuthorities(userData)); } private IdPrincipal loadEclipseUser(OAuth2UserRequest userRequest) { @@ -155,15 +141,10 @@ private IdPrincipal loadEclipseUser(OAuth2UserRequest userRequest) { } private Collection getAuthorities(UserData userData) { - var role = userData.getRole(); - switch (role != null ? role : "") { - case UserData.ROLE_ADMIN: - return AuthorityUtils.createAuthorityList("ROLE_ADMIN"); - case UserData.ROLE_PRIVILEGED: - return AuthorityUtils.createAuthorityList("ROLE_PRIVILEGED"); - default: - return Collections.emptyList(); - } + return switch (userData.getRole()) { + case UserData.ROLE_ADMIN -> createAuthorityList("ROLE_ADMIN"); + case UserData.ROLE_PRIVILEGED -> createAuthorityList("ROLE_PRIVILEGED"); + default -> emptyList(); + }; } - -} \ No newline at end of file +} diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index d39abcdbd..f6f04c806 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -29,6 +29,7 @@ import org.eclipse.openvsx.search.ExtensionSearch; import org.eclipse.openvsx.search.ISearchService; import org.eclipse.openvsx.search.SearchUtilService; +import org.eclipse.openvsx.security.AuthUserFactory; import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; @@ -2399,9 +2400,10 @@ OAuth2UserServices oauth2UserServices( TokenService tokens, RepositoryService repositories, EntityManager entityManager, - EclipseService eclipse + EclipseService eclipse, + AuthUserFactory authUserFactory ) { - return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse); + return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse, authUserFactory); } @Bean @@ -2525,5 +2527,10 @@ PublishExtensionVersionHandler publishExtensionVersionHandler( extensionControl ); } + + @Bean + AuthUserFactory authUserFactory() { + return new AuthUserFactory(); + } } -} \ No newline at end of file +} diff --git a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java index 2d96665fc..c6590ee48 100644 --- a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java @@ -38,6 +38,7 @@ import org.eclipse.openvsx.json.ResultJson; import org.eclipse.openvsx.json.UserJson; import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.security.AuthUserFactory; import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; @@ -563,9 +564,10 @@ OAuth2UserServices oauth2UserServices( TokenService tokens, RepositoryService repositories, EntityManager entityManager, - EclipseService eclipse + EclipseService eclipse, + AuthUserFactory authUserFactory ) { - return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse); + return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse, authUserFactory); } @Bean @@ -581,6 +583,10 @@ TokenService tokenService( LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); } + + @Bean + AuthUserFactory authUserFactory() { + return new AuthUserFactory(); + } } - } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java index 24172cee1..e8e32c813 100644 --- a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java @@ -27,6 +27,7 @@ import org.eclipse.openvsx.search.ExtensionSearch; import org.eclipse.openvsx.search.ISearchService; import org.eclipse.openvsx.search.SearchUtilService; +import org.eclipse.openvsx.security.AuthUserFactory; import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; @@ -877,9 +878,10 @@ OAuth2UserServices oauth2UserServices( TokenService tokens, RepositoryService repositories, EntityManager entityManager, - EclipseService eclipse + EclipseService eclipse, + AuthUserFactory authUserFactory ) { - return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse); + return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse, authUserFactory); } @Bean @@ -966,6 +968,10 @@ LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator( FilesCacheKeyGenerator filesCacheKeyGenerator() { return new FilesCacheKeyGenerator(); } - } + @Bean + AuthUserFactory authUserFactory() { + return new AuthUserFactory(); + } + } } diff --git a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java index b9a65926c..3a3da1f39 100644 --- a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java @@ -9,20 +9,57 @@ ********************************************************************************/ package org.eclipse.openvsx.admin; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import org.eclipse.openvsx.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.eclipse.openvsx.ExtensionService; +import org.eclipse.openvsx.ExtensionValidator; +import org.eclipse.openvsx.LocalRegistryService; +import org.eclipse.openvsx.MockTransactionTemplate; +import org.eclipse.openvsx.UpstreamRegistryService; +import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.adapter.VSCodeIdService; import org.eclipse.openvsx.cache.CacheService; import org.eclipse.openvsx.cache.LatestExtensionVersionCacheKeyGenerator; import org.eclipse.openvsx.eclipse.EclipseService; -import org.eclipse.openvsx.entities.*; -import org.eclipse.openvsx.json.*; +import org.eclipse.openvsx.entities.AdminStatistics; +import org.eclipse.openvsx.entities.Extension; +import org.eclipse.openvsx.entities.ExtensionVersion; +import org.eclipse.openvsx.entities.Namespace; +import org.eclipse.openvsx.entities.NamespaceMembership; +import org.eclipse.openvsx.entities.PersonalAccessToken; +import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.json.AdminStatisticsJson; +import org.eclipse.openvsx.json.ChangeNamespaceJson; +import org.eclipse.openvsx.json.ExtensionJson; +import org.eclipse.openvsx.json.NamespaceJson; +import org.eclipse.openvsx.json.NamespaceMembershipJson; +import org.eclipse.openvsx.json.NamespaceMembershipListJson; +import org.eclipse.openvsx.json.ResultJson; +import org.eclipse.openvsx.json.UserJson; +import org.eclipse.openvsx.json.UserPublishInfoJson; import org.eclipse.openvsx.publish.ExtensionVersionIntegrityService; import org.eclipse.openvsx.publish.PublishExtensionVersionHandler; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; +import org.eclipse.openvsx.security.AuthUserFactory; import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; @@ -48,20 +85,11 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.support.TransactionTemplate; -import jakarta.persistence.EntityManager; -import java.time.LocalDateTime; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import jakarta.persistence.EntityManager; @WebMvcTest(AdminAPI.class) @AutoConfigureWebClient @@ -1197,9 +1225,10 @@ OAuth2UserServices oauth2UserServices( TokenService tokens, RepositoryService repositories, EntityManager entityManager, - EclipseService eclipse + EclipseService eclipse, + AuthUserFactory authUserFactory ) { - return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse); + return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse, authUserFactory); } @Bean @@ -1324,5 +1353,10 @@ VersionService versionService() { LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); } + + @Bean + AuthUserFactory authUserFactory() { + return new AuthUserFactory(); + } } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java b/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java index 9beb6ad4e..3e787e14c 100644 --- a/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java @@ -9,16 +9,36 @@ * ****************************************************************************** */ package org.eclipse.openvsx.cache; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; +import static org.eclipse.openvsx.cache.CacheService.CACHE_EXTENSION_JSON; +import static org.eclipse.openvsx.entities.FileResource.DOWNLOAD; +import static org.eclipse.openvsx.entities.FileResource.STORAGE_LOCAL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + import org.eclipse.openvsx.ExtensionService; import org.eclipse.openvsx.LocalRegistryService; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.admin.AdminService; -import org.eclipse.openvsx.entities.*; +import org.eclipse.openvsx.entities.Extension; +import org.eclipse.openvsx.entities.ExtensionReview; +import org.eclipse.openvsx.entities.ExtensionVersion; +import org.eclipse.openvsx.entities.FileResource; +import org.eclipse.openvsx.entities.Namespace; +import org.eclipse.openvsx.entities.PersonalAccessToken; +import org.eclipse.openvsx.entities.UserData; import org.eclipse.openvsx.json.ExtensionJson; import org.eclipse.openvsx.json.ReviewJson; import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.security.AuthUserFactory; import org.eclipse.openvsx.security.IdPrincipal; import org.eclipse.openvsx.util.TempFile; import org.eclipse.openvsx.util.TimeUtil; @@ -35,17 +55,8 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import java.io.IOException; -import java.nio.file.Files; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -import static org.eclipse.openvsx.cache.CacheService.CACHE_EXTENSION_JSON; -import static org.eclipse.openvsx.entities.FileResource.DOWNLOAD; -import static org.eclipse.openvsx.entities.FileResource.STORAGE_LOCAL; -import static org.junit.jupiter.api.Assertions.*; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @@ -72,6 +83,9 @@ class CacheServiceTest { @Autowired RepositoryService repositories; + @Autowired + AuthUserFactory authUserFactory; + @Test @Transactional void testGetExtension() throws IOException { @@ -118,7 +132,8 @@ void testUpdateExistingUser() throws IOException { var user = extVersion.getPublishedWith().getUser(); var oauthUser = new DefaultOAuth2User(authorities, attributes, "name"); - users.updateExistingUser(user, oauthUser); + var authUser = authUserFactory.createAuthUser(authority, oauthUser); + users.updateExistingUser(user, authUser); assertNull(cache.getCache(CACHE_EXTENSION_JSON).get(cacheKey, ExtensionJson.class)); var json = registry.getExtension(namespace.getName(), extension.getName(), extVersion.getTargetPlatform(), extVersion.getVersion()); diff --git a/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java b/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java index 9c1a107dd..e250fccad 100644 --- a/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java +++ b/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java @@ -14,6 +14,7 @@ import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.eclipse.EclipseService; import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.security.AuthUserFactory; import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; @@ -75,14 +76,20 @@ OAuth2UserServices oauth2UserServices( TokenService tokens, RepositoryService repositories, EntityManager entityManager, - EclipseService eclipse + EclipseService eclipse, + AuthUserFactory authUserFactory ) { - return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse); + return new OAuth2UserServices(users, tokens, repositories, entityManager, eclipse, authUserFactory); } @Bean SitemapService sitemapService(RepositoryService repositories) { return new SitemapService(repositories); } + + @Bean + AuthUserFactory authUserFactory() { + return new AuthUserFactory(); + } } } From 6d38583ee974fd0be3a90af95b788d9d723fe1ff Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Mon, 9 Dec 2024 10:28:45 -0500 Subject: [PATCH 2/9] server: configure attribute names for auth Introduce a configuration to define attribute names to use when mapping attributes from an auth provider. --- .../java/org/eclipse/openvsx/OVSXConfig.java | 100 ++++++++++++++++++ .../openvsx/security/AuthUserFactory.java | 25 ++++- .../org/eclipse/openvsx/RegistryAPITest.java | 11 +- .../java/org/eclipse/openvsx/UserAPITest.java | 11 +- .../openvsx/adapter/VSCodeAPITest.java | 12 ++- .../eclipse/openvsx/admin/AdminAPITest.java | 12 ++- .../openvsx/web/SitemapControllerTest.java | 13 ++- 7 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 server/src/main/java/org/eclipse/openvsx/OVSXConfig.java diff --git a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java new file mode 100644 index 000000000..9ebdb407f --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java @@ -0,0 +1,100 @@ +package org.eclipse.openvsx; + +import static java.util.Collections.emptyMap; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * TODO: use lombok to reduce boilerplate, it's very needed. + */ +@Configuration +@ConfigurationProperties(prefix = "ovsx") +public class OVSXConfig { + + private AuthConfig authConfig = new AuthConfig(); + + public AuthConfig getAuthConfig() { + return authConfig; + } + + public void setAuthConfig(AuthConfig authConfig) { + this.authConfig = authConfig; + } + + public static class AuthConfig { + + /** + * Configuration example: + *

+         * ovsx:
+         *   auth:
+         *     attribute-names:
+         *       github: # provider name
+         *         login: field-returned-by-your-provider
+         * 
+ */ + private Map attributeNames = emptyMap(); + + public Map getAttributeNames() { + return attributeNames; + } + + public void setAttributeNames(Map attributeNames) { + this.attributeNames = attributeNames; + } + + public static class AttributeNames { + + public static final AttributeNames EMPTY = new AttributeNames(); + + private String avatarUrl = "avatar_url"; + private String email = "email"; + private String fullName = "name"; + private String loginName = "login"; + private String providerUrl = "html_url"; + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getLoginName() { + return loginName; + } + + public void setLoginName(String loginName) { + this.loginName = loginName; + } + + public String getProviderUrl() { + return providerUrl; + } + + public void setProviderUrl(String providerUrl) { + this.providerUrl = providerUrl; + } + } + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java index 2ea73db53..6bd259503 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java +++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java @@ -1,20 +1,35 @@ package org.eclipse.openvsx.security; +import org.eclipse.openvsx.OVSXConfig; +import org.eclipse.openvsx.OVSXConfig.AuthConfig.AttributeNames; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; @Service public class AuthUserFactory { + private final OVSXConfig config; + + public AuthUserFactory( + OVSXConfig config + ) { + this.config = config; + } + public AuthUser createAuthUser(String providerId, OAuth2User oauth2User) { + var attributeNames = getAttributeNames(providerId); return new DefaultAuthUser( oauth2User.getName(), - oauth2User.getAttribute("avatar_url"), - oauth2User.getAttribute("email"), - oauth2User.getAttribute("name"), - oauth2User.getAttribute("login"), + oauth2User.getAttribute(attributeNames.getAvatarUrl()), + oauth2User.getAttribute(attributeNames.getEmail()), + oauth2User.getAttribute(attributeNames.getFullName()), + oauth2User.getAttribute(attributeNames.getLoginName()), providerId, - oauth2User.getAttribute("html_url") + oauth2User.getAttribute(attributeNames.getProviderUrl()) ); } + + private AttributeNames getAttributeNames(String provider) { + return config.getAuthConfig().getAttributeNames().getOrDefault(provider, AttributeNames.EMPTY); + } } diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index f6f04c806..afbfee13f 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -2529,8 +2529,15 @@ PublishExtensionVersionHandler publishExtensionVersionHandler( } @Bean - AuthUserFactory authUserFactory() { - return new AuthUserFactory(); + AuthUserFactory authUserFactory( + OVSXConfig config + ) { + return new AuthUserFactory(config); + } + + @Bean + OVSXConfig ovsxConfig() { + return new OVSXConfig(); } } } diff --git a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java index c6590ee48..78dfa3964 100644 --- a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java @@ -585,8 +585,15 @@ LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator( } @Bean - AuthUserFactory authUserFactory() { - return new AuthUserFactory(); + AuthUserFactory authUserFactory( + OVSXConfig config + ) { + return new AuthUserFactory(config); + } + + @Bean + OVSXConfig ovsxConfig() { + return new OVSXConfig(); } } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java index e8e32c813..2f80963a5 100644 --- a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java @@ -16,6 +16,7 @@ import jakarta.persistence.EntityManager; import org.eclipse.openvsx.ExtensionValidator; import org.eclipse.openvsx.MockTransactionTemplate; +import org.eclipse.openvsx.OVSXConfig; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.cache.CacheService; import org.eclipse.openvsx.cache.FilesCacheKeyGenerator; @@ -970,8 +971,15 @@ FilesCacheKeyGenerator filesCacheKeyGenerator() { } @Bean - AuthUserFactory authUserFactory() { - return new AuthUserFactory(); + AuthUserFactory authUserFactory( + OVSXConfig config + ) { + return new AuthUserFactory(config); + } + + @Bean + OVSXConfig ovsxConfig() { + return new OVSXConfig(); } } } diff --git a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java index 3a3da1f39..a8df6ac29 100644 --- a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java @@ -33,6 +33,7 @@ import org.eclipse.openvsx.ExtensionValidator; import org.eclipse.openvsx.LocalRegistryService; import org.eclipse.openvsx.MockTransactionTemplate; +import org.eclipse.openvsx.OVSXConfig; import org.eclipse.openvsx.UpstreamRegistryService; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.adapter.VSCodeIdService; @@ -1355,8 +1356,15 @@ LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator( } @Bean - AuthUserFactory authUserFactory() { - return new AuthUserFactory(); + AuthUserFactory authUserFactory( + OVSXConfig config + ) { + return new AuthUserFactory(config); + } + + @Bean + OVSXConfig ovsxConfig() { + return new OVSXConfig(); } } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java b/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java index e250fccad..3e75ec8a8 100644 --- a/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java +++ b/server/src/test/java/org/eclipse/openvsx/web/SitemapControllerTest.java @@ -11,6 +11,8 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import jakarta.persistence.EntityManager; + +import org.eclipse.openvsx.OVSXConfig; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.eclipse.EclipseService; import org.eclipse.openvsx.repositories.RepositoryService; @@ -88,8 +90,15 @@ SitemapService sitemapService(RepositoryService repositories) { } @Bean - AuthUserFactory authUserFactory() { - return new AuthUserFactory(); + AuthUserFactory authUserFactory( + OVSXConfig config + ) { + return new AuthUserFactory(config); + } + + @Bean + OVSXConfig ovsxConfig() { + return new OVSXConfig(); } } } From 405ae7bbc3a8d0a57a450259a53301a3367b6e4b Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Mon, 9 Dec 2024 11:00:40 -0500 Subject: [PATCH 3/9] server: make login more generic Allow configuring the auth for arbitrary providers (other than github). --- .../java/org/eclipse/openvsx/OVSXConfig.java | 10 +++---- .../openvsx/security/AuthUserFactory.java | 25 +++++++++++++++- .../openvsx/security/CodedAuthException.java | 23 +++++++++------ .../openvsx/security/OAuth2UserServices.java | 29 +++++++++++-------- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java index 9ebdb407f..1c3003cd8 100644 --- a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java +++ b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java @@ -50,11 +50,11 @@ public static class AttributeNames { public static final AttributeNames EMPTY = new AttributeNames(); - private String avatarUrl = "avatar_url"; - private String email = "email"; - private String fullName = "name"; - private String loginName = "login"; - private String providerUrl = "html_url"; + private String avatarUrl; + private String email; + private String fullName; + private String loginName; + private String providerUrl; public String getAvatarUrl() { return avatarUrl; diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java index 6bd259503..7ec9f7bfc 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java +++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java @@ -1,5 +1,7 @@ package org.eclipse.openvsx.security; +import java.util.NoSuchElementException; + import org.eclipse.openvsx.OVSXConfig; import org.eclipse.openvsx.OVSXConfig.AuthConfig.AttributeNames; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -8,6 +10,16 @@ @Service public class AuthUserFactory { + private static final AttributeNames GITHUB_ATTRIBUTES = new AttributeNames(); + + static { + GITHUB_ATTRIBUTES.setAvatarUrl("avatar_url"); + GITHUB_ATTRIBUTES.setEmail("email"); + GITHUB_ATTRIBUTES.setFullName("name"); + GITHUB_ATTRIBUTES.setLoginName("login"); + GITHUB_ATTRIBUTES.setProviderUrl("html_url"); + } + private final OVSXConfig config; public AuthUserFactory( @@ -29,7 +41,18 @@ public AuthUser createAuthUser(String providerId, OAuth2User oauth2User) { ); } + /** + * @param provider The provider to get the attribute mappings for. + * @return The relevant attribute mappings. + */ private AttributeNames getAttributeNames(String provider) { - return config.getAuthConfig().getAttributeNames().getOrDefault(provider, AttributeNames.EMPTY); + var attributeNames = config.getAuthConfig().getAttributeNames().get(provider); + if (attributeNames == null) { + return switch (provider) { + case "github" -> GITHUB_ATTRIBUTES; + default -> throw new NoSuchElementException("No attributes found for provider: " + provider); + }; + } + return attributeNames; } } diff --git a/server/src/main/java/org/eclipse/openvsx/security/CodedAuthException.java b/server/src/main/java/org/eclipse/openvsx/security/CodedAuthException.java index 23df99eee..80465b762 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/CodedAuthException.java +++ b/server/src/main/java/org/eclipse/openvsx/security/CodedAuthException.java @@ -9,10 +9,10 @@ ********************************************************************************/ package org.eclipse.openvsx.security; -import org.springframework.security.core.AuthenticationException; - import java.io.Serial; +import org.springframework.security.core.AuthenticationException; + /** * Authentication exception that contains a code to be used in user interfaces to * provide more help for resolving the problem. @@ -21,22 +21,27 @@ public class CodedAuthException extends AuthenticationException { public static final String UNSUPPORTED_REGISTRATION = "unsupported-registration"; public static final String INVALID_GITHUB_USER = "invalid-github-user"; + public static final String INVALID_USER = "invalid-user"; public static final String NEED_MAIN_LOGIN = "need-main-login"; public static final String ECLIPSE_MISSING_GITHUB_ID = "eclipse-missing-github-id"; public static final String ECLIPSE_MISMATCH_GITHUB_ID = "eclipse-mismatch-github-id"; @Serial private static final long serialVersionUID = 1L; - + private final String code; - public CodedAuthException(String message, String code) { + public CodedAuthException(String message, String code) { super(message); this.code = code; } - + + public CodedAuthException(String message, String code, Throwable cause) { + super(message, cause); + this.code = code; + } + public String getCode() { - return code; - } - -} \ No newline at end of file + return code; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java index 137525166..7b48c14c9 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java +++ b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java @@ -12,7 +12,7 @@ import static java.util.Collections.emptyList; import static org.eclipse.openvsx.security.CodedAuthException.ECLIPSE_MISMATCH_GITHUB_ID; import static org.eclipse.openvsx.security.CodedAuthException.ECLIPSE_MISSING_GITHUB_ID; -import static org.eclipse.openvsx.security.CodedAuthException.INVALID_GITHUB_USER; +import static org.eclipse.openvsx.security.CodedAuthException.INVALID_USER; import static org.eclipse.openvsx.security.CodedAuthException.NEED_MAIN_LOGIN; import static org.eclipse.openvsx.security.CodedAuthException.UNSUPPORTED_REGISTRATION; import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList; @@ -88,19 +88,24 @@ public void authenticationSucceeded(AuthenticationSuccessEvent event) { public IdPrincipal loadUser(OAuth2UserRequest userRequest) { var registrationId = userRequest.getClientRegistration().getRegistrationId(); - return switch (registrationId) { - case "github" -> loadGitHubUser(userRequest); - case "eclipse" -> loadEclipseUser(userRequest); - default -> throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION); - }; + if (registrationId == "eclipse") { + return loadEclipseUser(userRequest); + } else try { + return loadGenericUser(userRequest); + } catch (Throwable t) { + throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION, t); + } } - private IdPrincipal loadGitHubUser(OAuth2UserRequest userRequest) { - var authUser = authUserFactory.createAuthUser("github", delegate.loadUser(userRequest)); - String loginName = authUser.getLoginName(); - if (StringUtils.isEmpty(loginName)) - throw new CodedAuthException("Invalid login: missing 'login' field.", INVALID_GITHUB_USER); - var userData = repositories.findUserByLoginName(authUser.getProviderId(), loginName); + private IdPrincipal loadGenericUser(OAuth2UserRequest userRequest) { + var authUser = authUserFactory.createAuthUser( + userRequest.getClientRegistration().getRegistrationId(), + delegate.loadUser(userRequest) + ); + if (StringUtils.isEmpty(authUser.getLoginName())) { + throw new CodedAuthException("Invalid login: missing 'login' field.", INVALID_USER); + } + var userData = repositories.findUserByLoginName(authUser.getProviderId(), authUser.getLoginName()); if (userData == null) { userData = users.registerNewUser(authUser); } else { From bac3a3eb59b358e9d1a10d6dfc0d41289d1b9032 Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Mon, 9 Dec 2024 12:02:56 -0500 Subject: [PATCH 4/9] server: fix auth config attribute name --- .../src/main/java/org/eclipse/openvsx/OVSXConfig.java | 10 +++++----- .../org/eclipse/openvsx/security/AuthUserFactory.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java index 1c3003cd8..9e6a86add 100644 --- a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java +++ b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java @@ -14,14 +14,14 @@ @ConfigurationProperties(prefix = "ovsx") public class OVSXConfig { - private AuthConfig authConfig = new AuthConfig(); + private AuthConfig auth = new AuthConfig(); - public AuthConfig getAuthConfig() { - return authConfig; + public AuthConfig getAuth() { + return auth; } - public void setAuthConfig(AuthConfig authConfig) { - this.authConfig = authConfig; + public void setAuth(AuthConfig authConfig) { + this.auth = authConfig; } public static class AuthConfig { diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java index 7ec9f7bfc..061bfc670 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java +++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java @@ -46,7 +46,7 @@ public AuthUser createAuthUser(String providerId, OAuth2User oauth2User) { * @return The relevant attribute mappings. */ private AttributeNames getAttributeNames(String provider) { - var attributeNames = config.getAuthConfig().getAttributeNames().get(provider); + var attributeNames = config.getAuth().getAttributeNames().get(provider); if (attributeNames == null) { return switch (provider) { case "github" -> GITHUB_ATTRIBUTES; From da3ee40266b15c6695e31c52d16c57fd8692cf6b Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Tue, 10 Dec 2024 09:01:12 -0500 Subject: [PATCH 5/9] server: handle OidcUserRequest + fix switch bug --- .../openvsx/security/OAuth2UserServices.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java index 7b48c14c9..28e7c05d1 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java +++ b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java @@ -10,6 +10,9 @@ package org.eclipse.openvsx.security; import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNullElse; +import static org.eclipse.openvsx.entities.UserData.ROLE_ADMIN; +import static org.eclipse.openvsx.entities.UserData.ROLE_PRIVILEGED; import static org.eclipse.openvsx.security.CodedAuthException.ECLIPSE_MISMATCH_GITHUB_ID; import static org.eclipse.openvsx.security.CodedAuthException.ECLIPSE_MISSING_GITHUB_ID; import static org.eclipse.openvsx.security.CodedAuthException.INVALID_USER; @@ -18,6 +21,7 @@ import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList; import java.util.Collection; +import java.util.NoSuchElementException; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.UserService; @@ -32,6 +36,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; @@ -44,13 +49,15 @@ @Service public class OAuth2UserServices { + private static final DefaultOAuth2UserService springOAuth2UserService = new DefaultOAuth2UserService(); + private static final OidcUserService springOidcUserService = new OidcUserService(); + private final UserService users; private final TokenService tokens; private final RepositoryService repositories; private final EntityManager entityManager; private final EclipseService eclipse; private final AuthUserFactory authUserFactory; - private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); public OAuth2UserServices( UserService users, @@ -92,16 +99,20 @@ public IdPrincipal loadUser(OAuth2UserRequest userRequest) { return loadEclipseUser(userRequest); } else try { return loadGenericUser(userRequest); - } catch (Throwable t) { - throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION, t); + } catch (NoSuchElementException e) { + throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION, e); } } + private OAuth2User springLoadUser(OAuth2UserRequest userRequest) { + return userRequest instanceof OidcUserRequest oidcRequest + ? springOidcUserService.loadUser(oidcRequest) + : springOAuth2UserService.loadUser(userRequest); + } + private IdPrincipal loadGenericUser(OAuth2UserRequest userRequest) { - var authUser = authUserFactory.createAuthUser( - userRequest.getClientRegistration().getRegistrationId(), - delegate.loadUser(userRequest) - ); + var registrationId = userRequest.getClientRegistration().getRegistrationId(); + var authUser = authUserFactory.createAuthUser(registrationId, springLoadUser(userRequest)); if (StringUtils.isEmpty(authUser.getLoginName())) { throw new CodedAuthException("Invalid login: missing 'login' field.", INVALID_USER); } @@ -146,9 +157,9 @@ private IdPrincipal loadEclipseUser(OAuth2UserRequest userRequest) { } private Collection getAuthorities(UserData userData) { - return switch (userData.getRole()) { - case UserData.ROLE_ADMIN -> createAuthorityList("ROLE_ADMIN"); - case UserData.ROLE_PRIVILEGED -> createAuthorityList("ROLE_PRIVILEGED"); + return switch (requireNonNullElse(userData.getRole(), "")) { + case ROLE_ADMIN -> createAuthorityList("ROLE_ADMIN"); + case ROLE_PRIVILEGED -> createAuthorityList("ROLE_PRIVILEGED"); default -> emptyList(); }; } From 1abe65951f9e92c584174dc169b25a98c012488e Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Tue, 10 Dec 2024 09:16:48 -0500 Subject: [PATCH 6/9] server+webui: use dynamic /login endpoint --- .../src/main/java/org/eclipse/openvsx/OVSXConfig.java | 10 ++++++++++ server/src/main/java/org/eclipse/openvsx/UserAPI.java | 7 +++++-- webui/src/extension-registry-service.ts | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java index 9e6a86add..e3bb7eab9 100644 --- a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java +++ b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java @@ -26,6 +26,8 @@ public void setAuth(AuthConfig authConfig) { public static class AuthConfig { + private String provider = "github"; + /** * Configuration example: *

@@ -38,6 +40,14 @@ public static class AuthConfig {
          */
         private Map attributeNames = emptyMap();
 
+        public String getProvider() {
+            return provider;
+        }
+
+        public void setProvider(String provider) {
+            this.provider = provider;
+        }
+
         public Map getAttributeNames() {
             return attributeNames;
         }
diff --git a/server/src/main/java/org/eclipse/openvsx/UserAPI.java b/server/src/main/java/org/eclipse/openvsx/UserAPI.java
index 4564de252..b286f4de4 100644
--- a/server/src/main/java/org/eclipse/openvsx/UserAPI.java
+++ b/server/src/main/java/org/eclipse/openvsx/UserAPI.java
@@ -53,17 +53,20 @@ public class UserAPI {
     private final UserService users;
     private final EclipseService eclipse;
     private final StorageUtilService storageUtil;
+    private final OVSXConfig config;
 
     public UserAPI(
             RepositoryService repositories,
             UserService users,
             EclipseService eclipse,
-            StorageUtilService storageUtil
+            StorageUtilService storageUtil,
+            OVSXConfig config
     ) {
         this.repositories = repositories;
         this.users = users;
         this.eclipse = eclipse;
         this.storageUtil = storageUtil;
+        this.config = config;
     }
 
     /**
@@ -73,7 +76,7 @@ public UserAPI(
         path = "/login"
     )
     public ModelAndView login(ModelMap model) {
-        return new ModelAndView("redirect:/oauth2/authorization/github", model);
+        return new ModelAndView("redirect:/oauth2/authorization/" + config.getAuth().getProvider(), model);
     }
 
     /**
diff --git a/webui/src/extension-registry-service.ts b/webui/src/extension-registry-service.ts
index 6bb3144fb..cddc0c9ec 100644
--- a/webui/src/extension-registry-service.ts
+++ b/webui/src/extension-registry-service.ts
@@ -25,7 +25,7 @@ export class ExtensionRegistryService {
     }
 
     getLoginUrl(): string {
-        return createAbsoluteURL([this.serverUrl, 'oauth2', 'authorization', 'github']);
+        return createAbsoluteURL([this.serverUrl, 'login']);
     }
 
     getLogoutUrl(): string {

From bde567c13e42aa96dd7f53e4171ccf8665d9ee00 Mon Sep 17 00:00:00 2001
From: Paul Marechal 
Date: Wed, 11 Dec 2024 09:21:47 -0500
Subject: [PATCH 7/9] server: cleanup

---
 .../java/org/eclipse/openvsx/OVSXConfig.java  |  2 -
 .../openvsx/security/AuthUserFactory.java     | 69 +++++++++++--------
 .../openvsx/security/DefaultAuthUser.java     | 14 ++--
 .../openvsx/security/OAuth2UserServices.java  | 25 ++++---
 4 files changed, 61 insertions(+), 49 deletions(-)

diff --git a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java
index e3bb7eab9..fc392739d 100644
--- a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java
+++ b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java
@@ -58,8 +58,6 @@ public void setAttributeNames(Map attributeNames) {
 
         public static class AttributeNames {
 
-            public static final AttributeNames EMPTY = new AttributeNames();
-
             private String avatarUrl;
             private String email;
             private String fullName;
diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java
index 061bfc670..ada4fb1e0 100644
--- a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java
+++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java
@@ -1,58 +1,69 @@
 package org.eclipse.openvsx.security;
 
-import java.util.NoSuchElementException;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.eclipse.openvsx.OVSXConfig;
 import org.eclipse.openvsx.OVSXConfig.AuthConfig.AttributeNames;
 import org.springframework.security.oauth2.core.user.OAuth2User;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 
-@Service
+@Component
 public class AuthUserFactory {
 
-    private static final AttributeNames GITHUB_ATTRIBUTES = new AttributeNames();
+    protected static final Map DEFAULTS = new HashMap<>();
+
+    public static class MissingProvider extends Exception {
+        public MissingProvider(String provider) { super("Missing configuration: ovsx.auth.attribute-names." + provider); }
+    }
 
     static {
-        GITHUB_ATTRIBUTES.setAvatarUrl("avatar_url");
-        GITHUB_ATTRIBUTES.setEmail("email");
-        GITHUB_ATTRIBUTES.setFullName("name");
-        GITHUB_ATTRIBUTES.setLoginName("login");
-        GITHUB_ATTRIBUTES.setProviderUrl("html_url");
+        var github = new AttributeNames();
+        github.setAvatarUrl("avatar_url");
+        github.setEmail("email");
+        github.setFullName("name");
+        github.setLoginName("login");
+        github.setProviderUrl("html_url");
+        DEFAULTS.put("github", github);
     }
 
-    private final OVSXConfig config;
+    protected final OVSXConfig config;
 
-    public AuthUserFactory(
-        OVSXConfig config
-    ) {
+    public AuthUserFactory(OVSXConfig config) {
         this.config = config;
     }
 
-    public AuthUser createAuthUser(String providerId, OAuth2User oauth2User) {
-        var attributeNames = getAttributeNames(providerId);
+    /**
+     * @param provider The configured OAuth2 provider from which the user object came from.
+     * @param user The OAuth2 user object to get attributes from.
+     * @return An {@link AuthUser} instance with attributes set according to the current configuration.
+     * @throws MissingProvider if an attribute name mapping is missing for the given provider.
+     */
+    public AuthUser createAuthUser(String provider, OAuth2User user) throws MissingProvider {
+        var attr = getAttributeNames(provider);
         return new DefaultAuthUser(
-            oauth2User.getName(),
-            oauth2User.getAttribute(attributeNames.getAvatarUrl()),
-            oauth2User.getAttribute(attributeNames.getEmail()),
-            oauth2User.getAttribute(attributeNames.getFullName()),
-            oauth2User.getAttribute(attributeNames.getLoginName()),
-            providerId,
-            oauth2User.getAttribute(attributeNames.getProviderUrl())
+            user.getName(),
+            getAttribute(user, attr.getAvatarUrl()),
+            getAttribute(user, attr.getEmail()),
+            getAttribute(user, attr.getFullName()),
+            getAttribute(user, attr.getLoginName()),
+            provider,
+            getAttribute(user, attr.getProviderUrl())
         );
     }
 
+    protected  T getAttribute(OAuth2User oauth2User, String attribute) {
+        return attribute == null ? null : oauth2User.getAttribute(attribute);
+    }
+
     /**
      * @param provider The provider to get the attribute mappings for.
      * @return The relevant attribute mappings.
      */
-    private AttributeNames getAttributeNames(String provider) {
+    protected AttributeNames getAttributeNames(String provider) throws MissingProvider {
         var attributeNames = config.getAuth().getAttributeNames().get(provider);
-        if (attributeNames == null) {
-            return switch (provider) {
-                case "github" -> GITHUB_ATTRIBUTES;
-                default -> throw new NoSuchElementException("No attributes found for provider: " + provider);
-            };
-        }
+        if (attributeNames == null) attributeNames = DEFAULTS.get(provider);
+        if (attributeNames == null) throw new MissingProvider(provider);
         return attributeNames;
     }
 }
diff --git a/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java b/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java
index 27d85d2cd..6b6cc8372 100644
--- a/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java
+++ b/server/src/main/java/org/eclipse/openvsx/security/DefaultAuthUser.java
@@ -11,13 +11,13 @@
 
 public class DefaultAuthUser implements AuthUser {
 
-    final String authId;
-    final String avatarUrl;
-    final String email;
-    final String fullName;
-    final String loginName;
-    final String providerId;
-    final String providerUrl;
+    protected final String authId;
+    protected final String avatarUrl;
+    protected final String email;
+    protected final String fullName;
+    protected final String loginName;
+    protected final String providerId;
+    protected final String providerUrl;
 
     public DefaultAuthUser(
         final String authId,
diff --git a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java
index 28e7c05d1..5222513a2 100644
--- a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java
+++ b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java
@@ -21,13 +21,13 @@
 import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList;
 
 import java.util.Collection;
-import java.util.NoSuchElementException;
 
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.openvsx.UserService;
 import org.eclipse.openvsx.eclipse.EclipseService;
 import org.eclipse.openvsx.entities.UserData;
 import org.eclipse.openvsx.repositories.RepositoryService;
+import org.eclipse.openvsx.security.AuthUserFactory.MissingProvider;
 import org.eclipse.openvsx.util.ErrorResultException;
 import org.springframework.context.event.EventListener;
 import org.springframework.security.authentication.AuthenticationServiceException;
@@ -94,14 +94,10 @@ public void authenticationSucceeded(AuthenticationSuccessEvent event) {
     }
 
     public IdPrincipal loadUser(OAuth2UserRequest userRequest) {
-        var registrationId = userRequest.getClientRegistration().getRegistrationId();
-        if (registrationId == "eclipse") {
-            return loadEclipseUser(userRequest);
-        } else try {
-            return loadGenericUser(userRequest);
-        } catch (NoSuchElementException e) {
-            throw new CodedAuthException("Unsupported registration: " + registrationId, UNSUPPORTED_REGISTRATION, e);
-        }
+        return switch (userRequest.getClientRegistration().getRegistrationId()) {
+            case "eclipse" -> loadEclipseUser(userRequest);
+            default -> loadGenericUser(userRequest);
+        };
     }
 
     private OAuth2User springLoadUser(OAuth2UserRequest userRequest) {
@@ -110,9 +106,16 @@ private OAuth2User springLoadUser(OAuth2UserRequest userRequest) {
             : springOAuth2UserService.loadUser(userRequest);
     }
 
+    private AuthUser loadAuthUser(OAuth2UserRequest userRequest) {
+        try {
+            return authUserFactory.createAuthUser(userRequest.getClientRegistration().getRegistrationId(), springLoadUser(userRequest));
+        } catch (MissingProvider e) {
+            throw new CodedAuthException(e.getMessage(), UNSUPPORTED_REGISTRATION);
+        }
+    }
+
     private IdPrincipal loadGenericUser(OAuth2UserRequest userRequest) {
-        var registrationId = userRequest.getClientRegistration().getRegistrationId();
-        var authUser = authUserFactory.createAuthUser(registrationId, springLoadUser(userRequest));
+        var authUser = loadAuthUser(userRequest);
         if (StringUtils.isEmpty(authUser.getLoginName())) {
             throw new CodedAuthException("Invalid login: missing 'login' field.", INVALID_USER);
         }

From e2b485287741c7c1d9d96de32e2ed7c858843350 Mon Sep 17 00:00:00 2001
From: Paul Marechal 
Date: Wed, 11 Dec 2024 09:53:22 -0500
Subject: [PATCH 8/9] server: fix test

---
 .../java/org/eclipse/openvsx/cache/CacheServiceTest.java  | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java b/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java
index 3e787e14c..1d2c79729 100644
--- a/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java
+++ b/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java
@@ -38,7 +38,9 @@
 import org.eclipse.openvsx.json.ExtensionJson;
 import org.eclipse.openvsx.json.ReviewJson;
 import org.eclipse.openvsx.repositories.RepositoryService;
+import org.eclipse.openvsx.security.AuthUser;
 import org.eclipse.openvsx.security.AuthUserFactory;
+import org.eclipse.openvsx.security.AuthUserFactory.MissingProvider;
 import org.eclipse.openvsx.security.IdPrincipal;
 import org.eclipse.openvsx.util.TempFile;
 import org.eclipse.openvsx.util.TimeUtil;
@@ -132,7 +134,11 @@ void testUpdateExistingUser() throws IOException {
 
             var user = extVersion.getPublishedWith().getUser();
             var oauthUser = new DefaultOAuth2User(authorities, attributes, "name");
-            var authUser = authUserFactory.createAuthUser(authority, oauthUser);
+            AuthUser authUser; try {
+                authUser = authUserFactory.createAuthUser(authority, oauthUser);
+            } catch (MissingProvider e) {
+                authUser = null;
+            }
             users.updateExistingUser(user, authUser);
             assertNull(cache.getCache(CACHE_EXTENSION_JSON).get(cacheKey, ExtensionJson.class));
 

From 960d8deadc1841c48107a1f561f5728f7c100ca7 Mon Sep 17 00:00:00 2001
From: Paul Marechal 
Date: Fri, 13 Dec 2024 09:19:51 -0500
Subject: [PATCH 9/9] server: rename config as ovsx.oauth2

---
 .../java/org/eclipse/openvsx/OVSXConfig.java  | 29 +++++++-----
 .../java/org/eclipse/openvsx/UserAPI.java     | 44 ++++++++++++++-----
 .../openvsx/security/AuthUserFactory.java     |  4 +-
 3 files changed, 52 insertions(+), 25 deletions(-)

diff --git a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java
index fc392739d..5f922009d 100644
--- a/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java
+++ b/server/src/main/java/org/eclipse/openvsx/OVSXConfig.java
@@ -14,28 +14,35 @@
 @ConfigurationProperties(prefix = "ovsx")
 public class OVSXConfig {
 
-    private AuthConfig auth = new AuthConfig();
+    private OAuth2Config oauth2 = new OAuth2Config();
 
-    public AuthConfig getAuth() {
-        return auth;
+    public OAuth2Config getOauth2() {
+        return oauth2;
     }
 
-    public void setAuth(AuthConfig authConfig) {
-        this.auth = authConfig;
+    public void setOauth2(OAuth2Config oauth2Config) {
+        this.oauth2 = oauth2Config;
     }
 
-    public static class AuthConfig {
+    public static class OAuth2Config {
 
+        /**
+         * The user authentication provider to use.
+         */
         private String provider = "github";
 
         /**
          * Configuration example:
          * 

-         * ovsx:
-         *   auth:
-         *     attribute-names:
-         *       github: # provider name
-         *         login: field-returned-by-your-provider
+         *ovsx:
+         *  oauth2:
+         *    attribute-names:
+         *      [provider-name]:
+         *        avatar-url: string
+         *        email: string
+         *        full-name: string
+         *        login-name: string
+         *        provider-url: string
          * 
*/ private Map attributeNames = emptyMap(); diff --git a/server/src/main/java/org/eclipse/openvsx/UserAPI.java b/server/src/main/java/org/eclipse/openvsx/UserAPI.java index b286f4de4..f34094e5d 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/UserAPI.java @@ -9,11 +9,31 @@ ********************************************************************************/ package org.eclipse.openvsx; -import jakarta.servlet.http.HttpServletRequest; +import static org.eclipse.openvsx.entities.FileResource.CHANGELOG; +import static org.eclipse.openvsx.entities.FileResource.DOWNLOAD; +import static org.eclipse.openvsx.entities.FileResource.ICON; +import static org.eclipse.openvsx.entities.FileResource.LICENSE; +import static org.eclipse.openvsx.entities.FileResource.MANIFEST; +import static org.eclipse.openvsx.entities.FileResource.README; +import static org.eclipse.openvsx.entities.FileResource.VSIXMANIFEST; +import static org.eclipse.openvsx.util.UrlUtil.createApiUrl; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; + import org.eclipse.openvsx.eclipse.EclipseService; import org.eclipse.openvsx.entities.NamespaceMembership; import org.eclipse.openvsx.entities.UserData; -import org.eclipse.openvsx.json.*; +import org.eclipse.openvsx.json.AccessTokenJson; +import org.eclipse.openvsx.json.CsrfTokenJson; +import org.eclipse.openvsx.json.ErrorJson; +import org.eclipse.openvsx.json.ExtensionJson; +import org.eclipse.openvsx.json.NamespaceDetailsJson; +import org.eclipse.openvsx.json.NamespaceJson; +import org.eclipse.openvsx.json.NamespaceMembershipListJson; +import org.eclipse.openvsx.json.ResultJson; +import org.eclipse.openvsx.json.UserJson; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.security.CodedAuthException; import org.eclipse.openvsx.storage.StorageUtilService; @@ -30,17 +50,17 @@ import org.springframework.security.web.WebAttributes; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.ModelAndView; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.eclipse.openvsx.entities.FileResource.*; -import static org.eclipse.openvsx.util.UrlUtil.createApiUrl; +import jakarta.servlet.http.HttpServletRequest; @RestController public class UserAPI { @@ -76,7 +96,7 @@ public UserAPI( path = "/login" ) public ModelAndView login(ModelMap model) { - return new ModelAndView("redirect:/oauth2/authorization/" + config.getAuth().getProvider(), model); + return new ModelAndView("redirect:/oauth2/authorization/" + config.getOauth2().getProvider(), model); } /** @@ -289,7 +309,7 @@ public ResponseEntity getNamespaceMembers(@PathVari membershipList.setNamespaceMemberships(memberships.stream().map(NamespaceMembership::toJson).toList()); return new ResponseEntity<>(membershipList, HttpStatus.OK); } else { - return new ResponseEntity<>(NamespaceMembershipListJson.error("You don't have the permission to see this."), HttpStatus.FORBIDDEN); + return new ResponseEntity<>(NamespaceMembershipListJson.error("You don't have the permission to see this."), HttpStatus.FORBIDDEN); } } @@ -342,4 +362,4 @@ public ResponseEntity signPublisherAgreement() { } } -} \ No newline at end of file +} diff --git a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java index ada4fb1e0..b63277975 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java +++ b/server/src/main/java/org/eclipse/openvsx/security/AuthUserFactory.java @@ -4,7 +4,7 @@ import java.util.Map; import org.eclipse.openvsx.OVSXConfig; -import org.eclipse.openvsx.OVSXConfig.AuthConfig.AttributeNames; +import org.eclipse.openvsx.OVSXConfig.OAuth2Config.AttributeNames; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Component; @@ -61,7 +61,7 @@ protected T getAttribute(OAuth2User oauth2User, String attribute) { * @return The relevant attribute mappings. */ protected AttributeNames getAttributeNames(String provider) throws MissingProvider { - var attributeNames = config.getAuth().getAttributeNames().get(provider); + var attributeNames = config.getOauth2().getAttributeNames().get(provider); if (attributeNames == null) attributeNames = DEFAULTS.get(provider); if (attributeNames == null) throw new MissingProvider(provider); return attributeNames;