@@ -86,8 +86,6 @@ public class HttpMxisd {
|
||||
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
||||
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvitationManager())))
|
||||
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
|
||||
.get(RemoteIdentityAPIv1.SESSION_REQUEST_TOKEN, SaneHandler.around(new RemoteSessionStartHandler(m.getSession(), m.getConfig().getView())))
|
||||
.get(RemoteIdentityAPIv1.SESSION_CHECK, SaneHandler.around(new RemoteSessionCheckHandler(m.getSession(), m.getConfig().getView())))
|
||||
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||
|
||||
@@ -45,7 +45,7 @@ import io.kamax.mxisd.notification.NotificationHandlers;
|
||||
import io.kamax.mxisd.notification.NotificationManager;
|
||||
import io.kamax.mxisd.profile.ProfileManager;
|
||||
import io.kamax.mxisd.profile.ProfileProviders;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
@@ -72,7 +72,7 @@ public class Mxisd {
|
||||
protected InvitationManager invMgr;
|
||||
protected ProfileManager pMgr;
|
||||
protected AppSvcManager asHander;
|
||||
protected SessionMananger sessMgr;
|
||||
protected SessionManager sessMgr;
|
||||
protected NotificationManager notifMgr;
|
||||
|
||||
public Mxisd(MxisdConfig cfg) {
|
||||
@@ -102,7 +102,7 @@ public class Mxisd {
|
||||
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
|
||||
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
|
||||
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
|
||||
sessMgr = new SessionMananger(cfg.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy, httpClient);
|
||||
sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy, httpClient);
|
||||
invMgr = new InvitationManager(cfg.getInvite(), store, idStrategy, signMgr, fedDns, notifMgr);
|
||||
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
||||
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
||||
@@ -137,7 +137,7 @@ public class Mxisd {
|
||||
return authMgr;
|
||||
}
|
||||
|
||||
public SessionMananger getSession() {
|
||||
public SessionManager getSession() {
|
||||
return sessMgr;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,68 +32,7 @@ public class SessionConfig {
|
||||
|
||||
public static class PolicyTemplate {
|
||||
|
||||
public static class PolicySource {
|
||||
|
||||
public static class PolicySourceRemote {
|
||||
|
||||
private boolean enabled;
|
||||
private String server;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void setServer(String server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean enabled;
|
||||
private boolean toLocal;
|
||||
private PolicySourceRemote toRemote = new PolicySourceRemote();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean toLocal() {
|
||||
return toLocal;
|
||||
}
|
||||
|
||||
public void setToLocal(boolean toLocal) {
|
||||
this.toLocal = toLocal;
|
||||
}
|
||||
|
||||
public boolean toRemote() {
|
||||
return toRemote.isEnabled();
|
||||
}
|
||||
|
||||
public PolicySourceRemote getToRemote() {
|
||||
return toRemote;
|
||||
}
|
||||
|
||||
public void setToRemote(PolicySourceRemote toRemote) {
|
||||
this.toRemote = toRemote;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean enabled;
|
||||
private PolicySource forLocal = new PolicySource();
|
||||
private PolicySource forRemote = new PolicySource();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -103,26 +42,6 @@ public class SessionConfig {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public PolicySource getForLocal() {
|
||||
return forLocal;
|
||||
}
|
||||
|
||||
public PolicySource forLocal() {
|
||||
return forLocal;
|
||||
}
|
||||
|
||||
public PolicySource getForRemote() {
|
||||
return forRemote;
|
||||
}
|
||||
|
||||
public PolicySource forRemote() {
|
||||
return forRemote;
|
||||
}
|
||||
|
||||
public PolicySource forIf(boolean isLocal) {
|
||||
return isLocal ? forLocal : forRemote;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PolicyUnbind {
|
||||
@@ -155,15 +74,6 @@ public class SessionConfig {
|
||||
|
||||
public Policy() {
|
||||
validation.enabled = true;
|
||||
validation.forLocal.enabled = true;
|
||||
validation.forLocal.toLocal = true;
|
||||
validation.forLocal.toRemote.enabled = true;
|
||||
validation.forLocal.toRemote.server = "matrix-org";
|
||||
|
||||
validation.forRemote.enabled = true;
|
||||
validation.forRemote.toLocal = false;
|
||||
validation.forRemote.toRemote.enabled = true;
|
||||
validation.forRemote.toRemote.server = "matrix-org";
|
||||
}
|
||||
|
||||
private PolicyTemplate validation = new PolicyTemplate();
|
||||
|
||||
@@ -21,12 +21,13 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ViewConfig {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(ViewConfig.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(ViewConfig.class);
|
||||
|
||||
public static class Session {
|
||||
|
||||
@@ -67,45 +68,13 @@ public class ViewConfig {
|
||||
|
||||
}
|
||||
|
||||
public static class Remote {
|
||||
|
||||
private Paths onRequest = new Paths();
|
||||
private Paths onCheck = new Paths();
|
||||
|
||||
public Paths getOnRequest() {
|
||||
return onRequest;
|
||||
}
|
||||
|
||||
public void setOnRequest(Paths onRequest) {
|
||||
this.onRequest = onRequest;
|
||||
}
|
||||
|
||||
public Paths getOnCheck() {
|
||||
return onCheck;
|
||||
}
|
||||
|
||||
public void setOnCheck(Paths onCheck) {
|
||||
this.onCheck = onCheck;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Legacy option
|
||||
private Local local = new Local();
|
||||
private Local localRemote = new Local();
|
||||
private Remote remote = new Remote();
|
||||
private Paths onTokenSubmit = new Paths();
|
||||
|
||||
public Session() {
|
||||
local.onTokenSubmit.success = "classpath:/templates/session/local/tokenSubmitSuccess.html";
|
||||
local.onTokenSubmit.failure = "classpath:/templates/session/local/tokenSubmitFailure.html";
|
||||
|
||||
localRemote.onTokenSubmit.success = "classpath:/templates/session/localRemote/tokenSubmitSuccess.html";
|
||||
localRemote.onTokenSubmit.failure = "classpath:/templates/session/local/tokenSubmitFailure.html";
|
||||
|
||||
remote.onRequest.success = "classpath:/templates/session/remote/requestSuccess.html";
|
||||
remote.onRequest.failure = "classpath:/templates/session/remote/requestFailure.html";
|
||||
|
||||
remote.onCheck.success = "classpath:/templates/session/remote/checkSuccess.html";
|
||||
remote.onCheck.failure = "classpath:/templates/session/remote/checkFailure.html";
|
||||
onTokenSubmit.success = "classpath:/templates/session/tokenSubmitSuccess.html";
|
||||
onTokenSubmit.failure = "classpath:/templates/session/tokenSubmitFailure.html";
|
||||
}
|
||||
|
||||
public Local getLocal() {
|
||||
@@ -116,21 +85,14 @@ public class ViewConfig {
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
public Local getLocalRemote() {
|
||||
return localRemote;
|
||||
public Paths getOnTokenSubmit() {
|
||||
return onTokenSubmit;
|
||||
}
|
||||
|
||||
public void setLocalRemote(Local localRemote) {
|
||||
this.localRemote = localRemote;
|
||||
public void setOnTokenSubmit(Paths onTokenSubmit) {
|
||||
this.onTokenSubmit = onTokenSubmit;
|
||||
}
|
||||
|
||||
public Remote getRemote() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
public void setRemote(Remote remote) {
|
||||
this.remote = remote;
|
||||
}
|
||||
}
|
||||
|
||||
private Session session = new Session();
|
||||
@@ -144,6 +106,17 @@ public class ViewConfig {
|
||||
}
|
||||
|
||||
public void build() {
|
||||
if (StringUtils.isNotBlank(session.local.onTokenSubmit.success) && StringUtils.isBlank(session.onTokenSubmit.success)) {
|
||||
log.warn("Legacy option session.local.onTokenSubmit.success in use, please switch to session.onTokenSubmit.success");
|
||||
session.onTokenSubmit.success = session.local.onTokenSubmit.success;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(session.local.onTokenSubmit.failure) && StringUtils.isBlank(session.onTokenSubmit.failure)) {
|
||||
log.warn("Legacy option session.local.onTokenSubmit.failure in use, please switch to session.onTokenSubmit.failure");
|
||||
session.onTokenSubmit.failure = session.local.onTokenSubmit.failure;
|
||||
}
|
||||
|
||||
|
||||
log.info("--- View config ---");
|
||||
log.info("Session: {}", GsonUtil.get().toJson(session));
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
|
||||
public class RemoteIdentityAPIv1 {
|
||||
|
||||
public static final String BASE = "/_matrix/identity/remote/api/v1";
|
||||
public static final String SESSION_REQUEST_TOKEN = BASE + "/validate/requestToken";
|
||||
public static final String SESSION_CHECK = BASE + "/validate/check";
|
||||
|
||||
public static String getRequestToken(String id, String secret) {
|
||||
return SESSION_REQUEST_TOKEN + "?sid=" + id + "&client_secret=" + secret;
|
||||
}
|
||||
|
||||
public static String getSessionCheck(String id, String secret) {
|
||||
return SESSION_CHECK + "?sid=" + id + "&client_secret=" + secret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
|
||||
import io.kamax.mxisd.config.ViewConfig;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.util.FileUtil;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
public class RemoteSessionCheckHandler extends BasicHttpHandler {
|
||||
|
||||
private SessionMananger mgr;
|
||||
private ViewConfig viewCfg;
|
||||
|
||||
public RemoteSessionCheckHandler(SessionMananger mgr, ViewConfig viewCfg) {
|
||||
this.mgr = mgr;
|
||||
this.viewCfg = viewCfg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
String sid = getQueryParameter(exchange, "sid");
|
||||
String secret = getQueryParameter(exchange, "client_secret");
|
||||
|
||||
String viewData;
|
||||
try {
|
||||
mgr.validateRemote(sid, secret);
|
||||
viewData = FileUtil.load(viewCfg.getSession().getRemote().getOnCheck().getSuccess());
|
||||
} catch (SessionNotValidatedException e) {
|
||||
viewData = FileUtil.load(viewCfg.getSession().getRemote().getOnCheck().getFailure());
|
||||
}
|
||||
|
||||
writeBodyAsUtf8(exchange, viewData);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
|
||||
import io.kamax.mxisd.config.ViewConfig;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
import io.kamax.mxisd.util.FileUtil;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
public class RemoteSessionStartHandler extends BasicHttpHandler {
|
||||
|
||||
private SessionMananger mgr;
|
||||
private ViewConfig viewCfg;
|
||||
|
||||
public RemoteSessionStartHandler(SessionMananger mgr, ViewConfig viewCfg) {
|
||||
this.mgr = mgr;
|
||||
this.viewCfg = viewCfg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
String sid = getQueryParameter(exchange, "sid");
|
||||
String secret = getQueryParameter(exchange, "client_secret");
|
||||
IThreePidSession session = mgr.createRemote(sid, secret);
|
||||
|
||||
String rawData = FileUtil.load(viewCfg.getSession().getRemote().getOnRequest().getSuccess());
|
||||
String data = rawData.replace("${checkLink}", RemoteIdentityAPIv1.getSessionCheck(session.getId(), session.getSecret()));
|
||||
writeBodyAsUtf8(exchange, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import io.kamax.mxisd.http.io.identity.RequestTokenResponse;
|
||||
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
||||
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
@@ -41,9 +41,9 @@ public class SessionStartHandler extends BasicHttpHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionStartHandler.class);
|
||||
|
||||
private SessionMananger mgr;
|
||||
private SessionManager mgr;
|
||||
|
||||
public SessionStartHandler(SessionMananger mgr) {
|
||||
public SessionStartHandler(SessionManager mgr) {
|
||||
this.mgr = mgr;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.BindRequest;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.util.QueryParameterUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@@ -44,10 +44,10 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
||||
|
||||
private SessionMananger mgr;
|
||||
private SessionManager mgr;
|
||||
private InvitationManager invMgr;
|
||||
|
||||
public SessionTpidBindHandler(SessionMananger mgr, InvitationManager invMgr) {
|
||||
public SessionTpidBindHandler(SessionManager mgr, InvitationManager invMgr) {
|
||||
this.mgr = mgr;
|
||||
this.invMgr = invMgr;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -34,11 +34,11 @@ public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/getValidated3pid";
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
||||
|
||||
private SessionMananger mgr;
|
||||
private SessionManager mgr;
|
||||
|
||||
public SessionTpidGetValidatedHandler(SessionMananger mgr) {
|
||||
public SessionTpidGetValidatedHandler(SessionManager mgr) {
|
||||
this.mgr = mgr;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,27 +23,23 @@ package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidUnbindHandler.class);
|
||||
private final SessionManager sessionMgr;
|
||||
|
||||
private final SessionMananger sessMgr;
|
||||
|
||||
public SessionTpidUnbindHandler(SessionMananger sessMgr) {
|
||||
this.sessMgr = sessMgr;
|
||||
public SessionTpidUnbindHandler(SessionManager sessionMgr) {
|
||||
this.sessionMgr = sessionMgr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
JsonObject body = parseJsonObject(exchange);
|
||||
sessMgr.unbind(body);
|
||||
sessionMgr.unbind(body);
|
||||
writeBodyAsUtf8(exchange, "{}");
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import io.kamax.mxisd.config.ViewConfig;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.SuccessStatusJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.kamax.mxisd.session.ValidationResult;
|
||||
import io.kamax.mxisd.util.FileUtil;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
@@ -44,11 +44,11 @@ public class SessionValidateHandler extends BasicHttpHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
|
||||
|
||||
private SessionMananger mgr;
|
||||
private SessionManager mgr;
|
||||
private ServerConfig srvCfg;
|
||||
private ViewConfig viewCfg;
|
||||
|
||||
public SessionValidateHandler(SessionMananger mgr, ServerConfig srvCfg, ViewConfig viewCfg) {
|
||||
public SessionValidateHandler(SessionManager mgr, ServerConfig srvCfg, ViewConfig viewCfg) {
|
||||
this.mgr = mgr;
|
||||
this.srvCfg = srvCfg;
|
||||
this.viewCfg = viewCfg;
|
||||
@@ -72,11 +72,11 @@ public class SessionValidateHandler extends BasicHttpHandler {
|
||||
if (isHtmlRequest) {
|
||||
handleHtmlRequest(exchange, medium, sid, secret, token);
|
||||
} else {
|
||||
handleJsonRequest(exchange, medium, sid, secret, token);
|
||||
handleJsonRequest(exchange, sid, secret, token);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleHtmlRequest(HttpServerExchange exchange, String medium, String sid, String secret, String token) {
|
||||
private void handleHtmlRequest(HttpServerExchange exchange, String medium, String sid, String secret, String token) {
|
||||
log.info("Validating session {} for medium {}", sid, medium);
|
||||
ValidationResult r = mgr.validate(sid, secret, token);
|
||||
log.info("Session {} was validated", sid);
|
||||
@@ -93,24 +93,18 @@ public class SessionValidateHandler extends BasicHttpHandler {
|
||||
exchange.getResponseHeaders().add(HttpString.tryFromString("Location"), url);
|
||||
} else {
|
||||
try {
|
||||
String rawData = FileUtil.load(viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess());
|
||||
if (r.isCanRemote()) {
|
||||
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
|
||||
String data = rawData.replace("${remoteSessionLink}", url);
|
||||
writeBodyAsUtf8(exchange, data);
|
||||
} else {
|
||||
writeBodyAsUtf8(exchange, rawData);
|
||||
}
|
||||
String data = FileUtil.load(viewCfg.getSession().getOnTokenSubmit().getSuccess());
|
||||
writeBodyAsUtf8(exchange, data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleJsonRequest(HttpServerExchange exchange, String medium, String sid, String secret, String token) {
|
||||
private void handleJsonRequest(HttpServerExchange exchange, String sid, String secret, String token) {
|
||||
log.info("Requested: {}", exchange.getRequestURL());
|
||||
|
||||
ValidationResult r = mgr.validate(sid, secret, token);
|
||||
mgr.validate(sid, secret, token);
|
||||
log.info("Session {} was validated", sid);
|
||||
|
||||
respondJson(exchange, new SuccessStatusJson(true));
|
||||
|
||||
228
src/main/java/io/kamax/mxisd/session/SessionManager.java
Normal file
228
src/main/java/io/kamax/mxisd/session/SessionManager.java
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.session;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.matrix._MatrixID;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.SessionConfig;
|
||||
import io.kamax.mxisd.exception.NotAllowedException;
|
||||
import io.kamax.mxisd.exception.NotImplementedException;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.exception.SessionUnknownException;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
import io.kamax.mxisd.notification.NotificationManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
||||
|
||||
public class SessionManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||
|
||||
private SessionConfig cfg;
|
||||
private MatrixConfig mxCfg;
|
||||
private IStorage storage;
|
||||
private NotificationManager notifMgr;
|
||||
private LookupStrategy lookupMgr;
|
||||
|
||||
// FIXME export into central class, set version
|
||||
private CloseableHttpClient client;
|
||||
|
||||
public SessionManager(
|
||||
SessionConfig cfg,
|
||||
MatrixConfig mxCfg,
|
||||
IStorage storage,
|
||||
NotificationManager notifMgr,
|
||||
LookupStrategy lookupMgr,
|
||||
CloseableHttpClient client
|
||||
) {
|
||||
this.cfg = cfg;
|
||||
this.mxCfg = mxCfg;
|
||||
this.storage = storage;
|
||||
this.notifMgr = notifMgr;
|
||||
this.lookupMgr = lookupMgr;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private ThreePidSession getSession(String sid, String secret) {
|
||||
Optional<IThreePidSessionDao> dao = storage.getThreePidSession(sid);
|
||||
if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) {
|
||||
throw new SessionUnknownException();
|
||||
}
|
||||
|
||||
return new ThreePidSession(dao.get());
|
||||
}
|
||||
|
||||
private ThreePidSession getSessionIfValidated(String sid, String secret) {
|
||||
ThreePidSession session = getSession(sid, secret);
|
||||
if (!session.isValidated()) {
|
||||
throw new SessionNotValidatedException();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
||||
PolicyTemplate policy = cfg.getPolicy().getValidation();
|
||||
if (!policy.isEnabled()) {
|
||||
throw new NotAllowedException("Validating 3PID is disabled");
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
log.info("Server {} is asking to create session for {} (Attempt #{}) - Next link: {}", server, tpid, attempt, nextLink);
|
||||
Optional<IThreePidSessionDao> dao = storage.findThreePidSession(tpid, secret);
|
||||
if (dao.isPresent()) {
|
||||
ThreePidSession session = new ThreePidSession(dao.get());
|
||||
log.info("We already have a session for {}: {}", tpid, session.getId());
|
||||
if (session.getAttempt() < attempt) {
|
||||
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
|
||||
notifMgr.sendForValidation(session);
|
||||
log.info("Sent validation notification to {}", tpid);
|
||||
session.increaseAttempt();
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
}
|
||||
|
||||
return session.getId();
|
||||
} else {
|
||||
log.info("No existing session for {}", tpid);
|
||||
|
||||
String sessionId;
|
||||
do {
|
||||
sessionId = Long.toString(System.currentTimeMillis());
|
||||
} while (storage.getThreePidSession(sessionId).isPresent());
|
||||
|
||||
String token = RandomStringUtils.randomNumeric(6);
|
||||
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
|
||||
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
|
||||
|
||||
storage.insertThreePidSession(session.getDao());
|
||||
log.info("Stored session {}", sessionId, tpid, server);
|
||||
|
||||
log.info("Session {} for {}: sending validation notification", sessionId, tpid);
|
||||
notifMgr.sendForValidation(session);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationResult validate(String sid, String secret, String token) {
|
||||
ThreePidSession session = getSession(sid, secret);
|
||||
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
||||
|
||||
session.validate(token);
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Session {} has been validated locally", session.getId());
|
||||
|
||||
ValidationResult r = new ValidationResult(session);
|
||||
session.getNextLink().ifPresent(r::setNextUrl);
|
||||
return r;
|
||||
}
|
||||
|
||||
public ThreePidValidation getValidated(String sid, String secret) {
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
||||
}
|
||||
|
||||
public void bind(String sid, String secret, String mxidRaw) {
|
||||
// We make sure we have an acceptable User ID
|
||||
if (StringUtils.isEmpty(mxidRaw)) {
|
||||
throw new IllegalArgumentException("No Matrix User ID provided");
|
||||
}
|
||||
|
||||
// We ensure the session was validated
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
|
||||
// We parse the Matrix ID as acceptable
|
||||
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
||||
|
||||
// Only accept binds if the domain matches our own
|
||||
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg + " can be bound");
|
||||
}
|
||||
|
||||
log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted",
|
||||
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||
}
|
||||
|
||||
public void unbind(JsonObject reqData) {
|
||||
// TODO also check for HS header to know which domain attempting the unbind
|
||||
if (reqData.entrySet().size() == 2 && reqData.has("mxid") && reqData.has("threepid")) {
|
||||
/* This is a HS request to remove a 3PID and is considered:
|
||||
* - An attack on user privacy
|
||||
* - A baffling spec breakage requiring IS and HS 3PID info to be independent [1]
|
||||
* - A baffling spec breakage that 3PID (un)bind is only one way [2]
|
||||
*
|
||||
* Given the lack of response on our extensive feedback on the proposal [3] which has not landed in the spec yet [4],
|
||||
* We'll be denying such unbind requests and will inform users using their 3PID that a fraudulent attempt of
|
||||
* removing their 3PID binding has been attempted and blocked.
|
||||
*
|
||||
* [1]: https://matrix.org/docs/spec/client_server/r0.4.0.html#adding-account-administrative-contact-information
|
||||
* [2]: https://matrix.org/docs/spec/identity_service/r0.1.0.html#privacy
|
||||
* [3]: https://docs.google.com/document/d/135g2muVxmuml0iUnLoTZxk8M2ZSt3kJzg81chGh51yg/edit
|
||||
* [4]: https://github.com/matrix-org/matrix-doc/issues/1194
|
||||
*/
|
||||
|
||||
log.warn("A remote host attempted to unbind without proper authorization. Request was denied");
|
||||
|
||||
if (!cfg.getPolicy().getUnbind().getFraudulent().getSendWarning()) {
|
||||
log.info("Not sending notification to 3PID owner as per configuration");
|
||||
} else {
|
||||
log.info("Sending notification to 3PID owner as per configuration");
|
||||
|
||||
ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class);
|
||||
Optional<SingleLookupReply> lookup = lookupMgr.findLocal(tpid.getMedium(), tpid.getAddress());
|
||||
if (!lookup.isPresent()) {
|
||||
log.info("No 3PID owner found, not sending any notification");
|
||||
} else {
|
||||
log.info("3PID owner found, sending notification");
|
||||
try {
|
||||
notifMgr.sendForFraudulentUnbind(tpid);
|
||||
log.info("Notification sent");
|
||||
} catch (NotImplementedException e) {
|
||||
log.warn("Unable to send notification: {}", e.getMessage());
|
||||
} catch (RuntimeException e) {
|
||||
log.warn("Unable to send notification due to unknown error. See stacktrace below", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Denying request");
|
||||
throw new NotAllowedException("You have attempted to alter 3PID bindings, which can only be done by the 3PID owner directly. " +
|
||||
"We have informed the 3PID owner of your fraudulent attempt.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.session;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.matrix._MatrixID;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.SessionConfig;
|
||||
import io.kamax.mxisd.exception.*;
|
||||
import io.kamax.mxisd.http.io.identity.RequestTokenResponse;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v1.RemoteIdentityAPIv1;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
import io.kamax.mxisd.matrix.IdentityServerUtils;
|
||||
import io.kamax.mxisd.notification.NotificationManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||
import io.kamax.mxisd.util.GsonParser;
|
||||
import io.kamax.mxisd.util.RestClientUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate.PolicySource;
|
||||
|
||||
public class SessionMananger {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionMananger.class);
|
||||
|
||||
private SessionConfig cfg;
|
||||
private MatrixConfig mxCfg;
|
||||
private IStorage storage;
|
||||
private NotificationManager notifMgr;
|
||||
private LookupStrategy lookupMgr;
|
||||
|
||||
private GsonParser parser = new GsonParser();
|
||||
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); // FIXME refactor for sessions handling their own stuff
|
||||
|
||||
// FIXME export into central class, set version
|
||||
private CloseableHttpClient client;
|
||||
|
||||
public SessionMananger(
|
||||
SessionConfig cfg,
|
||||
MatrixConfig mxCfg,
|
||||
IStorage storage,
|
||||
NotificationManager notifMgr,
|
||||
LookupStrategy lookupMgr,
|
||||
CloseableHttpClient client
|
||||
) {
|
||||
this.cfg = cfg;
|
||||
this.mxCfg = mxCfg;
|
||||
this.storage = storage;
|
||||
this.notifMgr = notifMgr;
|
||||
this.lookupMgr = lookupMgr;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private boolean isLocal(ThreePid tpid) {
|
||||
if (!ThreePidMedium.Email.is(tpid.getMedium())) { // We can only handle E-mails for now
|
||||
return false;
|
||||
}
|
||||
|
||||
String domain = tpid.getAddress().split("@")[1];
|
||||
return StringUtils.equalsIgnoreCase(mxCfg.getDomain(), domain);
|
||||
}
|
||||
|
||||
private ThreePidSession getSession(String sid, String secret) {
|
||||
Optional<IThreePidSessionDao> dao = storage.getThreePidSession(sid);
|
||||
if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) {
|
||||
throw new SessionUnknownException();
|
||||
}
|
||||
|
||||
return new ThreePidSession(dao.get());
|
||||
}
|
||||
|
||||
private ThreePidSession getSessionIfValidated(String sid, String secret) {
|
||||
ThreePidSession session = getSession(sid, secret);
|
||||
if (!session.isValidated()) {
|
||||
throw new SessionNotValidatedException();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
||||
PolicyTemplate policy = cfg.getPolicy().getValidation();
|
||||
if (!policy.isEnabled()) {
|
||||
throw new NotAllowedException("Validating 3PID is disabled globally");
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
log.info("Server {} is asking to create session for {} (Attempt #{}) - Next link: {}", server, tpid, attempt, nextLink);
|
||||
Optional<IThreePidSessionDao> dao = storage.findThreePidSession(tpid, secret);
|
||||
if (dao.isPresent()) {
|
||||
ThreePidSession session = new ThreePidSession(dao.get());
|
||||
log.info("We already have a session for {}: {}", tpid, session.getId());
|
||||
if (session.getAttempt() < attempt) {
|
||||
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
|
||||
notifMgr.sendForValidation(session);
|
||||
log.info("Sent validation notification to {}", tpid);
|
||||
session.increaseAttempt();
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
}
|
||||
|
||||
return session.getId();
|
||||
} else {
|
||||
log.info("No existing session for {}", tpid);
|
||||
|
||||
boolean isLocal = isLocal(tpid);
|
||||
log.info("Is 3PID bound to local domain? {}", isLocal);
|
||||
|
||||
// This might need a configuration by medium type?
|
||||
PolicySource policySource = policy.forIf(isLocal);
|
||||
if (!policySource.isEnabled() || (!policySource.toLocal() && !policySource.toRemote())) {
|
||||
log.info("Session for {}: cancelled due to policy", tpid);
|
||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
||||
}
|
||||
|
||||
String sessionId;
|
||||
do {
|
||||
sessionId = Long.toString(System.currentTimeMillis());
|
||||
} while (storage.getThreePidSession(sessionId).isPresent());
|
||||
|
||||
String token = RandomStringUtils.randomNumeric(6);
|
||||
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
|
||||
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
|
||||
|
||||
// This might need a configuration by medium type?
|
||||
if (policySource.toLocal()) {
|
||||
log.info("Session {} for {}: sending local validation notification", sessionId, tpid);
|
||||
notifMgr.sendForValidation(session);
|
||||
} else {
|
||||
log.info("Session {} for {}: sending remote-only validation notification", sessionId, tpid);
|
||||
notifMgr.sendForRemoteValidation(session);
|
||||
}
|
||||
|
||||
storage.insertThreePidSession(session.getDao());
|
||||
log.info("Stored session {}", sessionId, tpid, server);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationResult validate(String sid, String secret, String token) {
|
||||
ThreePidSession session = getSession(sid, secret);
|
||||
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
||||
|
||||
boolean isLocal = isLocal(session.getThreePid());
|
||||
PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal);
|
||||
if (!policy.isEnabled()) {
|
||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
||||
}
|
||||
|
||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium()) && session.isValidated() && session.isRemote()) {
|
||||
submitRemote(session, token);
|
||||
session.validateRemote();
|
||||
return new ValidationResult(session, false);
|
||||
}
|
||||
|
||||
session.validate(token);
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Session {} has been validated locally", session.getId());
|
||||
|
||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium()) && session.isValidated() && policy.toRemote()) {
|
||||
createRemote(sid, secret);
|
||||
// FIXME make the message configurable/customizable (templates?)
|
||||
throw new MessageForClientException("You will receive a NEW code from another number. Enter it below");
|
||||
}
|
||||
|
||||
// FIXME definitely doable in a nicer way
|
||||
ValidationResult r = new ValidationResult(session, policy.toRemote());
|
||||
if (!policy.toLocal()) {
|
||||
r.setNextUrl(RemoteIdentityAPIv1.getRequestToken(sid, secret));
|
||||
} else {
|
||||
session.getNextLink().ifPresent(r::setNextUrl);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public ThreePidValidation getValidated(String sid, String secret) {
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
||||
}
|
||||
|
||||
public void bind(String sid, String secret, String mxidRaw) {
|
||||
if (StringUtils.isEmpty(mxidRaw)) {
|
||||
throw new IllegalArgumentException("No Matrix User ID provided");
|
||||
}
|
||||
|
||||
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
|
||||
if (!session.isRemote()) {
|
||||
log.info("Session {} for {}: MXID {} was bound locally", sid, session.getThreePid(), mxid);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: MXID {} bind is remote", sid, session.getThreePid(), mxid);
|
||||
if (!session.isRemoteValidated()) {
|
||||
log.error("Session {} for {}: Not validated remotely", sid, session.getThreePid());
|
||||
throw new SessionNotValidatedException();
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: Performing remote bind", sid, session.getThreePid());
|
||||
|
||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
|
||||
Arrays.asList(
|
||||
new BasicNameValuePair("sid", session.getRemoteId()),
|
||||
new BasicNameValuePair("client_secret", session.getRemoteSecret()),
|
||||
new BasicNameValuePair("mxid", mxid.getId())
|
||||
), StandardCharsets.UTF_8);
|
||||
HttpPost bindReq = new HttpPost(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/bind");
|
||||
bindReq.setEntity(entity);
|
||||
|
||||
try (CloseableHttpResponse response = client.execute(bindReq)) {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
log.error("Session {} for {}: Remote IS {} failed when trying to bind {} for remote session {}\n{}",
|
||||
sid, session.getThreePid(), session.getRemoteServer(), mxid, session.getRemoteId(), body);
|
||||
throw new RemoteIdentityServerException(body);
|
||||
}
|
||||
|
||||
log.error("Session {} for {}: MXID {} was bound remotely", sid, session.getThreePid(), mxid);
|
||||
} catch (IOException e) {
|
||||
log.error("Session {} for {}: I/O Error when trying to bind mxid {}", sid, session.getThreePid(), mxid);
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void unbind(JsonObject reqData) {
|
||||
// TODO also check for HS header to know which domain attempting the unbind
|
||||
if (reqData.entrySet().size() == 2 && reqData.has("mxid") && reqData.has("threepid")) {
|
||||
/* This is a HS request to remove a 3PID and is considered:
|
||||
* - An attack on user privacy
|
||||
* - A baffling spec breakage requiring IS and HS 3PID info to be independent [1]
|
||||
* - A baffling spec breakage that 3PID (un)bind is only one way [2]
|
||||
*
|
||||
* Given the lack of response on our extensive feedback on the proposal [3] which has not landed in the spec yet [4],
|
||||
* We'll be denying such unbind requests and will inform users using their 3PID that a fraudulent attempt of
|
||||
* removing their 3PID binding has been attempted and blocked.
|
||||
*
|
||||
* [1]: https://matrix.org/docs/spec/client_server/r0.4.0.html#adding-account-administrative-contact-information
|
||||
* [2]: https://matrix.org/docs/spec/identity_service/r0.1.0.html#privacy
|
||||
* [3]: https://docs.google.com/document/d/135g2muVxmuml0iUnLoTZxk8M2ZSt3kJzg81chGh51yg/edit
|
||||
* [4]: https://github.com/matrix-org/matrix-doc/issues/1194
|
||||
*/
|
||||
|
||||
log.warn("A remote host attempted to unbind without proper authorization. Request was denied");
|
||||
|
||||
if (!cfg.getPolicy().getUnbind().getFraudulent().getSendWarning()) {
|
||||
log.info("Not sending notification to 3PID owner as per configuration");
|
||||
} else {
|
||||
log.info("Sending notification to 3PID owner as per configuration");
|
||||
|
||||
ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class);
|
||||
Optional<SingleLookupReply> lookup = lookupMgr.findLocal(tpid.getMedium(), tpid.getAddress());
|
||||
if (!lookup.isPresent()) {
|
||||
log.info("No 3PID owner found, not sending any notification");
|
||||
} else {
|
||||
log.info("3PID owner found, sending notification");
|
||||
try {
|
||||
notifMgr.sendForFraudulentUnbind(tpid);
|
||||
log.info("Notification sent");
|
||||
} catch (NotImplementedException e) {
|
||||
log.warn("Unable to send notification: {}", e.getMessage());
|
||||
} catch (RuntimeException e) {
|
||||
log.warn("Unable to send notification due to unknown error. See stacktrace below", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Denying request");
|
||||
throw new NotAllowedException("You have attempted to alter 3PID bindings, which can only be done by the 3PID owner directly. " +
|
||||
"We have informed the 3PID owner of your fraudulent attempt.");
|
||||
}
|
||||
|
||||
public IThreePidSession createRemote(String sid, String secret) {
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
log.info("Creating remote 3PID session for {} with local session [{}] to {}", session.getThreePid(), sid);
|
||||
|
||||
boolean isLocal = isLocal(session.getThreePid());
|
||||
PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal);
|
||||
if (!policy.isEnabled() || !policy.toRemote()) {
|
||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
||||
}
|
||||
log.info("Remote 3PID is allowed by policy");
|
||||
|
||||
List<String> servers = mxCfg.getIdentity().getServers(policy.getToRemote().getServer());
|
||||
if (servers.isEmpty()) {
|
||||
throw new FeatureNotAvailable("Remote 3PID sessions are enabled but server list is " +
|
||||
"misconstrued (invalid ID or empty list");
|
||||
}
|
||||
|
||||
String is = servers.get(0);
|
||||
String url = IdentityServerUtils.findIsUrlForDomain(is).orElse(is);
|
||||
log.info("Will use IS endpoint {}", url);
|
||||
|
||||
String remoteSecret = session.isRemote() ? session.getRemoteSecret() : RandomStringUtils.randomAlphanumeric(16);
|
||||
|
||||
JsonObject body = new JsonObject();
|
||||
body.addProperty("client_secret", remoteSecret);
|
||||
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||
body.addProperty("send_attempt", session.increaseAndGetRemoteAttempt());
|
||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium())) {
|
||||
try {
|
||||
Phonenumber.PhoneNumber msisdn = phoneUtil.parse("+" + session.getThreePid().getAddress(), null);
|
||||
String country = phoneUtil.getRegionCodeForNumber(msisdn).toUpperCase();
|
||||
body.addProperty("phone_number", phoneUtil.format(msisdn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL));
|
||||
body.addProperty("country", country);
|
||||
} catch (NumberParseException e) {
|
||||
throw new InternalServerError(e);
|
||||
}
|
||||
} else {
|
||||
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||
}
|
||||
|
||||
log.info("Requesting remote session with attempt {}", session.getRemoteAttempt());
|
||||
HttpPost tokenReq = RestClientUtils.post(url + "/_matrix/identity/api/v1/validate/" + session.getThreePid().getMedium() + "/requestToken", body);
|
||||
try (CloseableHttpResponse response = client.execute(tokenReq)) {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
JsonObject obj = parser.parseOptional(response).orElseThrow(() -> new RemoteIdentityServerException("Status " + status));
|
||||
throw new RemoteIdentityServerException(obj.get("errcode").getAsString() + ": " + obj.get("error").getAsString());
|
||||
}
|
||||
|
||||
RequestTokenResponse data = new GsonParser().parse(response, RequestTokenResponse.class);
|
||||
log.info("Remote Session ID: {}", data.getSid());
|
||||
|
||||
session.setRemoteData(url, data.getSid(), remoteSecret, 1);
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Updated Session {} with remote data", sid);
|
||||
|
||||
return session;
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to create remote session with {} for {}: {}", url, session.getThreePid(), e.getMessage());
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void submitRemote(ThreePidSession session, String token) {
|
||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
|
||||
Arrays.asList(
|
||||
new BasicNameValuePair("sid", session.getRemoteId()),
|
||||
new BasicNameValuePair("client_secret", session.getRemoteSecret()),
|
||||
new BasicNameValuePair("token", token)
|
||||
), StandardCharsets.UTF_8);
|
||||
HttpPost submitReq = new HttpPost(session.getRemoteServer() + "/_matrix/identity/api/v1/submitToken");
|
||||
submitReq.setEntity(entity);
|
||||
|
||||
try (CloseableHttpResponse response = client.execute(submitReq)) {
|
||||
JsonObject o = new GsonParser().parse(response.getEntity().getContent());
|
||||
if (!o.has("success") || !o.get("success").getAsBoolean()) {
|
||||
String errcode = o.get("errcode").getAsString();
|
||||
throw new RemoteIdentityServerException(errcode + ": " + o.get("error").getAsString());
|
||||
}
|
||||
|
||||
log.info("Successfully submitted validation token for {} to {}", session.getThreePid(), session.getRemoteServer());
|
||||
} catch (IOException e) {
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateRemote(String sid, String secret) {
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
if (!session.isRemote()) {
|
||||
throw new NotAllowedException("Cannot remotely validate a local session");
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: Validating remote 3PID session {} on {}", sid, session.getThreePid(), session.getRemoteId(), session.getRemoteServer());
|
||||
if (session.isRemoteValidated()) {
|
||||
log.info("Session {} for {}: Already remotely validated", sid, session.getThreePid());
|
||||
return;
|
||||
}
|
||||
|
||||
HttpGet validateReq = new HttpGet(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/getValidated3pid?sid=" + session.getRemoteId() + "&client_secret=" + session.getRemoteSecret());
|
||||
try (CloseableHttpResponse response = client.execute(validateReq)) {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
throw new RemoteIdentityServerException("Remote identity server returned with status " + status);
|
||||
}
|
||||
|
||||
JsonObject o = new GsonParser().parse(response.getEntity().getContent());
|
||||
if (o.has("errcode")) {
|
||||
String errcode = o.get("errcode").getAsString();
|
||||
if (StringUtils.equals("M_SESSION_NOT_VALIDATED", errcode)) {
|
||||
throw new SessionNotValidatedException();
|
||||
} else if (StringUtils.equals("M_NO_VALID_SESSION", errcode)) {
|
||||
throw new SessionUnknownException();
|
||||
} else {
|
||||
throw new RemoteIdentityServerException("Unknown error while validating Remote 3PID session: " + errcode + " - " + o.get("error").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
if (o.has("validated_at")) {
|
||||
ThreePid remoteThreePid = new ThreePid(o.get("medium").getAsString(), o.get("address").getAsString());
|
||||
if (!session.getThreePid().equals(remoteThreePid)) { // sanity check
|
||||
throw new InternalServerError("Local 3PID " + session.getThreePid() + " and remote 3PID " + remoteThreePid + " do not match for session " + session.getId());
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: Remotely validated successfully", sid, session.getThreePid());
|
||||
session.validateRemote();
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Session {} was updated in storage", sid);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Session {} for {}: Failed to validated remotely on {}: {}", sid, session.getThreePid(), session.getRemoteServer(), e.getMessage());
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,22 +27,16 @@ import java.util.Optional;
|
||||
public class ValidationResult {
|
||||
|
||||
private IThreePidSession session;
|
||||
private boolean canRemote;
|
||||
private String nextUrl;
|
||||
|
||||
public ValidationResult(IThreePidSession session, boolean canRemote) {
|
||||
public ValidationResult(IThreePidSession session) {
|
||||
this.session = session;
|
||||
this.canRemote = canRemote;
|
||||
}
|
||||
|
||||
public IThreePidSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public boolean isCanRemote() {
|
||||
return canRemote;
|
||||
}
|
||||
|
||||
public Optional<String> getNextUrl() {
|
||||
return Optional.ofNullable(nextUrl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user