Fix handling various GET and POST content types/logic for submitToken
- Properly support Form-encoded POST - Fix #167
This commit is contained in:
@@ -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())))
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@@ -33,6 +33,10 @@ public class BuiltInEmailConnectorSupplier implements EmailConnectorSupplier {
|
||||
|
||||
@Override
|
||||
public Optional<EmailConnector> 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));
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -33,15 +33,15 @@ public class BuiltInPhoneConnectorSupplier implements PhoneConnectorSupplier {
|
||||
|
||||
@Override
|
||||
public Optional<PhoneConnector> 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();
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user