You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2020/10/13 02:20:26 UTC

[openmeetings] branch master updated: [OPENMEETINGS-2476] HttpURLConnection is replaced with HttpClient

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git


The following commit(s) were added to refs/heads/master by this push:
     new c64509a  [OPENMEETINGS-2476] HttpURLConnection is replaced with HttpClient
c64509a is described below

commit c64509a16cd061dca8800db9bcdaf384bdbf7ee0
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Tue Oct 13 09:20:06 2020 +0700

    [OPENMEETINGS-2476] HttpURLConnection is replaced with HttpClient
---
 .../openmeetings/db/dao/user/IUserManager.java     |   3 -
 .../openmeetings/db/entity/server/OAuthServer.java |  17 +-
 .../openmeetings/web/admin/oauth/OAuthForm.java    |   2 +-
 .../apache/openmeetings/web/app/Application.java   |   3 +
 .../apache/openmeetings/web/app/UserManager.java   | 216 +++++++++++++++++++-
 .../openmeetings/web/pages/auth/SignInDialog.java  |   2 +-
 .../openmeetings/web/pages/auth/SignInPage.java    | 222 +--------------------
 .../web/app/TestUserManagerMocked.java             |  58 ++++--
 8 files changed, 279 insertions(+), 244 deletions(-)

diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/IUserManager.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/IUserManager.java
index 262b381..94d6aba 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/IUserManager.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/IUserManager.java
@@ -18,11 +18,9 @@
  */
 package org.apache.openmeetings.db.dao.user;
 
-import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 import java.util.Locale;
 
-import org.apache.openmeetings.db.dto.user.OAuthUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.util.OmException;
 
@@ -35,7 +33,6 @@ public interface IUserManager {
 	Object registerUser(User u, String password, String hash) throws OmException, NoSuchAlgorithmException;
 
 	Long getLanguage(Locale loc);
-	User loginOAuth(OAuthUser user, long serverId) throws IOException, NoSuchAlgorithmException;
 
 	boolean kickExternal(Long roomId, String externalType, String externalId);
 	boolean kickUsersByRoomId(Long roomId);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/server/OAuthServer.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/server/OAuthServer.java
index 24285f8..d223706 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/server/OAuthServer.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/server/OAuthServer.java
@@ -58,6 +58,14 @@ import org.apache.openmeetings.db.entity.HistoricalEntity;
 public class OAuthServer extends HistoricalEntity {
 	private static final long serialVersionUID = 1L;
 
+	public enum RequestTokenMethod {
+		POST, GET
+	}
+
+	public enum RequestInfoMethod {
+		POST, GET, HEADER
+	}
+
 	@Id
 	@GeneratedValue(strategy = GenerationType.IDENTITY)
 	@Column(name = "id")
@@ -116,7 +124,6 @@ public class OAuthServer extends HistoricalEntity {
 	@MapKeyColumn(name = "name")
 	@Column(name = "value")
 	@CollectionTable(name = "oauth_mapping", joinColumns = @JoinColumn(name = "oauth_id"))
-	//FIXME TODO @XmlElement(name = "attrMapping", required = false)
 	@XmlTransient
 	private Map<String, String> mapping = new LinkedHashMap<>();
 
@@ -263,12 +270,4 @@ public class OAuthServer extends HistoricalEntity {
 				.append(", isDeleted()=").append(isDeleted())
 				.append("]").toString();
 	}
-
-	public enum RequestTokenMethod {
-		POST, GET
-	}
-
-	public enum RequestInfoMethod {
-		POST, GET, HEADER
-	}
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java
index 7ad488a..61ac9bb 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthForm.java
@@ -18,9 +18,9 @@
  */
 package org.apache.openmeetings.web.admin.oauth;
 
+import static org.apache.openmeetings.web.app.UserManager.getRedirectUri;
 import static org.apache.openmeetings.web.app.WebSession.getUserId;
 import static org.apache.openmeetings.web.common.confirmation.ConfirmationBehavior.newOkCancelDangerConfirm;
-import static org.apache.openmeetings.web.pages.auth.SignInPage.getRedirectUri;
 
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
index 460d840..87b2a85 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/Application.java
@@ -176,6 +176,8 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 	@Autowired
 	private UserDao userDao;
 	@Autowired
+	private UserManager userManager;
+	@Autowired
 	private ClientManager cm;
 	@Autowired
 	private WhiteboardManager wbManager;
@@ -340,6 +342,7 @@ public class Application extends AuthenticatedWebApplication implements IApplica
 			setExtProcessTtl(cfgDao.getInt(CONFIG_EXT_PROCESS_TTL, getExtProcessTtl()));
 			Version.logOMStarted();
 			recordingDao.resetProcessingStatus(); //we are starting so all processing recordings are now errors
+			userManager.initHttpClient();
 			setInitComplete(true);
 		} catch (Exception err) {
 			log.error("[appStart]", err);
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java
index b2a5010..53265d5 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java
@@ -18,29 +18,52 @@
  */
 package org.apache.openmeetings.web.app;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.UUID.randomUUID;
 import static org.apache.openmeetings.db.dao.user.UserDao.getNewUserInstance;
 import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone;
 import static org.apache.openmeetings.util.OmException.UNKNOWN;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_IGNORE_BAD_SSL;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.getBaseUrl;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.getDefaultGroup;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.getDefaultLang;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.getMinLoginLength;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.isAllowRegisterFrontend;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.isSendVerificationEmail;
+import static org.apache.openmeetings.web.app.Application.urlForPage;
 
 import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
 import org.apache.openmeetings.db.dao.label.LabelDao;
 import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.IUserManager;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.dto.user.OAuthUser;
 import org.apache.openmeetings.db.entity.basic.Client;
+import org.apache.openmeetings.db.entity.server.OAuthServer;
+import org.apache.openmeetings.db.entity.server.OAuthServer.RequestInfoMethod;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Right;
 import org.apache.openmeetings.db.entity.user.User.Type;
@@ -49,15 +72,20 @@ import org.apache.openmeetings.service.mail.EmailManager;
 import org.apache.openmeetings.util.OmException;
 import org.apache.openmeetings.util.crypt.CryptProvider;
 import org.apache.openmeetings.util.crypt.ICrypt;
+import org.apache.openmeetings.web.pages.auth.SignInPage;
 import org.apache.wicket.IConverterLocator;
 import org.apache.wicket.core.util.lang.PropertyResolver;
 import org.apache.wicket.core.util.lang.PropertyResolverConverter;
+import org.apache.wicket.request.flow.RedirectToUrlException;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.util.string.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import com.github.openjson.JSONObject;
+
 /**
  *
  * @author swagner
@@ -68,6 +96,8 @@ public class UserManager implements IUserManager {
 	private static final Logger log = LoggerFactory.getLogger(UserManager.class);
 
 	@Autowired
+	private ConfigurationDao cfgDao;
+	@Autowired
 	private GroupDao groupDao;
 	@Autowired
 	private UserDao userDao;
@@ -75,6 +105,7 @@ public class UserManager implements IUserManager {
 	private EmailManager emailManager;
 	@Autowired
 	private IClientManager cm;
+	private HttpClient httpClient;
 
 	private static boolean sendConfirmation() {
 		String baseURL = getBaseUrl();
@@ -229,14 +260,63 @@ public class UserManager implements IUserManager {
 		return LabelDao.getLanguage(loc, getDefaultLang());
 	}
 
-	@Override
-	public User loginOAuth(OAuthUser user, long serverId) throws IOException, NoSuchAlgorithmException {
+	// ============= OAuth2 methods =============
+	public void initHttpClient() {
+		HttpClient.Builder builder = HttpClient.newBuilder()
+				.version(HttpClient.Version.HTTP_1_1)
+				.connectTimeout(Duration.ofSeconds(10));
+		final boolean ignoreBadSsl = cfgDao.getBool(CONFIG_IGNORE_BAD_SSL, false);
+		System.setProperty("jdk.internal.httpclient.disableHostnameVerification", String.valueOf(ignoreBadSsl));
+		if (ignoreBadSsl) {
+			TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
+				@Override
+				public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+					//no-op
+				}
+
+				@Override
+				public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+					//no-op
+				}
+
+				@Override
+				public X509Certificate[] getAcceptedIssuers() {
+					return new X509Certificate[] {};
+				}
+			}};
+			try {
+				SSLContext sslContext = SSLContext.getInstance("SSL");
+				sslContext.init(null, trustAllCerts, new SecureRandom());
+				SSLParameters sslParams = new SSLParameters();
+				sslParams.setEndpointIdentificationAlgorithm("");
+				builder.sslContext(sslContext)
+						.sslParameters(sslParams);
+			} catch (Exception e) {
+				log.error("[initHttpClient]", e);
+			}
+		}
+		httpClient = builder.build();
+	}
+
+	public User loginOAuth(String code, OAuthServer server) throws IOException, NoSuchAlgorithmException, InterruptedException {
+		if (code == null) {
+			showAuth(server);
+			return null;
+		}
+		log.debug("OAuth response code={}", code);
+		AuthInfo authInfo = getToken(code, server);
+		if (authInfo == null) {
+			return null;
+		}
+		log.debug("OAuthInfo={}", authInfo);
+		OAuthUser user = getAuthParams(authInfo, code, server);
+
 		if (!userDao.validLogin(user.getLogin())) {
 			log.error("Invalid login, please check parameters");
 			return null;
 		}
-		User u = userDao.getByLogin(user.getLogin(), Type.OAUTH, serverId);
-		if (!userDao.checkEmail(user.getEmail(), Type.OAUTH, serverId, u == null ? null : u.getId())) {
+		User u = userDao.getByLogin(user.getLogin(), Type.OAUTH, server.getId());
+		if (!userDao.checkEmail(user.getEmail(), Type.OAUTH, server.getId(), u == null ? null : u.getId())) {
 			log.error("Another user with the same email exists");
 			return null;
 		}
@@ -246,7 +326,7 @@ public class UserManager implements IUserManager {
 			final User fUser = getNewUserInstance(null);
 			fUser.setType(Type.OAUTH);
 			fUser.getRights().remove(Right.LOGIN);
-			fUser.setDomainId(serverId);
+			fUser.setDomainId(server.getId());
 			fUser.addGroup(groupDao.get(getDefaultGroup()));
 			for (Map.Entry<String, String> entry : user.getUserData().entrySet()) {
 				final String expression = entry.getKey();
@@ -262,6 +342,103 @@ public class UserManager implements IUserManager {
 		return u;
 	}
 
+	private static Map<String, String> getInitParams(final OAuthServer s) {
+		Map<String, String> params = new HashMap<>();
+		params.put("{$client_id}", s.getClientId());
+		params.put("{$redirect_uri}", getRedirectUri(s));
+		return params;
+	}
+
+	public static void showAuth(final OAuthServer s) {
+		String authUrl = prepareUrl(s.getRequestKeyUrl(), getInitParams(s));
+		log.debug("redirectUrl={}", authUrl);
+		throw new RedirectToUrlException(authUrl);
+	}
+
+	private static String prepareUrl(String urlTemplate, Map<String, String> params) {
+		String result = urlTemplate;
+		for (Entry<String, String> e : params.entrySet()) {
+			if (e.getValue() != null) {
+				result = result.replace(e.getKey(), URLEncoder.encode(e.getValue(), UTF_8));
+			}
+		}
+		return result;
+	}
+
+	public static String getRedirectUri(OAuthServer server) {
+		String result = "";
+		if (server.getId() != null) {
+			String base = getBaseUrl();
+			result = urlForPage(SignInPage.class, new PageParameters().add("oauthid", server.getId()), base);
+		}
+		return result;
+	}
+
+	private static Map<String, String> getParams(final OAuthServer s, String code, AuthInfo authInfo) {
+		Map<String, String> params = getInitParams(s);
+		params.put("{$client_id}", s.getClientId());
+		params.put("{$client_secret}", s.getClientSecret());
+		if (authInfo != null) {
+			params.put("{$access_token}", authInfo.accessToken);
+			params.put("{$user_id}", authInfo.userId);
+		}
+		if (code != null) {
+			params.put("{$code}", code);
+		}
+		return params;
+	}
+
+	private static HttpRequest.Builder setNoCache(HttpRequest.Builder builder) {
+		return builder
+				.header("Cache-Control", "no-cache, no-store, must-revalidate")
+				.header("Pragma", "no-cache")
+				.header("Expires", "0");
+	}
+
+	String doRequest(HttpRequest request) throws IOException, InterruptedException { // extracted as package private for testing
+		return httpClient.send(request, BodyHandlers.ofString()).body();
+	}
+
+	private AuthInfo getToken(String code, OAuthServer server) throws IOException, InterruptedException {
+		// build url params to request auth token
+		String requestTokenParams = prepareUrl(server.getRequestTokenAttributes(), getParams(server, code, null));
+		// request auth token
+		HttpRequest request = setNoCache(
+				HttpRequest.newBuilder()
+					.uri(URI.create(server.getRequestTokenUrl()))
+					.header("Content-Type", "application/x-www-form-urlencoded")
+					.header("charset", UTF_8.name())
+					.method(server.getRequestTokenMethod().name(), BodyPublishers.ofString(requestTokenParams))
+				).build();
+
+		String resp = doRequest(request);
+		// parse json result
+		AuthInfo result = new AuthInfo(resp);
+		// access token must be specified
+		if (result.accessToken == null) {
+			log.error("Response doesn't contain access_token field:\n {}", resp);
+			return null;
+		}
+		return result;
+	}
+
+	private OAuthUser getAuthParams(AuthInfo authInfo, String code, OAuthServer server) throws IOException, InterruptedException {
+		// prepare url
+		String requestInfoUrl = prepareUrl(server.getRequestInfoUrl(), getParams(server, code, authInfo));
+		HttpRequest.Builder builder = setNoCache(HttpRequest.newBuilder().uri(URI.create(requestInfoUrl)));
+
+		if (server.getRequestInfoMethod() == RequestInfoMethod.HEADER) {
+			builder.header("Authorization", "Bearer " + authInfo.accessToken);
+		} else {
+			builder.method(server.getRequestInfoMethod().name(), BodyPublishers.noBody());
+		}
+
+		String json = doRequest(builder.build());
+		log.debug("User info={}", json);
+		// parse json result
+		return new OAuthUser(json, server);
+	}
+
 	private class LanguageConverter extends PropertyResolverConverter {
 		private static final long serialVersionUID = 1L;
 		final String expression;
@@ -297,4 +474,33 @@ public class UserManager implements IUserManager {
 			return String.valueOf(object);
 		}
 	}
+
+	private static class AuthInfo {
+		final String accessToken;
+		final String refreshToken;
+		final String tokenType;
+		final String userId;
+		final long expiresIn;
+
+		AuthInfo(String jsonStr) {
+			log.debug("AuthInfo={}", jsonStr);
+			JSONObject json = new JSONObject(jsonStr);
+			accessToken = json.optString("access_token");
+			refreshToken = json.optString("refresh_token");
+			tokenType = json.optString("token_type");
+			userId = json.optString("user_id");
+			expiresIn = json.optLong("expires_in");
+		}
+
+		@Override
+		public String toString() {
+			return new StringBuilder()
+				.append("AuthInfo [accessToken=").append(accessToken)
+				.append(", refreshToken=").append(refreshToken)
+				.append(", tokenType=").append(tokenType)
+				.append(", userId=").append(userId)
+				.append(", expiresIn=").append(expiresIn)
+				.append("]").toString();
+		}
+	}
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInDialog.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInDialog.java
index 0bb98d2..fac217b 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInDialog.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInDialog.java
@@ -20,9 +20,9 @@ package org.apache.openmeetings.web.pages.auth;
 
 import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_DEFAULT_LDAP_ID;
 import static org.apache.openmeetings.web.app.Application.getAuthenticationStrategy;
+import static org.apache.openmeetings.web.app.UserManager.showAuth;
 import static org.apache.openmeetings.web.pages.HashPage.APP;
 import static org.apache.openmeetings.web.pages.HashPage.APP_TYPE_NETWORK;
-import static org.apache.openmeetings.web.pages.auth.SignInPage.showAuth;
 
 import java.util.List;
 
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInPage.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInPage.java
index 0e794e6..b26cd30 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInPage.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/SignInPage.java
@@ -18,43 +18,19 @@
  */
 package org.apache.openmeetings.web.pages.auth;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_IGNORE_BAD_SSL;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.getBaseUrl;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.isAllowRegisterFrontend;
-import static org.apache.openmeetings.web.app.Application.urlForPage;
 
-import java.io.DataOutputStream;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLEncoder;
 import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
 
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-import org.apache.commons.io.IOUtils;
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
 import org.apache.openmeetings.db.dao.server.OAuth2Dao;
-import org.apache.openmeetings.db.dao.user.IUserManager;
-import org.apache.openmeetings.db.dto.user.OAuthUser;
 import org.apache.openmeetings.db.entity.server.OAuthServer;
-import org.apache.openmeetings.db.entity.server.OAuthServer.RequestInfoMethod;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Type;
 import org.apache.openmeetings.util.OmException;
 import org.apache.openmeetings.web.app.Application;
+import org.apache.openmeetings.web.app.UserManager;
 import org.apache.openmeetings.web.app.WebSession;
 import org.apache.openmeetings.web.common.OmModalCloseButton;
 import org.apache.openmeetings.web.pages.BaseInitedPage;
@@ -67,7 +43,6 @@ import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.request.IRequestParameters;
 import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.request.flow.RedirectToUrlException;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 import org.apache.wicket.util.string.StringValue;
@@ -75,7 +50,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.github.openjson.JSONException;
-import com.github.openjson.JSONObject;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.TextContentModal;
@@ -137,7 +111,7 @@ public class SignInPage extends BaseInitedPage {
 	@SpringBean
 	private ConfigurationDao cfgDao;
 	@SpringBean
-	private IUserManager userManager;
+	private UserManager userManager;
 	@SpringBean
 	private OAuth2Dao oauthDao;
 
@@ -162,20 +136,14 @@ public class SignInPage extends BaseInitedPage {
 					return;
 				}
 
-				if (!p.get("code").isNull()) { // got code
-					String code = p.get("code").toString();
-					log.debug("OAuth response code={}", code);
-					AuthInfo authInfo = getToken(code, server);
-					if (authInfo == null) {
-						return;
-					}
-					log.debug("OAuthInfo={}", authInfo);
-					OAuthUser user = getAuthParams(authInfo, code, server);
-					loginViaOAuth2(user, serverId);
-				} else { // redirect to get code
-					showAuth(server);
+				User u = userManager.loginOAuth(p.get("code").toOptionalString(), server);
+
+				if (u != null && WebSession.get().signIn(u)) {
+					setResponsePage(Application.get().getHomePage());
+				} else {
+					log.error("Failed to login via OAuth2!");
 				}
-			} catch (IOException|NoSuchAlgorithmException|JSONException e) {
+			} catch (IOException|NoSuchAlgorithmException|InterruptedException|JSONException e) {
 				log.error("OAuth2 login error", e);
 			}
 		}
@@ -240,176 +208,4 @@ public class SignInPage extends BaseInitedPage {
 	protected void onParameterArrival(IRequestParameters params, AjaxRequestTarget arg1) {
 		WebSession.get().setArea(getUrlFragment(params));
 	}
-
-	// ============= OAuth2 methods =============
-	private static Map<String, String> getInitParams(final OAuthServer s) {
-		Map<String, String> params = new HashMap<>();
-		params.put("{$client_id}", s.getClientId());
-		params.put("{$redirect_uri}", getRedirectUri(s));
-		return params;
-	}
-
-	public static void showAuth(final OAuthServer s) {
-		String authUrl = prepareUrl(s.getRequestKeyUrl(), getInitParams(s));
-		log.debug("redirectUrl={}", authUrl);
-		throw new RedirectToUrlException(authUrl);
-	}
-
-	private static String prepareUrl(String urlTemplate, Map<String, String> params) {
-		String result = urlTemplate;
-		for (Entry<String, String> e : params.entrySet()) {
-			if (e.getValue() != null) {
-				try {
-					result = result.replace(e.getKey(), URLEncoder.encode(e.getValue(), UTF_8.name()));
-				} catch (UnsupportedEncodingException err) {
-					log.error("Unexpected exception while encoding URI param {}", e, err);
-				}
-			}
-		}
-		return result;
-	}
-
-	public static String getRedirectUri(OAuthServer server) {
-		String result = "";
-		if (server.getId() != null) {
-			String base = getBaseUrl();
-			result = urlForPage(SignInPage.class, new PageParameters().add("oauthid", server.getId()), base);
-		}
-		return result;
-	}
-
-	private void prepareConnection(URLConnection inConnection) {
-		if (!(inConnection instanceof HttpsURLConnection)) {
-			return;
-		}
-		if (!cfgDao.getBool(CONFIG_IGNORE_BAD_SSL, false)) {
-			return;
-		}
-		TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
-			@Override
-			public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
-				//no-op
-			}
-
-			@Override
-			public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
-				//no-op
-			}
-
-			@Override
-			public X509Certificate[] getAcceptedIssuers() {
-				return new X509Certificate[] {};
-			}
-		}};
-		try {
-			HttpsURLConnection connection = (HttpsURLConnection)inConnection;
-			SSLContext sslContext = SSLContext.getInstance("SSL");
-			sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
-			SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
-			connection.setSSLSocketFactory(sslSocketFactory);
-			connection.setHostnameVerifier((arg0, arg1) -> true);
-		} catch (Exception e) {
-			log.error("[prepareConnection]", e);
-		}
-	}
-
-	private static Map<String, String> getParams(final OAuthServer s, String code, AuthInfo authInfo) {
-		Map<String, String> params = getInitParams(s);
-		params.put("{$client_id}", s.getClientId());
-		params.put("{$client_secret}", s.getClientSecret());
-		if (authInfo != null) {
-			params.put("{$access_token}", authInfo.accessToken);
-			params.put("{$user_id}", authInfo.userId);
-		}
-		if (code != null) {
-			params.put("{$code}", code);
-		}
-		return params;
-	}
-
-	private AuthInfo getToken(String code, OAuthServer server) throws IOException {
-		String requestTokenBaseUrl = server.getRequestTokenUrl();
-		// build url params to request auth token
-		String requestTokenParams = server.getRequestTokenAttributes();
-		requestTokenParams = prepareUrl(requestTokenParams, getParams(server, code, null));
-		// request auth token
-		HttpURLConnection connection = (HttpURLConnection) new URL(requestTokenBaseUrl).openConnection();
-		prepareConnection(connection);
-		connection.setRequestMethod(server.getRequestTokenMethod().name());
-		connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-		connection.setRequestProperty("charset", UTF_8.name());
-		connection.setRequestProperty("Content-Length", String.valueOf(requestTokenParams.length()));
-		connection.setDoInput(true);
-		connection.setDoOutput(true);
-		connection.setUseCaches(false);
-		DataOutputStream paramsOutputStream = new DataOutputStream(connection.getOutputStream());
-		paramsOutputStream.writeBytes(requestTokenParams);
-		paramsOutputStream.flush();
-		String sourceResponse = IOUtils.toString(connection.getInputStream(), UTF_8);
-		// parse json result
-		AuthInfo result = new AuthInfo(sourceResponse);
-		// access token must be specified
-		if (result.accessToken == null) {
-			log.error("Response doesn't contain access_token field:\n {}", sourceResponse);
-			return null;
-		}
-		return result;
-	}
-
-	private OAuthUser getAuthParams(AuthInfo authInfo, String code, OAuthServer server) throws IOException {
-		// prepare url
-		String requestInfoUrl = server.getRequestInfoUrl();
-		requestInfoUrl = prepareUrl(requestInfoUrl, getParams(server, code, authInfo));
-		// send request
-		HttpURLConnection connection = (HttpURLConnection) new URL(requestInfoUrl).openConnection();
-		if (server.getRequestInfoMethod() == RequestInfoMethod.HEADER) {
-			connection.setRequestProperty("Authorization", String.format("Bearer %s", authInfo.accessToken));
-		} else {
-			connection.setRequestMethod(server.getRequestInfoMethod().name());
-		}
-		prepareConnection(connection);
-		String json = IOUtils.toString(connection.getInputStream(), UTF_8);
-		log.debug("User info={}", json);
-		// parse json result
-		return new OAuthUser(json, server);
-	}
-
-	private void loginViaOAuth2(OAuthUser user, long serverId) throws IOException, NoSuchAlgorithmException {
-		User u = userManager.loginOAuth(user, serverId);
-
-		if (u != null && WebSession.get().signIn(u)) {
-			setResponsePage(Application.get().getHomePage());
-		} else {
-			log.error("Failed to login via OAuth2!");
-		}
-	}
-
-	private static class AuthInfo {
-		final String accessToken;
-		final String refreshToken;
-		final String tokenType;
-		final String userId;
-		final long expiresIn;
-
-		AuthInfo(String jsonStr) {
-			log.debug("AuthInfo={}", jsonStr);
-			JSONObject json = new JSONObject(jsonStr);
-			accessToken = json.optString("access_token");
-			refreshToken = json.optString("refresh_token");
-			tokenType = json.optString("token_type");
-			userId = json.optString("user_id");
-			expiresIn = json.optLong("expires_in");
-		}
-
-		@Override
-		public String toString() {
-			return new StringBuilder()
-				.append("AuthInfo [accessToken=").append(accessToken)
-				.append(", refreshToken=").append(refreshToken)
-				.append(", tokenType=").append(tokenType)
-				.append(", userId=").append(userId)
-				.append(", expiresIn=").append(expiresIn)
-				.append("]").toString();
-		}
-	}
 }
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/web/app/TestUserManagerMocked.java b/openmeetings-web/src/test/java/org/apache/openmeetings/web/app/TestUserManagerMocked.java
index ce19e5b..b57c21e 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/web/app/TestUserManagerMocked.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/web/app/TestUserManagerMocked.java
@@ -18,6 +18,10 @@
  */
 package org.apache.openmeetings.web.app;
 
+import static org.apache.openmeetings.db.dto.user.OAuthUser.PARAM_EMAIL;
+import static org.apache.openmeetings.db.dto.user.OAuthUser.PARAM_FNAME;
+import static org.apache.openmeetings.db.dto.user.OAuthUser.PARAM_LNAME;
+import static org.apache.openmeetings.db.dto.user.OAuthUser.PARAM_LOGIN;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.setCryptClassName;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -25,16 +29,18 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.when;
 
 import java.io.IOException;
+import java.net.http.HttpRequest;
 import java.security.NoSuchAlgorithmException;
 
 import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
-import org.apache.openmeetings.db.dto.user.OAuthUser;
 import org.apache.openmeetings.db.entity.server.OAuthServer;
+import org.apache.openmeetings.db.entity.server.OAuthServer.RequestInfoMethod;
+import org.apache.openmeetings.db.entity.server.OAuthServer.RequestTokenMethod;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Type;
 import org.apache.openmeetings.db.manager.IClientManager;
@@ -44,6 +50,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.stubbing.Answer;
@@ -59,25 +66,52 @@ class TestUserManagerMocked {
 	@Mock
 	private IClientManager cm;
 	@InjectMocks
-	private UserManager userManager;
+	private UserManager userManager = Mockito.spy(new UserManager());
 
 	@Test
-	void oauthTest() throws NoSuchAlgorithmException, IOException {
+	void oauthTest() throws NoSuchAlgorithmException, IOException, InterruptedException {
+		OAuthServer server = new OAuthServer()
+				.setName("Google")
+				.setIconUrl("https://www.google.com/images/google_favicon_128.png")
+				.setEnabled(false)
+				.setClientId("secret-client-id")
+				.setClientSecret("secret-client-secret")
+				.setRequestKeyUrl("https://accounts.google.com/o/oauth2/auth?redirect_uri={$redirect_uri}&response_type=code&client_id={$client_id}"
+						+ "&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile")
+				.setRequestTokenUrl("https://accounts.google.com/o/oauth2/token")
+				.setRequestTokenMethod(RequestTokenMethod.POST)
+				.setRequestTokenAttributes("code={$code}&client_id={$client_id}&client_secret={$client_secret}&redirect_uri={$redirect_uri}&grant_type=authorization_code")
+				.setRequestInfoUrl("https://www.googleapis.com/oauth2/v1/userinfo?access_token={$access_token}")
+				.setRequestInfoMethod(RequestInfoMethod.GET)
+				.addMapping(PARAM_LOGIN, "preferred_username")
+				.addMapping(PARAM_EMAIL, "email")
+				.addMapping(PARAM_FNAME, "given_name")
+				.addMapping(PARAM_LNAME, "family_name");
 		setCryptClassName(SCryptImplementation.class.getCanonicalName());
 		doReturn(true).when(userDao).validLogin(anyString());
-		doReturn(true).when(userDao).checkEmail(anyString(), eq(Type.OAUTH), any(Long.class), nullable(Long.class));
-		when(userDao.update(any(User.class), nullable(String.class), any(Long.class))).then(new Answer<User>() {
+		doReturn(true).when(userDao).checkEmail(eq("openmeetings@pod.land"), eq(Type.OAUTH), nullable(Long.class), nullable(Long.class));
+		doAnswer(new Answer<User>() {
 			@Override
 			public User answer(InvocationOnMock invocation) throws Throwable {
 				Object[] args = invocation.getArguments();
 				return (User)args[0];
 			}
-		});
-		final String json = "{\"email\":\"openmeetings@pod.land\",\"email_verified\":true,\"id\":78207,\"nationalcode_verified\":false,\"phone_number_verified\":false,\"preferred_username\":\"openmeetings\",\"sub\":\"78207\",\"user_metadata\":\"{\\\"mail.auto-forward\\\":true,\\\"email\\\":{\\\"auto_forward\\\":true}}\"}";
-		OAuthUser user = new OAuthUser(json, new OAuthServer()
-				.addMapping(OAuthUser.PARAM_LOGIN, "preferred_username")
-				.addMapping(OAuthUser.PARAM_EMAIL, "email"));
-		User u = userManager.loginOAuth(user, 1);
+		}).when(userDao).update(any(User.class), nullable(String.class), any(Long.class));
+		final String userJson = "{\"email\":\"openmeetings@pod.land\",\"email_verified\":true,\"id\":78207,\"nationalcode_verified\":false,\"phone_number_verified\":false,\"preferred_username\":\"openmeetings\",\"sub\":\"78207\",\"user_metadata\":\"{\\\"mail.auto-forward\\\":true,\\\"email\\\":{\\\"auto_forward\\\":true}}\"}";
+		final String tokenJson = "{\"accessToken\": \"aaa\", \"refreshToken\": \"bbb\", \"tokenType\": \"cccc\", \"userId\": \"ddddd\", \"expiresIn\": \"eeeeee\"}";
+		doAnswer(new Answer<String>() {
+			@Override
+			public String answer(InvocationOnMock invocation) throws Throwable {
+				Object[] args = invocation.getArguments();
+				HttpRequest req = (HttpRequest)args[0];
+				if (req.uri().getHost().equals("accounts.google.com")) {
+					return tokenJson;
+				}
+				return userJson;
+			}
+		}).when(userManager).doRequest(any(HttpRequest.class));
+
+		User u = userManager.loginOAuth("bla-bla-bla", server);
 		assertNotNull(u, "Valid user should be created");
 		assertEquals("openmeetings", u.getLogin(), "User should have valid login");
 		assertEquals("openmeetings@pod.land", u.getAddress().getEmail(), "User should have valid email");