From 736cbf4602d273f7e45434137c851d237729b35f Mon Sep 17 00:00:00 2001 From: Paul Marechal Date: Sat, 25 Nov 2023 03:07:57 -0500 Subject: [PATCH] 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 | 54 ++++++++++--------- .../eclipse/openvsx/security/AuthUser.java | 48 +++++++++++++++++ .../openvsx/security/OAuth2UserServices.java | 51 ++++++++++++++---- 3 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 server/src/main/java/org/eclipse/openvsx/security/AuthUser.java diff --git a/server/src/main/java/org/eclipse/openvsx/UserService.java b/server/src/main/java/org/eclipse/openvsx/UserService.java index 0740fba56..ca8d274a3 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserService.java +++ b/server/src/main/java/org/eclipse/openvsx/UserService.java @@ -9,13 +9,23 @@ ********************************************************************************/ package org.eclipse.openvsx; -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 java.util.Objects; +import java.util.UUID; + import org.eclipse.openvsx.cache.CacheService; -import org.eclipse.openvsx.entities.*; +import org.eclipse.openvsx.entities.FileResource; +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.AccessTokenJson; 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.ErrorResultException; @@ -25,16 +35,12 @@ import org.springframework.beans.factory.annotation.Autowired; 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 com.google.common.base.Joiner; + import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; -import java.util.Objects; -import java.util.UUID; - -import static org.eclipse.openvsx.cache.CacheService.CACHE_NAMESPACE_DETAILS_JSON; -import static org.eclipse.openvsx.util.UrlUtil.createApiUrl; @Component public class UserService { @@ -66,44 +72,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; @@ -298,4 +304,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/OAuth2UserServices.java b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java index 1a69ee538..ea723d476 100644 --- a/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java +++ b/server/src/main/java/org/eclipse/openvsx/security/OAuth2UserServices.java @@ -9,13 +9,15 @@ ********************************************************************************/ package org.eclipse.openvsx.security; -import static org.eclipse.openvsx.security.CodedAuthException.*; +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 java.util.Collection; import java.util.Collections; -import jakarta.persistence.EntityManager; - import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.eclipse.EclipseService; @@ -39,6 +41,8 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; +import jakarta.persistence.EntityManager; + @Service public class OAuth2UserServices { @@ -47,7 +51,7 @@ public class OAuth2UserServices { @Autowired TokenService tokens; - + @Autowired RepositoryService repositories; @@ -112,16 +116,17 @@ public IdPrincipal loadUser(OAuth2UserRequest userRequest) { } private IdPrincipal loadGitHubUser(OAuth2UserRequest userRequest) { - var authUser = delegate.loadUser(userRequest); - String loginName = authUser.getAttribute("login"); + var authUser = new GithubAuthUser(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) + 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) { @@ -169,4 +174,30 @@ private Collection getAuthorities(UserData userData) { } } -} \ No newline at end of file + static class GithubAuthUser implements AuthUser { + + final String authId; + final String avatarUrl; + final String email; + final String fullName; + final String loginName; + final String 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 "github"; } + @Override public String getProviderUrl() { return providerUrl; } + + public GithubAuthUser(OAuth2User oauth2User) { + authId = oauth2User.getName(); + avatarUrl = oauth2User.getAttribute("avatar_url"); + email = oauth2User.getAttribute("email"); + fullName = oauth2User.getAttribute("name"); + loginName = oauth2User.getAttribute("login"); + providerUrl = oauth2User.getAttribute("html_url"); + } + } +}