Skip to content

Commit

Permalink
refactor: introduce AuthUser interface
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
paul-marechal committed Mar 1, 2024
1 parent 0ce7b7b commit 736cbf4
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 34 deletions.
54 changes: 30 additions & 24 deletions server/src/main/java/org/eclipse/openvsx/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -298,4 +304,4 @@ public ResultJson deleteAccessToken(UserData user, long id) {
token.setActive(false);
return ResultJson.success("Deleted access token for user " + user.getLoginName() + ".");
}
}
}
48 changes: 48 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/security/AuthUser.java
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -47,7 +51,7 @@ public class OAuth2UserServices {

@Autowired
TokenService tokens;

@Autowired
RepositoryService repositories;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -169,4 +174,30 @@ private Collection<GrantedAuthority> getAuthorities(UserData userData) {
}
}

}
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");
}
}
}

0 comments on commit 736cbf4

Please sign in to comment.