Compare commits
18 Commits
v1.3.0-alp
...
v1.3.0-alp
Author | SHA1 | Date | |
---|---|---|---|
|
3e240fe34d | ||
|
635f6fdbe7 | ||
|
4237eeb3b6 | ||
|
a0e91e7896 | ||
|
aab0b86646 | ||
|
3e22301af7 | ||
|
2b202323c0 | ||
|
4ec05f518e | ||
|
6da68298b0 | ||
|
aecaafdeca | ||
|
d885932f45 | ||
|
c689a3f161 | ||
|
7805112548 | ||
|
3e89f0bc5e | ||
|
c6b8f7d48e | ||
|
83377ebee0 | ||
|
2aa6e4d142 | ||
|
82a1a3df68 |
@@ -8,6 +8,7 @@ mxisd - Federated Matrix Identity Server
|
||||
- [Getting Started](#getting-started)
|
||||
- [Support](#support)
|
||||
- [Contribute](#contribute)
|
||||
- [Powered by mxisd](#powered-by-mxisd)
|
||||
- [FAQ](#faq)
|
||||
- [Contact](#contact)
|
||||
|
||||
@@ -96,6 +97,11 @@ You can contribute as an organisation/corporation by:
|
||||
maintained regularly and you get direct access to the support team.
|
||||
- Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further.
|
||||
|
||||
# Powered by mxisd
|
||||
The following projects use mxisd under the hood for some or all their features. Check them out!
|
||||
- [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)
|
||||
- [matrix-register-bot](https://github.com/krombel/matrix-register-bot)
|
||||
|
||||
# FAQ
|
||||
See the [dedicated document](docs/faq.md)
|
||||
|
||||
|
@@ -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'
|
||||
|
@@ -1 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
theme: jekyll-theme-hacker
|
@@ -10,6 +10,15 @@ Following these quick start instructions, you will have a basic setup that can p
|
||||
talk to the central Matrix.org Identity server.
|
||||
This will be a good ground work for further integration with features and your existing Identity stores.
|
||||
|
||||
---
|
||||
|
||||
If you would like a more fully integrated setup out of the box, the [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)
|
||||
project provides a turn-key full-stack solution, including LDAP and the various mxisd features enabled and ready.
|
||||
We work closely with the project owner so the latest mxisd version is always supported.
|
||||
|
||||
If you choose to use it, this Getting Started guide is not applicable - See the project documentation. You may then
|
||||
directly go to the [Next steps](#next-steps).
|
||||
|
||||
## Preparation
|
||||
You will need:
|
||||
- Working Homeserver, ideally with working federation
|
||||
|
@@ -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": {}
|
||||
}
|
||||
```
|
||||
|
@@ -36,4 +36,10 @@ notification:
|
||||
body:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the HTML part of the email. Do not set to not use one>
|
||||
unbind:
|
||||
fraudulent:
|
||||
subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds>
|
||||
body:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
```
|
||||
|
@@ -20,7 +20,9 @@ threepid:
|
||||
session:
|
||||
validation:
|
||||
local: '/path/to/validate-local-template.eml'
|
||||
remote: 'path/to/validate-remote-template.eml'
|
||||
remote: '/path/to/validate-remote-template.eml'
|
||||
unbind:
|
||||
frandulent: '/path/to/unbind-fraudulent-template.eml'
|
||||
generic:
|
||||
matrixId: '/path/to/mxid-invite-template.eml'
|
||||
```
|
||||
|
@@ -149,6 +149,9 @@ session:
|
||||
toRemote:
|
||||
enabled: true
|
||||
server: 'configExample' # Not to be included in config! Already present in default config!
|
||||
unbind:
|
||||
fraudulent:
|
||||
sendWarning: true
|
||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
||||
# CONFIGURATION EXAMPLE
|
||||
```
|
||||
@@ -168,6 +171,14 @@ Each scope is divided into three parts:
|
||||
If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID
|
||||
locally validated.
|
||||
|
||||
---
|
||||
|
||||
`unbind.fraudulent` controls warning notifications if an illegal/fraudulent 3PID removal is attempted on the Identity server.
|
||||
This is directly related to synapse disregard for privacy and new GDPR laws in Europe in an attempt to inform users about
|
||||
potential privacy leaks.
|
||||
|
||||
For more information, see the corresponding [synapse issue](https://github.com/matrix-org/synapse/issues/4540).
|
||||
|
||||
### Web views
|
||||
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
||||
If the session or token is invalid, an error page is displayed.
|
||||
|
@@ -85,6 +85,7 @@ public class HttpMxisd {
|
||||
.post(SessionValidateHandler.Path, sessValidateHandler)
|
||||
.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())))
|
||||
|
||||
|
@@ -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());
|
||||
@@ -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, httpClient);
|
||||
sessMgr = new SessionMananger(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());
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
26
src/main/java/io/kamax/mxisd/backend/sql/DriverLoader.java
Normal file
26
src/main/java/io/kamax/mxisd/backend/sql/DriverLoader.java
Normal 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> {
|
||||
}
|
38
src/main/java/io/kamax/mxisd/backend/sql/Drivers.java
Normal file
38
src/main/java/io/kamax/mxisd/backend/sql/Drivers.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
@@ -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 {
|
||||
|
@@ -125,6 +125,34 @@ public class SessionConfig {
|
||||
|
||||
}
|
||||
|
||||
public static class PolicyUnbind {
|
||||
|
||||
public static class PolicyUnbindFraudulent {
|
||||
|
||||
private boolean sendWarning = true;
|
||||
|
||||
public boolean getSendWarning() {
|
||||
return sendWarning;
|
||||
}
|
||||
|
||||
public void setSendWarning(boolean sendWarning) {
|
||||
this.sendWarning = sendWarning;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private PolicyUnbindFraudulent fraudulent = new PolicyUnbindFraudulent();
|
||||
|
||||
public PolicyUnbindFraudulent getFraudulent() {
|
||||
return fraudulent;
|
||||
}
|
||||
|
||||
public void setFraudulent(PolicyUnbindFraudulent fraudulent) {
|
||||
this.fraudulent = fraudulent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Policy() {
|
||||
validation.enabled = true;
|
||||
validation.forLocal.enabled = true;
|
||||
@@ -139,6 +167,7 @@ public class SessionConfig {
|
||||
}
|
||||
|
||||
private PolicyTemplate validation = new PolicyTemplate();
|
||||
private PolicyUnbind unbind = new PolicyUnbind();
|
||||
|
||||
public PolicyTemplate getValidation() {
|
||||
return validation;
|
||||
@@ -148,6 +177,14 @@ public class SessionConfig {
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public PolicyUnbind getUnbind() {
|
||||
return unbind;
|
||||
}
|
||||
|
||||
public void setUnbind(PolicyUnbind unbind) {
|
||||
this.unbind = unbind;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Policy policy = new Policy();
|
||||
|
@@ -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() {
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -115,7 +115,7 @@ public class EmailSendGridConfig {
|
||||
|
||||
public static class Templates {
|
||||
|
||||
public static class TemplateSession {
|
||||
public static class TemplateSessionValidation {
|
||||
|
||||
private EmailTemplate local = new EmailTemplate();
|
||||
private EmailTemplate remote = new EmailTemplate();
|
||||
@@ -137,6 +137,43 @@ public class EmailSendGridConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public static class TemplateSessionUnbind {
|
||||
|
||||
private EmailTemplate fraudulent = new EmailTemplate();
|
||||
|
||||
public EmailTemplate getFraudulent() {
|
||||
return fraudulent;
|
||||
}
|
||||
|
||||
public void setFraudulent(EmailTemplate fraudulent) {
|
||||
this.fraudulent = fraudulent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TemplateSession {
|
||||
|
||||
private TemplateSessionValidation validation = new TemplateSessionValidation();
|
||||
private TemplateSessionUnbind unbind = new TemplateSessionUnbind();
|
||||
|
||||
public TemplateSessionValidation getValidation() {
|
||||
return validation;
|
||||
}
|
||||
|
||||
public void setValidation(TemplateSessionValidation validation) {
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public TemplateSessionUnbind getUnbind() {
|
||||
return unbind;
|
||||
}
|
||||
|
||||
public void setUnbind(TemplateSessionUnbind unbind) {
|
||||
this.unbind = unbind;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private EmailTemplate invite = new EmailTemplate();
|
||||
private TemplateSession session = new TemplateSession();
|
||||
private Map<String, EmailTemplate> generic = new HashMap<>();
|
||||
|
@@ -32,6 +32,7 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
|
||||
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
|
||||
getSession().getValidation().setLocal("classpath:/threepids/email/validate-local-template.eml");
|
||||
getSession().getValidation().setRemote("classpath:/threepids/email/validate-remote-template.eml");
|
||||
getSession().getUnbind().setFraudulent("classpath:/threepids/email/unbind-fraudulent.eml");
|
||||
}
|
||||
|
||||
public EmailTemplateConfig build() {
|
||||
|
@@ -62,7 +62,22 @@ public class GenericTemplateConfig {
|
||||
|
||||
}
|
||||
|
||||
public static class SessionUnbind {
|
||||
|
||||
private String fraudulent;
|
||||
|
||||
public String getFraudulent() {
|
||||
return fraudulent;
|
||||
}
|
||||
|
||||
public void setFraudulent(String fraudulent) {
|
||||
this.fraudulent = fraudulent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SessionValidation validation = new SessionValidation();
|
||||
private SessionUnbind unbind = new SessionUnbind();
|
||||
|
||||
public SessionValidation getValidation() {
|
||||
return validation;
|
||||
@@ -72,6 +87,14 @@ public class GenericTemplateConfig {
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public SessionUnbind getUnbind() {
|
||||
return unbind;
|
||||
}
|
||||
|
||||
public void setUnbind(SessionUnbind unbind) {
|
||||
this.unbind = unbind;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String invite;
|
||||
|
@@ -32,6 +32,7 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
|
||||
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
|
||||
getSession().getValidation().setLocal("classpath:/threepids/sms/validate-local-template.txt");
|
||||
getSession().getValidation().setRemote("classpath:/threepids/sms/validate-remote-template.txt");
|
||||
getSession().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt");
|
||||
}
|
||||
|
||||
public PhoneSmsTemplateConfig build() {
|
||||
|
@@ -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) {
|
||||
|
@@ -25,7 +25,7 @@ import org.apache.http.HttpStatus;
|
||||
public class SessionNotValidatedException extends HttpMatrixException {
|
||||
|
||||
public SessionNotValidatedException() {
|
||||
super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
|
||||
super(HttpStatus.SC_BAD_REQUEST, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,8 @@
|
||||
|
||||
package io.kamax.mxisd.exception;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
public class SessionUnknownException extends HttpMatrixException {
|
||||
|
||||
public SessionUnknownException() {
|
||||
@@ -27,7 +29,7 @@ public class SessionUnknownException extends HttpMatrixException {
|
||||
}
|
||||
|
||||
public SessionUnknownException(String error) {
|
||||
super(200, "M_NO_VALID_SESSION", error);
|
||||
super(HttpStatus.SC_NOT_FOUND, "M_NO_VALID_SESSION", error);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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,25 +73,32 @@ 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) {
|
||||
try {
|
||||
JsonObject base = GsonUtil.parseObj(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8));
|
||||
return GsonUtil.getObj(base, key);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return GsonUtil.getObj(parseJsonObject(exchange), key);
|
||||
}
|
||||
|
||||
protected JsonObject parseJsonObject(HttpServerExchange exchange) {
|
||||
return GsonUtil.parseObj(getBodyUtf8(exchange));
|
||||
}
|
||||
|
||||
protected void respond(HttpServerExchange ex, int statusCode, JsonElement bodyJson) {
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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");
|
||||
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
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 SessionMananger sessMgr;
|
||||
|
||||
public SessionTpidUnbindHandler(SessionMananger sessMgr) {
|
||||
this.sessMgr = sessMgr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
JsonObject body = parseJsonObject(exchange);
|
||||
sessMgr.unbind(body);
|
||||
writeBodyAsUtf8(exchange, "{}");
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
@@ -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()));
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.notification;
|
||||
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
@@ -38,4 +39,6 @@ public interface NotificationHandler {
|
||||
|
||||
void sendForRemoteValidation(IThreePidSession session);
|
||||
|
||||
void sendForFraudulentUnbind(ThreePid tpid);
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.notification;
|
||||
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||
import io.kamax.mxisd.config.threepid.notification.NotificationConfig;
|
||||
import io.kamax.mxisd.exception.NotImplementedException;
|
||||
@@ -81,4 +82,8 @@ public class NotificationManager {
|
||||
ensureMedium(session.getThreePid().getMedium()).sendForRemoteValidation(session);
|
||||
}
|
||||
|
||||
public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException {
|
||||
ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -28,12 +28,15 @@ 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;
|
||||
@@ -71,6 +74,7 @@ public class SessionMananger {
|
||||
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
|
||||
@@ -78,11 +82,19 @@ public class SessionMananger {
|
||||
// FIXME export into central class, set version
|
||||
private CloseableHttpClient client;
|
||||
|
||||
public SessionMananger(SessionConfig cfg, MatrixConfig mxCfg, IStorage storage, NotificationManager notifMgr, 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;
|
||||
}
|
||||
|
||||
@@ -214,6 +226,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);
|
||||
|
||||
@@ -255,6 +271,54 @@ public class SessionMananger {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@@ -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);
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.threepid.generator;
|
||||
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
@@ -27,16 +28,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 +48,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());
|
||||
}
|
||||
@@ -93,4 +83,10 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
|
||||
return populateForRemoteValidation(session, getTemplateContent(cfg.getSession().getValidation().getRemote()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getForFraudulentUnbind(ThreePid tpid) {
|
||||
log.info("Generating notification content for fraudulent unbind");
|
||||
return populateForFraudulentUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getFraudulent()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.threepid.generator;
|
||||
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
@@ -38,4 +39,6 @@ public interface NotificationGenerator {
|
||||
|
||||
String getForRemoteValidation(IThreePidSession session);
|
||||
|
||||
String getForFraudulentUnbind(ThreePid tpid);
|
||||
|
||||
}
|
||||
|
@@ -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)
|
||||
@@ -103,4 +106,8 @@ public abstract class PlaceholderNotificationGenerator {
|
||||
return populateForValidation(session, input);
|
||||
}
|
||||
|
||||
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
|
||||
return populateForCommon(tpid, input);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.threepid.notification;
|
||||
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
@@ -76,4 +77,9 @@ public abstract class GenericNotificationHandler<A extends ThreePidConnector, B
|
||||
send(connector, session.getThreePid().getAddress(), generator.getForRemoteValidation(session));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendForFraudulentUnbind(ThreePid tpid) {
|
||||
send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ package io.kamax.mxisd.threepid.notification.email;
|
||||
|
||||
import com.sendgrid.SendGrid;
|
||||
import com.sendgrid.SendGridException;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
@@ -31,14 +32,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 +77,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);
|
||||
}
|
||||
@@ -109,7 +108,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
||||
|
||||
@Override
|
||||
public void sendForValidation(IThreePidSession session) {
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getValidation().getLocal();
|
||||
Email email = getEmail();
|
||||
email.setSubject(populateForValidation(session, template.getSubject()));
|
||||
email.setText(populateForValidation(session, getFromFile(template.getBody().getText())));
|
||||
@@ -120,7 +119,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
||||
|
||||
@Override
|
||||
public void sendForRemoteValidation(IThreePidSession session) {
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getValidation().getRemote();
|
||||
Email email = getEmail();
|
||||
email.setSubject(populateForRemoteValidation(session, template.getSubject()));
|
||||
email.setText(populateForRemoteValidation(session, getFromFile(template.getBody().getText())));
|
||||
@@ -129,6 +128,17 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
||||
send(session.getThreePid().getAddress(), email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendForFraudulentUnbind(ThreePid tpid) {
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();
|
||||
Email email = getEmail();
|
||||
email.setSubject(populateForCommon(tpid, template.getSubject()));
|
||||
email.setText(populateForCommon(tpid, getFromFile(template.getBody().getText())));
|
||||
email.setHtml(populateForCommon(tpid, getFromFile(template.getBody().getHtml())));
|
||||
|
||||
send(tpid.getAddress(), email);
|
||||
}
|
||||
|
||||
private void send(String recipient, Email email) {
|
||||
if (StringUtils.isBlank(cfg.getIdentity().getFrom())) {
|
||||
throw new FeatureNotAvailable("3PID Email identity: sender address is empty - " +
|
||||
|
57
src/main/java/io/kamax/mxisd/util/FileUtil.java
Normal file
57
src/main/java/io/kamax/mxisd/util/FileUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1 @@
|
||||
io.kamax.mxisd.backend.sql.BuiltInDriverLoader
|
135
src/main/resources/threepids/email/unbind-fraudulent.eml
Normal file
135
src/main/resources/threepids/email/unbind-fraudulent.eml
Normal file
@@ -0,0 +1,135 @@
|
||||
Subject: IMPORTANT - %DOMAIN% Matrix Identity Server - Unauthorized 3PID unbind blocked
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
|
||||
|
||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Disposition: inline
|
||||
|
||||
Hi,
|
||||
|
||||
**THIS IS IMPORTANT, PLEASE READ CAREFULLY**.
|
||||
If you are the system administrator of the Matrix installation, read the second section.
|
||||
|
||||
This is a notification email that a possibly unauthorized entity has attempted to alter your
|
||||
3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.
|
||||
|
||||
This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.
|
||||
|
||||
If you do not understand this email, please forward it to your System administrator.
|
||||
|
||||
-----------
|
||||
|
||||
As the system administrator:
|
||||
|
||||
If you are using synapse as a Homeserver, this is a known issue related to MSC1194 [1] and abuse of separation of concerns.
|
||||
As a privacy-centric product and to protect your privacy, the request was actively blocked. We have written a more detailed
|
||||
explanation on our Privacy wiki page [2] (Direct link [3]) so you can fully grasp the impact for you and your users.
|
||||
|
||||
We have open an issue [4] on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would
|
||||
appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.
|
||||
|
||||
If you are using another Homeserver or this came following no action from your own users, then you have been the target
|
||||
of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of
|
||||
the attack and take relevant actions following your policy.
|
||||
|
||||
If you would like to disable these notifications, please see the 3PID sessions configuration documentation [5].
|
||||
|
||||
Thanks,
|
||||
|
||||
%DOMAIN_PRETTY% Admins
|
||||
|
||||
---
|
||||
|
||||
[1] https://github.com/matrix-org/matrix-doc/issues/1194
|
||||
[2] https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy
|
||||
[3] https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#msc1194-synapse-and-impacts-on-your-privacy
|
||||
[4] https://github.com/matrix-org/synapse/issues/4540
|
||||
[5] https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#configuration
|
||||
|
||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||
Content-Type: multipart/related;
|
||||
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
|
||||
type="text/html"
|
||||
|
||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Disposition: inline
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#page {
|
||||
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
||||
font-color: #454545;
|
||||
font-size: 12pt;
|
||||
width: 100%%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#inner {
|
||||
width: 640px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table id="page">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td id="inner">
|
||||
<p>Hi,</p>
|
||||
|
||||
<p><b>THIS IS IMPORTANT, PLEASE READ CAREFULLY</b>.<br/>
|
||||
If you are the system administrator of the Matrix installation, read the second section.</p>
|
||||
|
||||
<p>This is a notification email that a possibly unauthorized entity has attempted to alter your
|
||||
3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.</p>
|
||||
|
||||
<p>This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.</p>
|
||||
|
||||
<p>If you do not understand this email, please forward it to your System administrator.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>As the system administrator:</p>
|
||||
|
||||
<p>If you are using synapse as a Homeserver, this is a known issue related to <a href="https://github.com/matrix-org/matrix-doc/issues/1194">MSC1194</a>
|
||||
and abuse of separation of concerns. As a privacy-centric product and to protect your privacy, the request was actively
|
||||
blocked. We have written a more detailed explanation on our <a href="https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy">Privacy wiki page</a>
|
||||
(<a href="https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#msc1194-synapse-and-impacts-on-your-privacy">Direct link to section</a>)
|
||||
so you can fully grasp the impact for you and your users.</p>
|
||||
|
||||
<p>We have open an issue on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would
|
||||
appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.<br/>
|
||||
Issue: <a href="https://github.com/matrix-org/synapse/issues/4540">https://github.com/matrix-org/synapse/issues/4540</a></p>
|
||||
|
||||
<p>If you are using another Homeserver or this came following no action from your own users, then you have been the target
|
||||
of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of
|
||||
the attack and take relevant actions following your policy.</p>
|
||||
|
||||
<p>If you would like to disable these notifications, please see the
|
||||
<a href="https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#configuration">3PID sessions configuration documentation.</a></p>
|
||||
|
||||
<p>Thanks,</p>
|
||||
|
||||
<p>%DOMAIN_PRETTY% Admins</p>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
|
||||
|
||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
|
1
src/main/resources/threepids/sms/unbind-fraudulent.txt
Normal file
1
src/main/resources/threepids/sms/unbind-fraudulent.txt
Normal file
@@ -0,0 +1 @@
|
||||
INFORMATIONAL ONLY - Someone attempted to change your Matrix 3PIDs, with a potential data leak. Please contact your system administrator.
|
@@ -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(), "{}");
|
||||
}
|
Reference in New Issue
Block a user