diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java index 0ee9d9b..26a4426 100644 --- a/src/main/java/io/kamax/mxisd/HttpMxisd.java +++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java @@ -73,7 +73,6 @@ public class HttpMxisd { HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs())); HttpHandler storeInvHandler = SaneHandler.around(new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager())); - HttpHandler sessValidateHandler = SaneHandler.around(new SessionValidateHandler(m.getSession(), m.getConfig().getServer(), m.getConfig().getView())); httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing() @@ -103,8 +102,8 @@ public class HttpMxisd { .post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity()))) .post(StoreInviteHandler.Path, storeInvHandler) .post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession()))) - .get(SessionValidateHandler.Path, sessValidateHandler) - .post(SessionValidateHandler.Path, sessValidateHandler) + .get(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationGetHandler(m.getSession(), m.getConfig()))) + .post(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationPostHandler(m.getSession()))) .get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession()))) .post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvite()))) .post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession()))) diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java index fa059f8..26e1eeb 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java @@ -31,6 +31,7 @@ import io.kamax.mxisd.proxy.Response; import io.kamax.mxisd.util.RestClientUtils; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.FormData; import io.undertow.util.HttpString; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -48,10 +49,7 @@ import java.net.InetSocketAddress; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import java.util.Deque; -import java.util.LinkedList; -import java.util.Map; -import java.util.Optional; +import java.util.*; public abstract class BasicHttpHandler implements HttpHandler { @@ -122,6 +120,20 @@ public abstract class BasicHttpHandler implements HttpHandler { return GsonUtil.parseObj(getBodyUtf8(exchange)); } + protected String getOrThrow(FormData data, String key) { + FormData.FormValue value = data.getFirst(key); + if (Objects.isNull(value)) { + throw new IllegalArgumentException("Form key " + key + " is missing"); + } + + String object = value.getValue(); + if (Objects.isNull(object)) { + throw new IllegalArgumentException("Form key " + key + " does not have a value"); + } + + return object; + } + protected void putHeader(HttpServerExchange ex, String name, String value) { ex.getResponseHeaders().put(HttpString.tryFromString(name), value); } diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidateHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidateHandler.java index 589d904..00dcd90 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidateHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidateHandler.java @@ -20,94 +20,36 @@ package io.kamax.mxisd.http.undertow.handler.identity.v1; -import io.kamax.mxisd.config.ServerConfig; -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.SessionManager; import io.kamax.mxisd.session.ValidationResult; -import io.kamax.mxisd.util.FileUtil; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.HttpString; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -public class SessionValidateHandler extends BasicHttpHandler { +public abstract class SessionValidateHandler extends BasicHttpHandler { public static final String Path = IsAPIv1.Base + "/validate/{medium}/submitToken"; private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class); private SessionManager mgr; - private ServerConfig srvCfg; - private ViewConfig viewCfg; - public SessionValidateHandler(SessionManager mgr, ServerConfig srvCfg, ViewConfig viewCfg) { + public SessionValidateHandler(SessionManager mgr) { this.mgr = mgr; - this.srvCfg = srvCfg; - this.viewCfg = viewCfg; } - @Override - public void handleRequest(HttpServerExchange exchange) { - String medium = getQueryParameter(exchange, "medium"); - String sid = getQueryParameter(exchange, "sid"); - String secret = getQueryParameter(exchange, "client_secret"); - String token = getQueryParameter(exchange, "token"); - - boolean isHtmlRequest = false; - for (String v : exchange.getRequestHeaders().get("Accept")) { - if (StringUtils.startsWithIgnoreCase(v, "text/html")) { - isHtmlRequest = true; - break; - } + protected ValidationResult handleRequest(String sid, String secret, String token) { + if (StringUtils.isEmpty(sid)) { + throw new IllegalArgumentException("sid is not set or is empty"); } - if (isHtmlRequest) { - handleHtmlRequest(exchange, medium, sid, secret, token); - } else { - handleJsonRequest(exchange, sid, secret, token); + if (StringUtils.isEmpty(secret)) { + throw new IllegalArgumentException("client secret is not set or is empty"); } - } - 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); - if (r.getNextUrl().isPresent()) { - String url = r.getNextUrl().get(); - try { - url = new URL(url).toString(); - } catch (MalformedURLException e) { - log.info("Session next URL {} is not a valid one, will prepend public URL {}", url, srvCfg.getPublicUrl()); - url = srvCfg.getPublicUrl() + r.getNextUrl().get(); - } - log.info("Session {} validation: next URL is present, redirecting to {}", sid, url); - exchange.setStatusCode(302); - exchange.getResponseHeaders().add(HttpString.tryFromString("Location"), url); - } else { - try { - String data = FileUtil.load(viewCfg.getSession().getOnTokenSubmit().getSuccess()); - writeBodyAsUtf8(exchange, data); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - private void handleJsonRequest(HttpServerExchange exchange, String sid, String secret, String token) { - log.info("Requested: {}", exchange.getRequestURL()); - - mgr.validate(sid, secret, token); - log.info("Session {} was validated", sid); - - respondJson(exchange, new SuccessStatusJson(true)); + return mgr.validate(sid, secret, token); } } diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidationGetHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidationGetHandler.java new file mode 100644 index 0000000..f9294a2 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidationGetHandler.java @@ -0,0 +1,82 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2019 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 . + */ + +package io.kamax.mxisd.http.undertow.handler.identity.v1; + +import io.kamax.mxisd.config.MxisdConfig; +import io.kamax.mxisd.config.ServerConfig; +import io.kamax.mxisd.config.ViewConfig; +import io.kamax.mxisd.session.SessionManager; +import io.kamax.mxisd.session.ValidationResult; +import io.kamax.mxisd.util.FileUtil; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class SessionValidationGetHandler extends SessionValidateHandler { + + private transient final Logger log = LoggerFactory.getLogger(SessionValidationGetHandler.class); + + private ServerConfig srvCfg; + private ViewConfig viewCfg; + + public SessionValidationGetHandler(SessionManager mgr, MxisdConfig cfg) { + super(mgr); + this.srvCfg = cfg.getServer(); + this.viewCfg = cfg.getView(); + } + + @Override + public void handleRequest(HttpServerExchange exchange) { + log.info("Handling GET request to validate session"); + + String sid = getQueryParameter(exchange, "sid"); + String secret = getQueryParameter(exchange, "client_secret"); + String token = getQueryParameter(exchange, "token"); + + ValidationResult r = handleRequest(sid, secret, token); + log.info("Session {} was validated", sid); + if (r.getNextUrl().isPresent()) { + String url = r.getNextUrl().get(); + try { + url = new URL(url).toString(); + } catch (MalformedURLException e) { + log.info("Session next URL {} is not a valid one, will prepend public URL {}", url, srvCfg.getPublicUrl()); + url = srvCfg.getPublicUrl() + r.getNextUrl().get(); + } + log.info("Session {} validation: next URL is present, redirecting to {}", sid, url); + exchange.setStatusCode(302); + exchange.getResponseHeaders().add(HttpString.tryFromString("Location"), url); + } else { + try { + String data = FileUtil.load(viewCfg.getSession().getOnTokenSubmit().getSuccess()); + writeBodyAsUtf8(exchange, data); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidationPostHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidationPostHandler.java new file mode 100644 index 0000000..7ccdcec --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionValidationPostHandler.java @@ -0,0 +1,80 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2019 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 . + */ + +package io.kamax.mxisd.http.undertow.handler.identity.v1; + +import com.google.gson.JsonObject; +import io.kamax.matrix.json.GsonUtil; +import io.kamax.mxisd.http.io.identity.SuccessStatusJson; +import io.kamax.mxisd.session.SessionManager; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.FormData; +import io.undertow.server.handlers.form.FormParserFactory; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class SessionValidationPostHandler extends SessionValidateHandler { + + private transient final Logger log = LoggerFactory.getLogger(SessionValidationPostHandler.class); + + private FormParserFactory factory; + + public SessionValidationPostHandler(SessionManager mgr) { + super(mgr); + factory = FormParserFactory.builder().build(); + } + + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + log.info("Handling POST request to validate session"); + + String sid; + String secret; + String token; + + String contentType = getContentType(exchange).orElseThrow(() -> new IllegalArgumentException("Content type header is not set")); + if (StringUtils.equals(contentType, "application/json")) { // FIXME use MIME parsing tools + log.info("Parsing as JSON data"); + + JsonObject body = parseJsonObject(exchange); + sid = GsonUtil.getStringOrThrow(body, "sid"); + secret = GsonUtil.getStringOrThrow(body, "client_secret"); + token = GsonUtil.getStringOrThrow(body, "token"); + } else if (StringUtils.equals(contentType, "application/x-www-form-urlencoded")) { // FIXME use MIME parsing tools + log.info("Parsing as Form data"); + + FormData data = factory.createParser(exchange).parseBlocking(); + sid = getOrThrow(data, "sid"); + secret = getOrThrow(data, "client_secret"); + token = getOrThrow(data, "token"); + } else { + log.info("Unsupported Content type: {}", contentType); + throw new IllegalArgumentException("Unsupported Content type: " + contentType); + } + + handleRequest(sid, secret, token); + respondJson(exchange, new SuccessStatusJson(true)); + } + +} diff --git a/src/main/java/io/kamax/mxisd/session/SessionManager.java b/src/main/java/io/kamax/mxisd/session/SessionManager.java index 66e5d11..aea6eb1 100644 --- a/src/main/java/io/kamax/mxisd/session/SessionManager.java +++ b/src/main/java/io/kamax/mxisd/session/SessionManager.java @@ -38,8 +38,8 @@ 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.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.http.impl.client.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -139,12 +139,13 @@ public class SessionManager { } public ValidationResult validate(String sid, String secret, String token) { + log.info("Validating session {}", sid); ThreePidSession session = getSession(sid, secret); - log.info("Attempting validation for session {} from {}", session.getId(), session.getServer()); + log.info("Session {} is from {}", session.getId(), session.getServer()); session.validate(token); storage.updateThreePidSession(session.getDao()); - log.info("Session {} has been validated locally", session.getId()); + log.info("Session {} has been validated", session.getId()); ValidationResult r = new ValidationResult(session); session.getNextLink().ifPresent(r::setNextUrl); diff --git a/src/main/java/io/kamax/mxisd/threepid/connector/email/BlackholeEmailConnector.java b/src/main/java/io/kamax/mxisd/threepid/connector/email/BlackholeEmailConnector.java new file mode 100644 index 0000000..0c862e6 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/threepid/connector/email/BlackholeEmailConnector.java @@ -0,0 +1,37 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2019 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 . + */ + +package io.kamax.mxisd.threepid.connector.email; + +public class BlackholeEmailConnector implements EmailConnector { + + public static final String ID = "none"; + + @Override + public String getId() { + return ID; + } + + @Override + public void send(String senderAddress, String senderName, String recipient, String content) { + //dev/null + } + +} diff --git a/src/main/java/io/kamax/mxisd/threepid/connector/email/BuiltInEmailConnectorSupplier.java b/src/main/java/io/kamax/mxisd/threepid/connector/email/BuiltInEmailConnectorSupplier.java index f2b6d99..f886287 100644 --- a/src/main/java/io/kamax/mxisd/threepid/connector/email/BuiltInEmailConnectorSupplier.java +++ b/src/main/java/io/kamax/mxisd/threepid/connector/email/BuiltInEmailConnectorSupplier.java @@ -33,6 +33,10 @@ public class BuiltInEmailConnectorSupplier implements EmailConnectorSupplier { @Override public Optional apply(EmailConfig cfg, Mxisd mxisd) { + if (StringUtils.equals(BlackholeEmailConnector.ID, cfg.getConnector())) { + return Optional.of(new BlackholeEmailConnector()); + } + if (StringUtils.equals(EmailSmtpConnector.ID, cfg.getConnector())) { EmailSmtpConfig smtpCfg = GsonUtil.get().fromJson(cfg.getConnectors().getOrDefault(EmailSmtpConnector.ID, new JsonObject()), EmailSmtpConfig.class); return Optional.of(new EmailSmtpConnector(smtpCfg)); diff --git a/src/main/java/io/kamax/mxisd/threepid/connector/phone/BlackholePhoneConnector.java b/src/main/java/io/kamax/mxisd/threepid/connector/phone/BlackholePhoneConnector.java index b9378cd..14c243d 100644 --- a/src/main/java/io/kamax/mxisd/threepid/connector/phone/BlackholePhoneConnector.java +++ b/src/main/java/io/kamax/mxisd/threepid/connector/phone/BlackholePhoneConnector.java @@ -24,14 +24,14 @@ public class BlackholePhoneConnector implements PhoneConnector { public static final String ID = "none"; - @Override - public void send(String recipient, String content) { - //dev/null - } - @Override public String getId() { return ID; } + @Override + public void send(String recipient, String content) { + //dev/null + } + } diff --git a/src/main/java/io/kamax/mxisd/threepid/connector/phone/BuiltInPhoneConnectorSupplier.java b/src/main/java/io/kamax/mxisd/threepid/connector/phone/BuiltInPhoneConnectorSupplier.java index d00bcf6..355316f 100644 --- a/src/main/java/io/kamax/mxisd/threepid/connector/phone/BuiltInPhoneConnectorSupplier.java +++ b/src/main/java/io/kamax/mxisd/threepid/connector/phone/BuiltInPhoneConnectorSupplier.java @@ -33,15 +33,15 @@ public class BuiltInPhoneConnectorSupplier implements PhoneConnectorSupplier { @Override public Optional apply(PhoneConfig cfg, Mxisd mxisd) { + if (StringUtils.equals(BlackholePhoneConnector.ID, cfg.getConnector())) { + return Optional.of(new BlackholePhoneConnector()); + } + if (StringUtils.equals(PhoneSmsTwilioConnector.ID, cfg.getConnector())) { PhoneTwilioConfig cCfg = GsonUtil.get().fromJson(cfg.getConnectors().getOrDefault(PhoneSmsTwilioConnector.ID, new JsonObject()), PhoneTwilioConfig.class); return Optional.of(new PhoneSmsTwilioConnector(cCfg)); } - if (StringUtils.equals(BlackholePhoneConnector.ID, cfg.getConnector())) { - return Optional.of(new BlackholePhoneConnector()); - } - return Optional.empty(); }