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