Compare commits

...

13 Commits

Author SHA1 Message Date
Max Dor
3e22301af7 Properly handle /v1/store-invite 2019-01-16 02:57:40 +01:00
Max Dor
2b202323c0 Catch and handle more exceptions in Base HTTP handler 2019-01-16 02:57:40 +01:00
Max Dor
4ec05f518e Properly handle v1 of 3pid/bind 2019-01-16 02:57:40 +01:00
Max Dor
6da68298b0 Fix invalid paths 2019-01-16 02:57:40 +01:00
Max Dor
aecaafdeca Set theme jekyll for github pages 2019-01-16 01:31:21 +01:00
Max Dor
d885932f45 Fix loading failures of JDBC drivers for SQL-based Identity stores 2019-01-15 06:22:03 +01:00
Max Dor
c689a3f161 Fix classpath resources config 2019-01-13 00:30:52 +01:00
Max Dor
7805112548 Fix #110 2019-01-11 23:07:58 +01:00
Max Dor
3e89f0bc5e Fix #109 2019-01-11 23:07:52 +01:00
Max Dor
c6b8f7d48e Better handle of File reading / Input Streams 2019-01-11 23:02:57 +01:00
Max Dor
83377ebee0 Protect against NPE 2019-01-11 22:08:35 +01:00
Max Dor
2aa6e4d142 Fix missing .html from Spring to Undertow port 2019-01-11 22:08:22 +01:00
Max Dor
82a1a3df68 Fix invalid parsing of 3PID medium configs 2019-01-11 21:44:51 +01:00
33 changed files with 601 additions and 117 deletions

View File

@@ -129,7 +129,7 @@ dependencies {
compile 'org.xerial:sqlite-jdbc:3.20.0'
// PostgreSQL
compile 'org.postgresql:postgresql:42.1.4'
compile 'org.postgresql:postgresql:42.2.5'
// MariaDB/MySQL
compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2'

View File

@@ -1 +1 @@
theme: jekyll-theme-cayman
theme: jekyll-theme-hacker

View File

@@ -268,3 +268,10 @@ Structure with example values:
}
```
The base `profile` key is mandatory. `display_name`, `threepids` and `roles` are only to be returned on the relevant request.
If there is no profile, the following response is expected:
```json
{
"profile": {}
}
```

View File

@@ -47,7 +47,7 @@ import io.kamax.mxisd.profile.ProfileManager;
import io.kamax.mxisd.profile.ProfileProviders;
import io.kamax.mxisd.session.SessionMananger;
import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.ormlite.OrmLiteSqliteStorage;
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@@ -88,7 +88,7 @@ public class Mxisd {
srvFetcher = new RemoteIdentityServerFetcher(httpClient);
store = new OrmLiteSqliteStorage(cfg);
store = new OrmLiteSqlStorage(cfg);
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
signMgr = CryptoFactory.getSignatureManager(keyMgr, cfg.getServer());
ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());

View File

@@ -60,7 +60,9 @@ public class GoogleFirebaseBackend {
private FirebaseCredential getCreds(String credsPath) throws IOException {
if (StringUtils.isNotBlank(credsPath)) {
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
try (FileInputStream is = new FileInputStream(credsPath)) {
return FirebaseCredentials.fromCertificate(is);
}
} else {
return FirebaseCredentials.applicationDefault();
}

View File

@@ -0,0 +1,55 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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.backend.sql;
import org.apache.commons.lang3.StringUtils;
public class BuiltInDriverLoader implements DriverLoader {
@Override
public void accept(String s) {
String className = null;
if (StringUtils.equals("sqlite", s)) {
className = "org.sqlite.JDBC";
}
if (StringUtils.equals("postgresql", s)) {
className = "org.postgresql.Driver";
}
if (StringUtils.equals("mariadb", s)) {
className = "org.mariadb.jdbc.Driver";
}
if (StringUtils.equals("mysql", s)) {
className = "org.mariadb.jdbc.Driver";
}
if (StringUtils.isNotEmpty(className)) {
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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.backend.sql;
import java.util.function.Consumer;
public interface DriverLoader extends Consumer<String> {
}

View File

@@ -0,0 +1,38 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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.backend.sql;
import java.util.Objects;
import java.util.ServiceLoader;
public class Drivers {
private static ServiceLoader<DriverLoader> svcLoader;
public static void load(String type) {
if (Objects.isNull(svcLoader)) {
svcLoader = ServiceLoader.load(DriverLoader.class);
}
svcLoader.iterator().forEachRemaining(drv -> drv.accept(type));
}
}

View File

@@ -37,11 +37,15 @@ public class SqlConnectionPool {
private ComboPooledDataSource ds;
public SqlConnectionPool(SqlConfig cfg) {
Drivers.load(cfg.getType());
ds = new ComboPooledDataSource();
ds.setJdbcUrl("jdbc:" + cfg.getType() + ":" + cfg.getConnection());
ds.setMinPoolSize(1);
ds.setMaxPoolSize(10);
ds.setAcquireIncrement(2);
ds.setAcquireRetryAttempts(10);
ds.setAcquireRetryDelay(1000);
}
public Connection get() throws SQLException {

View File

@@ -95,17 +95,17 @@ public class ViewConfig {
private Remote remote = new Remote();
public Session() {
local.onTokenSubmit.success = "session/local/tokenSubmitSuccess";
local.onTokenSubmit.failure = "session/local/tokenSubmitFailure";
local.onTokenSubmit.success = "classpath:/templates/session/local/tokenSubmitSuccess.html";
local.onTokenSubmit.failure = "classpath:/templates/session/local/tokenSubmitFailure.html";
localRemote.onTokenSubmit.success = "session/localRemote/tokenSubmitSuccess";
localRemote.onTokenSubmit.failure = "session/local/tokenSubmitFailure";
localRemote.onTokenSubmit.success = "classpath:/templates/session/localRemote/tokenSubmitSuccess.html";
localRemote.onTokenSubmit.failure = "classpath:/templates/session/local/tokenSubmitFailure.html";
remote.onRequest.success = "session/remote/requestSuccess";
remote.onRequest.failure = "session/remote/requestFailure";
remote.onRequest.success = "classpath:/templates/session/remote/requestSuccess.html";
remote.onRequest.failure = "classpath:/templates/session/remote/requestFailure.html";
remote.onCheck.success = "session/remote/checkSuccess";
remote.onCheck.failure = "session/remote/checkFailure";
remote.onCheck.success = "classpath:/templates/session/remote/checkSuccess.html";
remote.onCheck.failure = "classpath:/templates/session/remote/checkFailure.html";
}
public Local getLocal() {

View File

@@ -37,8 +37,10 @@ public class YamlConfigLoader {
rep.getPropertyUtils().setAllowReadOnlyProperties(true);
rep.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(MxisdConfig.class), rep);
Object o = yaml.load(new FileInputStream(path));
return GsonUtil.get().fromJson(GsonUtil.get().toJson(o), MxisdConfig.class);
try (FileInputStream is = new FileInputStream(path)) {
Object o = yaml.load(is);
return GsonUtil.get().fromJson(GsonUtil.get().toJson(o), MxisdConfig.class);
}
}
public static Optional<MxisdConfig> tryLoadFromFile(String path) {

View File

@@ -34,8 +34,8 @@ public class RestBackendConfig {
public static class IdentityEndpoints {
private String single = "/_mxisd/backend/api/v1/identity/lookup/single";
private String bulk = "/_mxisd/backend/api/v1/identity/lookup/bulk";
private String single = "/_mxisd/backend/api/v1/identity/single";
private String bulk = "/_mxisd/backend/api/v1/identity/bulk";
public String getSingle() {
return single;

View File

@@ -20,7 +20,6 @@
package io.kamax.mxisd.config.threepid;
import com.google.gson.JsonObject;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
@@ -31,18 +30,18 @@ import java.util.Map;
public class ThreePidConfig {
private Map<String, JsonObject> medium = new HashMap<>();
private Map<String, Object> medium = new HashMap<>();
public ThreePidConfig() {
public ThreePidConfig() { // TODO Check if this is still needed
medium.put(ThreePidMedium.Email.getId(), GsonUtil.makeObj(new EmailConfig()));
medium.put(ThreePidMedium.PhoneNumber.getId(), GsonUtil.makeObj(new PhoneConfig()));
}
public Map<String, JsonObject> getMedium() {
public Map<String, Object> getMedium() {
return medium;
}
public void setMedium(Map<String, JsonObject> medium) {
public void setMedium(Map<String, Object> medium) {
this.medium = medium;
}

View File

@@ -28,7 +28,7 @@ public class ConfigurationException extends RuntimeException {
private String detailedMsg;
public ConfigurationException(String key) {
super("Invalid or empty value for configuration item " + key);
super("Invalid or empty value for configuration item: " + key);
}
public ConfigurationException(Throwable t) {

View File

@@ -0,0 +1,67 @@
/*
* 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.io.identity;
import com.google.gson.annotations.SerializedName;
public class BindRequest {
public static class Keys {
public static final String SessionID = "sid";
public static final String Secret = "client_secret";
public static final String UserID = "mxid";
}
@SerializedName(Keys.SessionID)
private String sid;
@SerializedName(Keys.Secret)
private String secret;
@SerializedName(Keys.UserID)
private String userId;
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}

View File

@@ -0,0 +1,179 @@
/*
* 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.io.identity;
import com.google.gson.annotations.SerializedName;
public class StoreInviteRequest {
// Available keys from Spec + HS implementations reverse-engineering
//
// Synapse: https://github.com/matrix-org/synapse/blob/a219ce87263ad9be887cf039a04b4a1f06b7b0b8/synapse/handlers/room_member.py#L826
public static class Keys {
public static final String Medium = "medium";
public static final String Address = "address";
public static final String RoomID = "room_id";
public static final String RoomAlias = "room_alias"; // Not in the spec, arbitrary
public static final String RoomAvatarURL = "room_avatar_url"; // Not in the spec, arbitrary
public static final String RoomJoinRules = "room_join_rules"; // Not in the spec, arbitrary
public static final String RoomName = "room_name"; // Not in the spec, arbitrary
public static final String Sender = "sender";
public static final String SenderDisplayName = "sender_display_name"; // Not in the spec, arbitrary
public static final String SenderAvatarURL = "sender_avatar_url"; // Not in the spec, arbitrary
public static final String GuestAccessToken = "guest_access_token"; // Not in the spec, arbitrary
public static final String GuestUserID = "guest_user_id"; // Not in the spec, arbitrary
}
@SerializedName(Keys.Medium)
private String medium;
@SerializedName(Keys.Address)
private String address;
@SerializedName(Keys.RoomID)
private String roomId;
@SerializedName(Keys.RoomAlias)
private String roomAlias;
@SerializedName(Keys.RoomAvatarURL)
private String roomAvatarUrl;
@SerializedName(Keys.RoomJoinRules)
private String roomJoinRules;
@SerializedName(Keys.RoomName)
private String roomName;
@SerializedName(Keys.Sender)
private String sender;
@SerializedName(Keys.SenderDisplayName)
private String senderDisplayName;
@SerializedName(Keys.SenderAvatarURL)
private String senderAvatarUrl;
@SerializedName(Keys.GuestAccessToken)
private String guestAccessToken;
@SerializedName(Keys.GuestUserID)
private String guestUserId;
public String getMedium() {
return medium;
}
public void setMedium(String medium) {
this.medium = medium;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public String getRoomAlias() {
return roomAlias;
}
public void setRoomAlias(String roomAlias) {
this.roomAlias = roomAlias;
}
public String getRoomAvatarUrl() {
return roomAvatarUrl;
}
public void setRoomAvatarUrl(String roomAvatarUrl) {
this.roomAvatarUrl = roomAvatarUrl;
}
public String getRoomJoinRules() {
return roomJoinRules;
}
public void setRoomJoinRules(String roomJoinRules) {
this.roomJoinRules = roomJoinRules;
}
public String getRoomName() {
return roomName;
}
public void setRoomName(String roomName) {
this.roomName = roomName;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getSenderDisplayName() {
return senderDisplayName;
}
public void setSenderDisplayName(String senderDisplayName) {
this.senderDisplayName = senderDisplayName;
}
public String getSenderAvatarUrl() {
return senderAvatarUrl;
}
public void setSenderAvatarUrl(String senderAvatarUrl) {
this.senderAvatarUrl = senderAvatarUrl;
}
public String getGuestAccessToken() {
return guestAccessToken;
}
public void setGuestAccessToken(String guestAccessToken) {
this.guestAccessToken = guestAccessToken;
}
public String getGuestUserId() {
return guestUserId;
}
public void setGuestUserId(String guestUserId) {
this.guestUserId = guestUserId;
}
}

View File

@@ -30,6 +30,7 @@ import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HttpString;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,7 +39,10 @@ import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
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;
public abstract class BasicHttpHandler implements HttpHandler {
@@ -49,8 +53,16 @@ public abstract class BasicHttpHandler implements HttpHandler {
}
protected String getQueryParameter(HttpServerExchange exchange, String name) {
return getQueryParameter(exchange.getQueryParameters(), name);
}
protected String getQueryParameter(Map<String, Deque<String>> parms, String name) {
try {
String raw = exchange.getQueryParameters().getOrDefault(name, new LinkedList<>()).peekFirst();
String raw = parms.getOrDefault(name, new LinkedList<>()).peekFirst();
if (StringUtils.isEmpty(raw)) {
return raw;
}
return URLDecoder.decode(raw, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new InternalServerError(e);
@@ -61,22 +73,33 @@ public abstract class BasicHttpHandler implements HttpHandler {
return getQueryParameter(exchange, name);
}
protected Optional<String> getContentType(HttpServerExchange exchange) {
return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Content-Type"));
}
protected void writeBodyAsUtf8(HttpServerExchange exchange, String body) {
exchange.getResponseSender().send(body, StandardCharsets.UTF_8);
}
protected <T> T parseJsonTo(HttpServerExchange exchange, Class<T> type) {
protected String getBodyUtf8(HttpServerExchange exchange) {
try {
return GsonUtil.get().fromJson(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8), type);
return IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected <T> T parseJsonTo(HttpServerExchange exchange, Class<T> type) {
return GsonUtil.get().fromJson(getBodyUtf8(exchange), type);
}
protected JsonObject parseJsonObject(HttpServerExchange exchange, String key) {
return GsonUtil.getObj(parseJsonObject(exchange), key);
}
protected JsonObject parseJsonObject(HttpServerExchange exchange) {
try {
JsonObject base = GsonUtil.parseObj(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8));
return GsonUtil.getObj(base, key);
return GsonUtil.parseObj(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@@ -21,7 +21,9 @@
package io.kamax.mxisd.http.undertow.handler;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.MalformedJsonException;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.InvalidJsonException;
import io.kamax.mxisd.exception.*;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
@@ -63,8 +65,10 @@ public class SaneHandler extends BasicHttpHandler {
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_ALREADY_EXISTS", e.getMessage());
} catch (JsonMemberNotFoundException e) {
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_JSON_MISSING_KEYS", e.getMessage());
} catch (InvalidResponseJsonException | JsonSyntaxException e) {
} catch (InvalidResponseJsonException | JsonSyntaxException | MalformedJsonException e) {
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_JSON", e.getMessage());
} catch (InvalidJsonException e) {
respond(exchange, HttpStatus.SC_BAD_REQUEST, e.getErrorCode(), e.getError());
} catch (InvalidCredentialsException e) {
respond(exchange, HttpStatus.SC_UNAUTHORIZED, "M_UNAUTHORIZED", e.getMessage());
} catch (ObjectNotFoundException e) {

View File

@@ -24,11 +24,8 @@ 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;
import org.apache.commons.io.IOUtils;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
public class RemoteSessionCheckHandler extends BasicHttpHandler {
@@ -45,18 +42,15 @@ public class RemoteSessionCheckHandler extends BasicHttpHandler {
String sid = getQueryParameter(exchange, "sid");
String secret = getQueryParameter(exchange, "client_secret");
String viewData;
try {
FileInputStream f = new FileInputStream(viewCfg.getSession().getRemote().getOnCheck().getSuccess());
String viewData = IOUtils.toString(f, StandardCharsets.UTF_8);
mgr.validateRemote(sid, secret);
writeBodyAsUtf8(exchange, viewData);
viewData = FileUtil.load(viewCfg.getSession().getRemote().getOnCheck().getSuccess());
} catch (SessionNotValidatedException e) {
FileInputStream f = new FileInputStream(viewCfg.getSession().getRemote().getOnCheck().getFailure());
String viewData = IOUtils.toString(f, StandardCharsets.UTF_8);
writeBodyAsUtf8(exchange, viewData);
viewData = FileUtil.load(viewCfg.getSession().getRemote().getOnCheck().getFailure());
}
writeBodyAsUtf8(exchange, viewData);
}
}

View File

@@ -24,11 +24,8 @@ 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;
import org.apache.commons.io.IOUtils;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
public class RemoteSessionStartHandler extends BasicHttpHandler {
@@ -46,8 +43,7 @@ public class RemoteSessionStartHandler extends BasicHttpHandler {
String secret = getQueryParameter(exchange, "client_secret");
IThreePidSession session = mgr.createRemote(sid, secret);
FileInputStream f = new FileInputStream(viewCfg.getSession().getRemote().getOnRequest().getSuccess());
String rawData = IOUtils.toString(f, StandardCharsets.UTF_8);
String rawData = FileUtil.load(viewCfg.getSession().getRemote().getOnRequest().getSuccess());
String data = rawData.replace("${checkLink}", RemoteIdentityAPIv1.getSessionCheck(session.getId(), session.getSecret()));
writeBodyAsUtf8(exchange, data);
}

View File

@@ -23,14 +23,21 @@ package io.kamax.mxisd.http.undertow.handler.identity.v1;
import com.google.gson.JsonObject;
import io.kamax.mxisd.exception.BadRequestException;
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.undertow.server.HttpServerExchange;
import io.undertow.util.QueryParameterUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.Map;
public class SessionTpidBindHandler extends BasicHttpHandler {
public static final String Path = IsAPIv1.Base + "/3pid/bind";
@@ -47,12 +54,27 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
@Override
public void handleRequest(HttpServerExchange exchange) {
String sid = getQueryParameter(exchange, "sid");
String secret = getQueryParameter(exchange, "client_secret");
String mxid = getQueryParameter(exchange, "mxid");
BindRequest bindReq = new BindRequest();
bindReq.setSid(getQueryParameter(exchange, BindRequest.Keys.SessionID));
bindReq.setSecret(getQueryParameter(exchange, BindRequest.Keys.Secret));
bindReq.setUserId(getQueryParameter(exchange, BindRequest.Keys.UserID));
String reqContentType = getContentType(exchange).orElse("application/octet-stream");
if (StringUtils.equals("application/x-www-form-urlencoded", reqContentType)) {
String body = getBodyUtf8(exchange);
Map<String, Deque<String>> parms = QueryParameterUtils.parseQueryString(body, StandardCharsets.UTF_8.name());
bindReq.setSid(getQueryParameter(parms, BindRequest.Keys.SessionID));
bindReq.setSecret(getQueryParameter(parms, BindRequest.Keys.Secret));
bindReq.setUserId(getQueryParameter(parms, BindRequest.Keys.UserID));
} else if (StringUtils.equals("application/json", reqContentType)) {
bindReq = parseJsonTo(exchange, BindRequest.class);
} else {
log.warn("Unknown encoding in 3PID session bind: {}", reqContentType);
log.warn("The request will most likely fail");
}
try {
mgr.bind(sid, secret, mxid);
mgr.bind(bindReq.getSid(), bindReq.getSecret(), bindReq.getUserId());
respond(exchange, new JsonObject());
} catch (BadRequestException e) {
log.info("requested session was not validated");

View File

@@ -27,18 +27,16 @@ 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.ValidationResult;
import io.kamax.mxisd.util.FileUtil;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HttpString;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SessionValidateHandler extends BasicHttpHandler {
@@ -95,16 +93,13 @@ 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()) {
FileInputStream f = new FileInputStream(viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess());
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
String rawData = IOUtils.toString(f, StandardCharsets.UTF_8);
String data = rawData.replace("${remoteSessionLink}", url);
writeBodyAsUtf8(exchange, data);
} else {
FileInputStream f = new FileInputStream(viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess());
String data = IOUtils.toString(f, StandardCharsets.UTF_8);
writeBodyAsUtf8(exchange, data);
writeBodyAsUtf8(exchange, rawData);
}
} catch (IOException e) {
throw new RuntimeException(e);

View File

@@ -20,10 +20,16 @@
package io.kamax.mxisd.http.undertow.handler.identity.v1;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix.crypto.KeyManager;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.http.io.identity.StoreInviteRequest;
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
import io.kamax.mxisd.invitation.IThreePidInvite;
@@ -31,11 +37,13 @@ import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.invitation.InvitationManager;
import io.kamax.mxisd.invitation.ThreePidInvite;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.QueryParameterUtils;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class StoreInviteHandler extends BasicHttpHandler {
@@ -53,21 +61,39 @@ public class StoreInviteHandler extends BasicHttpHandler {
@Override
public void handleRequest(HttpServerExchange exchange) {
Map<String, String> parameters = new HashMap<>();
String reqContentType = getContentType(exchange).orElse("application/octet-stream");
JsonObject invJson = new JsonObject();
for (Map.Entry<String, Deque<String>> entry : exchange.getQueryParameters().entrySet()) {
if (Objects.nonNull(entry.getValue().peekFirst())) {
parameters.put(entry.getKey(), entry.getValue().peekFirst());
}
if (StringUtils.startsWith(reqContentType, "application/json")) {
invJson = parseJsonObject(exchange);
}
// TODO test with missing parameters to see behaviour
String sender = parameters.get("sender");
String medium = parameters.get("medium");
String address = parameters.get("address");
String roomId = parameters.get("room_id");
// Backward compatibility for pre-r0.1.0 implementations
else if (StringUtils.startsWith(reqContentType, "application/x-www-form-urlencoded")) {
String body = getBodyUtf8(exchange);
Map<String, Deque<String>> parms = QueryParameterUtils.parseQueryString(body, StandardCharsets.UTF_8.name());
for (Map.Entry<String, Deque<String>> entry : parms.entrySet()) {
if (entry.getValue().size() == 0) {
return;
}
IThreePidInvite invite = new ThreePidInvite(MatrixID.asAcceptable(sender), medium, address, roomId, parameters);
if (entry.getValue().size() > 1) {
throw new BadRequestException("key " + entry.getKey() + " has more than one value");
}
invJson.addProperty(entry.getKey(), entry.getValue().peekFirst());
}
} else {
throw new BadRequestException("Unsupported Content-Type: " + reqContentType);
}
Type parmType = new TypeToken<Map<String, String>>() {
}.getType();
Map<String, String> parameters = GsonUtil.get().fromJson(invJson, parmType);
StoreInviteRequest inv = GsonUtil.get().fromJson(invJson, StoreInviteRequest.class);
_MatrixID sender = MatrixID.asAcceptable(inv.getSender());
IThreePidInvite invite = new ThreePidInvite(sender, inv.getMedium(), inv.getAddress(), inv.getRoomId(), parameters);
IThreePidInviteReply reply = invMgr.storeInvite(invite);
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), cfg.getPublicUrl()));

View File

@@ -214,6 +214,10 @@ public class SessionMananger {
}
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);

View File

@@ -40,7 +40,6 @@ import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.time.Instant;
@@ -49,9 +48,9 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
public class OrmLiteSqliteStorage implements IStorage {
public class OrmLiteSqlStorage implements IStorage {
private transient final Logger log = LoggerFactory.getLogger(OrmLiteSqliteStorage.class);
private transient final Logger log = LoggerFactory.getLogger(OrmLiteSqlStorage.class);
@FunctionalInterface
private interface Getter<T> {
@@ -71,11 +70,11 @@ public class OrmLiteSqliteStorage implements IStorage {
private Dao<ThreePidSessionDao, String> sessionDao;
private Dao<ASTransactionDao, String> asTxnDao;
public OrmLiteSqliteStorage(MxisdConfig cfg) {
public OrmLiteSqlStorage(MxisdConfig cfg) {
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
}
public OrmLiteSqliteStorage(String backend, String path) {
public OrmLiteSqlStorage(String backend, String path) {
if (StringUtils.isBlank(backend)) {
throw new ConfigurationException("storage.backend");
}
@@ -85,13 +84,6 @@ public class OrmLiteSqliteStorage implements IStorage {
}
withCatcher(() -> {
if (path.startsWith("/") && !path.startsWith("//")) {
File parent = new File(path).getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new RuntimeException("Unable to create DB parent directory: " + parent);
}
}
ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + path);
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);

View File

@@ -27,16 +27,12 @@ import io.kamax.mxisd.config.threepid.medium.GenericTemplateConfig;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.io.IOUtils;
import io.kamax.mxisd.util.FileUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
public abstract class GenericTemplateNotificationGenerator extends PlaceholderNotificationGenerator implements NotificationGenerator {
@@ -51,14 +47,7 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
private String getTemplateContent(String location) {
try {
URI loc = URI.create(location);
InputStream is;
if (StringUtils.equals("classpath", loc.getScheme())) {
is = getClass().getResourceAsStream(loc.getSchemeSpecificPart());
} else {
is = new FileInputStream(location);
}
return IOUtils.toString(is, StandardCharsets.UTF_8);
return FileUtil.load(location);
} catch (IOException e) {
throw new InternalServerError("Unable to read template content at " + location + ": " + e.getMessage());
}

View File

@@ -30,6 +30,9 @@ import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.RoomName;
import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.SenderDisplayName;
public abstract class PlaceholderNotificationGenerator {
private MatrixConfig mxCfg;
@@ -51,9 +54,9 @@ public abstract class PlaceholderNotificationGenerator {
}
protected String populateForInvite(IMatrixIdInvite invite, String input) {
String senderName = invite.getProperties().getOrDefault("sender_display_name", "");
String senderName = invite.getProperties().getOrDefault(SenderDisplayName, "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getSender().getId());
String roomName = invite.getProperties().getOrDefault("room_name", "");
String roomName = invite.getProperties().getOrDefault(RoomName, "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getRoomId());
return populateForCommon(new ThreePid(invite.getMedium(), invite.getAddress()), input)
@@ -69,9 +72,9 @@ public abstract class PlaceholderNotificationGenerator {
protected String populateForReply(IThreePidInviteReply invite, String input) {
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
String senderName = invite.getInvite().getProperties().getOrDefault(SenderDisplayName, "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
String roomName = invite.getInvite().getProperties().getOrDefault(RoomName, "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
return populateForCommon(tpid, input)

View File

@@ -26,6 +26,7 @@ import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig;
import io.kamax.mxisd.threepid.generator.GenericTemplateNotificationGenerator;
import org.apache.commons.lang3.StringUtils;
public class GenericEmailNotificationGenerator extends GenericTemplateNotificationGenerator implements EmailGenerator {
@@ -46,8 +47,8 @@ public class GenericEmailNotificationGenerator extends GenericTemplateNotificati
@Override
protected String populateForCommon(ThreePid recipient, String body) {
body = super.populateForCommon(recipient, body);
body = body.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom());
body = body.replace("%FROM_NAME%", cfg.getIdentity().getName());
body = body.replace("%FROM_EMAIL%", StringUtils.defaultIfEmpty(cfg.getIdentity().getFrom(), ""));
body = body.replace("%FROM_NAME%", StringUtils.defaultIfEmpty(cfg.getIdentity().getName(), ""));
return body;
}

View File

@@ -63,9 +63,9 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
private void acceptEmail(String handler, Mxisd mxisd) {
if (StringUtils.equals(EmailRawNotificationHandler.ID, handler)) {
JsonObject emailCfgJson = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.Email.getId());
if (Objects.nonNull(emailCfgJson)) {
EmailConfig emailCfg = GsonUtil.get().fromJson(emailCfgJson, EmailConfig.class);
Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.Email.getId());
if (Objects.nonNull(o)) {
EmailConfig emailCfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), EmailConfig.class);
if (org.apache.commons.lang.StringUtils.isBlank(emailCfg.getGenerator())) {
throw new ConfigurationException("notification.email.generator");
@@ -105,9 +105,9 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
private void acceptPhone(String handler, Mxisd mxisd) {
if (StringUtils.equals(PhoneNotificationHandler.ID, handler)) {
JsonObject cfgJson = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.PhoneNumber.getId());
if (Objects.nonNull(cfgJson)) {
PhoneConfig cfg = GsonUtil.get().fromJson(cfgJson, PhoneConfig.class);
Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.PhoneNumber.getId());
if (Objects.nonNull(o)) {
PhoneConfig cfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), PhoneConfig.class);
List<PhoneGenerator> generators = StreamSupport
.stream(ServiceLoader.load(PhoneGeneratorSupplier.class).spliterator(), false)

View File

@@ -31,14 +31,12 @@ import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.notification.NotificationHandler;
import io.kamax.mxisd.threepid.generator.PlaceholderNotificationGenerator;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.io.IOUtils;
import io.kamax.mxisd.util.FileUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static com.sendgrid.SendGrid.Email;
import static com.sendgrid.SendGrid.Response;
@@ -78,7 +76,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
private String getFromFile(String path) {
try {
return IOUtils.toString(new FileInputStream(path), StandardCharsets.UTF_8);
return FileUtil.load(path);
} catch (IOException e) {
throw new RuntimeException("Couldn't create notification content using file " + path, e);
}

View File

@@ -0,0 +1,57 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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.util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class FileUtil {
public static String load(String loc) throws IOException {
URI uri = URI.create(loc);
InputStream is;
if (StringUtils.equals("classpath", uri.getScheme())) {
String resource = uri.getSchemeSpecificPart();
is = FileUtil.class.getResourceAsStream(resource);
if (Objects.isNull(is)) {
throw new FileNotFoundException("No classpath resource: " + resource);
}
} else {
is = new FileInputStream(loc);
}
try {
return IOUtils.toString(is, StandardCharsets.UTF_8);
} finally {
is.close();
}
}
}

View File

@@ -0,0 +1 @@
io.kamax.mxisd.backend.sql.BuiltInDriverLoader

View File

@@ -20,23 +20,23 @@
package io.kamax.mxisd.test.storage;
import io.kamax.mxisd.storage.ormlite.OrmLiteSqliteStorage;
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
import org.junit.Test;
import java.time.Instant;
public class OrmLiteSqliteStorageTest {
public class OrmLiteSqlStorageTest {
@Test
public void insertAsTxnDuplicate() {
OrmLiteSqliteStorage store = new OrmLiteSqliteStorage("sqlite", ":memory:");
OrmLiteSqlStorage store = new OrmLiteSqlStorage("sqlite", ":memory:");
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
store.insertTransactionResult("mxisd", "2", Instant.now(), "{}");
}
@Test(expected = RuntimeException.class)
public void insertAsTxnSame() {
OrmLiteSqliteStorage store = new OrmLiteSqliteStorage("sqlite", ":memory:");
OrmLiteSqlStorage store = new OrmLiteSqlStorage("sqlite", ":memory:");
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
}