Further work

This commit is contained in:
Maxime Dor
2017-09-20 17:22:51 +02:00
parent 0b087ee08c
commit bf2afd8739
22 changed files with 623 additions and 206 deletions

View File

@@ -0,0 +1,35 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.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.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("session")
public class SessionConfig {
private static Logger log = LoggerFactory.getLogger(SessionConfig.class);
}

View File

@@ -1,67 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.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.config.invite.medium;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.File;
@Configuration
@ConfigurationProperties("invite.medium.email")
public class EmailInviteConfig {
private Logger log = LoggerFactory.getLogger(EmailInviteConfig.class);
private String template;
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
@PostConstruct
public void build() {
log.info("--- E-mail invites config ---");
if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
if (StringUtils.isBlank(getTemplate())) {
log.warn("invite.medium.email is empty! Will not send invites");
} else {
File cp = new File(getTemplate()).getAbsoluteFile();
log.info("Template: {}", cp.getAbsolutePath());
if (!cp.exists() || !cp.isFile() || !cp.canRead()) {
log.warn(getTemplate() + " does not exist, is not a file or cannot be read");
}
}
} else {
log.info("Template: Built-in: {}", getTemplate());
}
}
}

View File

@@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@Configuration @Configuration
@ConfigurationProperties(prefix = "threepid.email.connector.provider.smtp") @ConfigurationProperties(prefix = "threepid.medium.email.connectors.smtp")
public class EmailSmtpConfig { public class EmailSmtpConfig {
private Logger log = LoggerFactory.getLogger(EmailSmtpConfig.class); private Logger log = LoggerFactory.getLogger(EmailSmtpConfig.class);

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.config.threepid.medium; package io.kamax.mxisd.config.threepid.medium;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -35,43 +36,81 @@ import javax.annotation.PostConstruct;
@ConfigurationProperties("threepid.medium.email") @ConfigurationProperties("threepid.medium.email")
public class EmailConfig { public class EmailConfig {
public static class Identity {
private String from;
private String name;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private String generator;
private String connector;
private Logger log = LoggerFactory.getLogger(EmailConfig.class); private Logger log = LoggerFactory.getLogger(EmailConfig.class);
private MatrixConfig mxCfg; private MatrixConfig mxCfg;
private Identity identity = new Identity();
private String from;
private String name;
@Autowired @Autowired
public EmailConfig(MatrixConfig mxCfg) { public EmailConfig(MatrixConfig mxCfg) {
this.mxCfg = mxCfg; this.mxCfg = mxCfg;
} }
public String getFrom() { public Identity getIdentity() {
return from; return identity;
} }
public void setFrom(String from) { public String getGenerator() {
this.from = from; return generator;
} }
public String getName() { public void setGenerator(String generator) {
return name; this.generator = generator;
} }
public void setName(String name) { public String getConnector() {
this.name = name; return connector;
}
public void setConnector(String connector) {
this.connector = connector;
} }
@PostConstruct @PostConstruct
public void build() { public void build() {
log.info("--- E-mail config ---"); log.info("--- E-mail config ---");
log.info("From: {}", getFrom());
if (StringUtils.isBlank(getName())) { if (StringUtils.isBlank(getGenerator())) {
setName(WordUtils.capitalize(mxCfg.getDomain()) + " Identity Server"); throw new ConfigurationException("generator");
} }
log.info("Name: {}", getName());
if (StringUtils.isBlank(getConnector())) {
throw new ConfigurationException("connector");
}
log.info("From: {}", identity.getFrom());
if (StringUtils.isBlank(identity.getName())) {
identity.setName(WordUtils.capitalize(mxCfg.getDomain()) + " Identity Server");
}
log.info("Name: {}", identity.getName());
log.info("Generator: {}", getGenerator());
log.info("Connector: {}", getConnector());
} }
} }

View File

@@ -0,0 +1,82 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.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.config.threepid.medium;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("threepid.medium.email.generators.template")
public class EmailTemplateConfig {
private static Logger log = LoggerFactory.getLogger(EmailTemplateConfig.class);
private static final String classpathPrefix = "classpath:";
private static String getName(String path) {
if (StringUtils.startsWith(path, classpathPrefix)) {
return "Built-in (" + path.substring(classpathPrefix.length()) + ")";
}
return path;
}
public static class Session {
private String validation;
public String getValidation() {
return validation;
}
public void setValidation(String validation) {
this.validation = validation;
}
}
private String invite;
private Session session = new Session();
public String getInvite() {
return invite;
}
public void setInvite(String invite) {
this.invite = invite;
}
public Session getSession() {
return session;
}
@PostConstruct
public void build() {
log.info("--- E-mail Generator templates config ---");
log.info("Invite: {}", getName(getInvite()));
log.info("Session validation: {}", getName(getSession().getValidation()));
}
}

View File

@@ -29,7 +29,7 @@ import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
import io.kamax.mxisd.exception.BadRequestException import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.invitation.InvitationManager import io.kamax.mxisd.invitation.InvitationManager
import io.kamax.mxisd.lookup.ThreePidValidation import io.kamax.mxisd.lookup.ThreePidValidation
import io.kamax.mxisd.mapping.MappingManager import io.kamax.mxisd.session.SessionMananger
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.apache.http.HttpStatus import org.apache.http.HttpStatus
import org.slf4j.Logger import org.slf4j.Logger
@@ -48,7 +48,7 @@ import java.nio.charset.StandardCharsets
class SessionController { class SessionController {
@Autowired @Autowired
private MappingManager mgr private SessionMananger mgr
@Autowired @Autowired
private InvitationManager invMgr; private InvitationManager invMgr;

View File

@@ -24,5 +24,10 @@ import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.ResponseStatus
@ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED) @ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED)
class NotImplementedException extends RuntimeException { public class NotImplementedException extends RuntimeException {
public NotImplementedException(String s) {
super(s);
}
} }

View File

@@ -26,14 +26,13 @@ import io.kamax.mxisd.config.DnsOverwrite;
import io.kamax.mxisd.config.DnsOverwriteEntry; import io.kamax.mxisd.config.DnsOverwriteEntry;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException; import io.kamax.mxisd.exception.MappingAlreadyExistsException;
import io.kamax.mxisd.invitation.generator.IInviteContentGenerator;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.signature.SignatureManager; import io.kamax.mxisd.signature.SignatureManager;
import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO; import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
import io.kamax.mxisd.threepid.connector.IThreePidConnector;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -84,35 +83,15 @@ public class InvitationManager {
@Autowired @Autowired
private DnsOverwrite dns; private DnsOverwrite dns;
private Map<String, IInviteContentGenerator> generators; private NotificationManager notifMgr;
private Map<String, IThreePidConnector> connectors;
private CloseableHttpClient client; private CloseableHttpClient client;
private Gson gson; private Gson gson;
private Timer refreshTimer; private Timer refreshTimer;
@Autowired @Autowired
public InvitationManager( public InvitationManager(NotificationManager notifMgr) {
List<IInviteContentGenerator> generatorList, this.notifMgr = notifMgr;
List<IThreePidConnector> connectorList
) {
generators = new HashMap<>();
generatorList.forEach(sender -> { // FIXME to support several possible implementations
if (generators.containsKey(sender.getMedium())) {
throw new RuntimeException("More than one " + sender.getMedium() + " content generator");
}
generators.put(sender.getMedium(), sender);
});
connectors = new HashMap<>();
connectorList.forEach(connector -> { // FIXME to support several possible implementations
if (connectors.containsKey(connector.getMedium())) {
throw new RuntimeException("More than one " + connector.getMedium() + " connector");
}
connectors.put(connector.getMedium(), connector);
});
} }
@PostConstruct @PostConstruct
@@ -221,9 +200,7 @@ public class InvitationManager {
} }
public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync
IInviteContentGenerator generator = generators.get(invitation.getMedium()); if (!notifMgr.isMediumSupported(invitation.getMedium())) {
IThreePidConnector connector = connectors.get(invitation.getMedium());
if (generator == null || connector == null) {
throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported"); throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported");
} }
@@ -246,7 +223,7 @@ public class InvitationManager {
IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName); IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName);
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress()); log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
connector.send(reply, generator.generate(reply)); notifMgr.send(reply);
log.info("Storing invite under ID {}", invId); log.info("Storing invite under ID {}", invId);
storage.insertInvite(reply); storage.insertInvite(reply);

View File

@@ -18,14 +18,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.invitation.generator; package io.kamax.mxisd.notification;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
public interface IInviteContentGenerator { public interface INotificationHandler {
String getMedium(); String getMedium();
String generate(IThreePidInviteReply invite); void notify(IThreePidInviteReply invite);
void notify(IThreePidSession session);
} }

View File

@@ -0,0 +1,69 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.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.notification;
import io.kamax.mxisd.exception.NotImplementedException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class NotificationManager {
private Map<String, INotificationHandler> handlers;
@Autowired
public NotificationManager(List<INotificationHandler> handlers) {
this.handlers = new HashMap<>();
handlers.forEach(h -> this.handlers.put(h.getMedium(), h));
}
private INotificationHandler ensureMedium(String medium) {
INotificationHandler handler = handlers.get(medium);
if (handler == null) {
throw new NotImplementedException(medium + " is not a supported 3PID medium type");
}
return handler;
}
public boolean isMediumSupported(String medium) {
return handlers.containsKey(medium);
}
public void sendForInvite(IThreePidInviteReply invite) {
ensureMedium(invite.getInvite().getMedium()).notify(invite);
}
public void sendForValidation(IThreePidSession session) {
ensureMedium(session.getThreePid().getMedium()).notify(session);
}
public void sendforRemotePublish(IThreePidSession session) {
throw new NotImplementedException("Remote publish of 3PID bind");
}
}

View File

@@ -18,14 +18,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.mapping; package io.kamax.mxisd.session;
import io.kamax.matrix.ThreePidMedium; import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.SessionConfig;
import io.kamax.mxisd.exception.InvalidCredentialsException; import io.kamax.mxisd.exception.InvalidCredentialsException;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.ThreePidValidation; import io.kamax.mxisd.lookup.ThreePidValidation;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.dao.IThreePidSessionDao; import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
import io.kamax.mxisd.threepid.session.ThreePidSession; import io.kamax.mxisd.threepid.session.ThreePidSession;
@@ -40,16 +42,21 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@Component @Component
public class MappingManager { public class SessionMananger {
private Logger log = LoggerFactory.getLogger(MappingManager.class); private Logger log = LoggerFactory.getLogger(SessionMananger.class);
private SessionConfig cfg;
private IStorage storage; private IStorage storage;
private LookupStrategy lookup; private LookupStrategy lookup;
private NotificationManager notifMgr;
@Autowired @Autowired
public MappingManager(IStorage storage, LookupStrategy lookup) { public SessionMananger(SessionConfig cfg, IStorage storage, LookupStrategy lookup, NotificationManager notifMgr) {
this.cfg = cfg;
this.storage = storage; this.storage = storage;
this.lookup = lookup;
this.notifMgr = notifMgr;
} }
private ThreePidSession getSession(String sid, String secret) { private ThreePidSession getSession(String sid, String secret) {
@@ -78,7 +85,8 @@ public class MappingManager {
log.info("We already have a session for {}: {}", tpid, session.getId()); log.info("We already have a session for {}: {}", tpid, session.getId());
if (session.getAttempt() < attempt) { if (session.getAttempt() < attempt) {
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt()); log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
// TODO send via connector notifMgr.sendForValidation(session);
log.info("Sent validation notification to {}", tpid);
session.increaseAttempt(); session.increaseAttempt();
storage.updateThreePidSession(session.getDao()); storage.updateThreePidSession(session.getDao());
} }
@@ -95,8 +103,8 @@ public class MappingManager {
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token); ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server); log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
// TODO send via connector notifMgr.sendForValidation(session);
// log.info("Sent validation notification to {}", tpid); log.info("Sent validation notification to {}", tpid);
storage.insertThreePidSession(session.getDao()); storage.insertThreePidSession(session.getDao());
log.info("Stored session {}", sessionId, tpid, server); log.info("Stored session {}", sessionId, tpid, server);
@@ -124,13 +132,7 @@ public class MappingManager {
ThreePidSession session = getSessionIfValidated(sid, secret); ThreePidSession session = getSessionIfValidated(sid, secret);
log.info("Attempting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer()); log.info("Attempting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer());
// We lookup if the 3PID is already known locally. // We lookup if the 3PID is already known remotely.
// If it is, we do not need to process any further as it is already bound.
Optional<SingleLookupReply> rLocal = lookup.findLocal(session.getThreePid().getMedium(), session.getThreePid().getAddress());
boolean knownLocal = rLocal.isPresent() && StringUtils.equals(rLocal.get().getMxid().getId(), mxid);
log.info("Mapping {} -> {} is " + (knownLocal ? "already" : "not") + " known locally", mxid, session.getThreePid());
// MXID is not known locally, checking remotely
Optional<SingleLookupReply> rRemote = lookup.findRemote(session.getThreePid().getMedium(), session.getThreePid().getAddress()); Optional<SingleLookupReply> rRemote = lookup.findRemote(session.getThreePid().getMedium(), session.getThreePid().getAddress());
boolean knownRemote = rRemote.isPresent() && StringUtils.equals(rRemote.get().getMxid().getId(), mxid); boolean knownRemote = rRemote.isPresent() && StringUtils.equals(rRemote.get().getMxid().getId(), mxid);
log.info("Mapping {} -> {} is " + (knownRemote ? "already" : "not") + " known remotely", mxid, session.getThreePid()); log.info("Mapping {} -> {} is " + (knownRemote ? "already" : "not") + " known remotely", mxid, session.getThreePid());
@@ -143,25 +145,28 @@ public class MappingManager {
isLocalDomain = session.getThreePid().getAddress().isEmpty(); // FIXME only for testing isLocalDomain = session.getThreePid().getAddress().isEmpty(); // FIXME only for testing
} }
if (knownRemote) { if (knownRemote) {
if (isLocalDomain && !knownLocal) {
log.warn("Mapping {} -> {} is not known locally but is about a local domain!");
}
log.info("No further action needed for Mapping {} -> {}"); log.info("No further action needed for Mapping {} -> {}");
return; return;
} }
// We lookup if the 3PID is already known locally.
Optional<SingleLookupReply> rLocal = lookup.findLocal(session.getThreePid().getMedium(), session.getThreePid().getAddress());
boolean knownLocal = rLocal.isPresent() && StringUtils.equals(rLocal.get().getMxid().getId(), mxid);
log.info("Mapping {} -> {} is " + (knownLocal ? "already" : "not") + " known locally", mxid, session.getThreePid());
// This might need a configuration by medium type? // This might need a configuration by medium type?
if (knownLocal) { // 3PID is ony known local if (knownLocal) { // 3PID is ony known local
if (isLocalDomain) { if (isLocalDomain) {
// TODO // TODO
// 1. Check if global publishing is enabled, allowed and offered. If one is no, return. // 1. Check if global publishing is enabled, allowed and offered. If one is no, return.
// 2. Publish globally // 2. Publish globally
notifMgr.sendforRemotePublish(session);
} }
if (System.currentTimeMillis() % 2 == 0) { if (System.currentTimeMillis() % 2 == 0) { // FIXME only for testing
// TODO // TODO
// 1. Check if configured to publish globally non-local domain. If no, return // 1. Check if configured to publish globally non-local domain. If no, return
notifMgr.sendforRemotePublish(session);
} }
// TODO // TODO
@@ -172,6 +177,7 @@ public class MappingManager {
// 2. call mxisd-specific endpoint to publish globally // 2. call mxisd-specific endpoint to publish globally
// 3. check regularly on client page for a binding // 3. check regularly on client page for a binding
// 4. when found, show page "Done globally!" // 4. when found, show page "Done globally!"
notifMgr.sendforRemotePublish(session);
} else { } else {
if (isLocalDomain) { // 3PID is not known anywhere but is a local domain if (isLocalDomain) { // 3PID is not known anywhere but is a local domain
// TODO // TODO
@@ -180,6 +186,7 @@ public class MappingManager {
} else { // 3PID is not known anywhere and is remote } else { // 3PID is not known anywhere and is remote
// TODO // TODO
// Proxy to configurable IS, by default Matrix.org // Proxy to configurable IS, by default Matrix.org
notifMgr.sendforRemotePublish(session);
} }
} }
} }

View File

@@ -20,12 +20,10 @@
package io.kamax.mxisd.threepid.connector; package io.kamax.mxisd.threepid.connector;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
public interface IThreePidConnector { public interface IThreePidConnector {
String getId();
String getMedium(); String getMedium();
void send(IThreePidInviteReply invite, String content);
} }

View File

@@ -18,14 +18,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.threepid.connector; package io.kamax.mxisd.threepid.connector.email;
import com.sun.mail.smtp.SMTPTransport; import com.sun.mail.smtp.SMTPTransport;
import io.kamax.matrix.ThreePidMedium; import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig; import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -42,26 +39,22 @@ import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
@Component @Component
public class EmailSmtpConnector implements IThreePidConnector { public class EmailSmtpConnector implements IEmailConnector {
private Logger log = LoggerFactory.getLogger(EmailSmtpConnector.class); private Logger log = LoggerFactory.getLogger(EmailSmtpConnector.class);
private EmailSmtpConfig cfg; private EmailSmtpConfig cfg;
private Session session; private Session session;
private InternetAddress sender;
@Autowired @Autowired
public EmailSmtpConnector(EmailConfig cfg, EmailSmtpConfig smtpCfg) { public EmailSmtpConnector(EmailSmtpConfig cfg) {
try { this.cfg = cfg;
session = Session.getInstance(System.getProperties()); session = Session.getInstance(System.getProperties());
sender = new InternetAddress(cfg.getFrom(), cfg.getName()); }
} catch (UnsupportedEncodingException e) {
// What are we supposed to do with this?!
throw new ConfigurationException(e);
}
this.cfg = smtpCfg; @Override
public String getId() {
return "smtp";
} }
@Override @Override
@@ -70,20 +63,17 @@ public class EmailSmtpConnector implements IThreePidConnector {
} }
@Override @Override
public void send(IThreePidInviteReply invite, String content) { public void send(String senderAddress, String senderName, String recipient, String content) {
if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) {
throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type");
}
try { try {
InternetAddress sender = new InternetAddress(senderAddress, senderName);
MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(content, StandardCharsets.UTF_8));
msg.setHeader("X-Mailer", "mxisd"); // TODO set version msg.setHeader("X-Mailer", "mxisd"); // FIXME set version
msg.setSentDate(new Date()); msg.setSentDate(new Date());
msg.setFrom(sender); msg.setFrom(sender);
msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); msg.setRecipients(Message.RecipientType.TO, recipient);
msg.saveChanges(); msg.saveChanges();
log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); log.info("Sending invite to {} via SMTP using {}:{}", recipient, cfg.getHost(), cfg.getPort());
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");
transport.setStartTLS(cfg.getTls() > 0); transport.setStartTLS(cfg.getTls() > 0);
transport.setRequireStartTLS(cfg.getTls() > 1); transport.setRequireStartTLS(cfg.getTls() > 1);
@@ -91,13 +81,13 @@ public class EmailSmtpConnector implements IThreePidConnector {
log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort()); log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort());
transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword()); transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword());
try { try {
transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress())); transport.sendMessage(msg, InternetAddress.parse(recipient));
log.info("Invite to {} was sent", invite.getInvite().getAddress()); log.info("Invite to {} was sent", recipient);
} finally { } finally {
transport.close(); transport.close();
} }
} catch (MessagingException e) { } catch (UnsupportedEncodingException | MessagingException e) {
throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e); throw new RuntimeException("Unable to send e-mail invite to " + recipient, e);
} }
} }

View File

@@ -0,0 +1,35 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.connector.email;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.threepid.connector.IThreePidConnector;
public interface IEmailConnector extends IThreePidConnector {
@Override
default String getMedium() {
return ThreePidMedium.Email.getId();
}
void send(String senderAddress, String senderName, String recipient, String content);
}

View File

@@ -0,0 +1,38 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.notification;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
public interface INotificationGenerator {
String getId();
String getMedium();
String get(IThreePidInviteReply invite);
String getForValidation(IThreePidSession session);
String getForRemotePublishingValidation(IThreePidSession session);
}

View File

@@ -18,13 +18,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.invitation.generator; package io.kamax.mxisd.threepid.notification.email;
import io.kamax.matrix.ThreePidMedium; import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.invite.medium.EmailInviteConfig;
import io.kamax.mxisd.config.threepid.medium.EmailConfig; import io.kamax.mxisd.config.threepid.medium.EmailConfig;
import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.WordUtils;
@@ -34,55 +35,68 @@ import org.springframework.stereotype.Component;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@Component @Component
public class EmailInviteContentGenerator implements IInviteContentGenerator { public class EmailNotificationGenerator implements IEmailNotificationGenerator {
private EmailConfig cfg; private EmailConfig cfg;
private EmailInviteConfig invCfg; private EmailTemplateConfig templateCfg;
private MatrixConfig mxCfg; private MatrixConfig mxCfg;
private ApplicationContext app; private ApplicationContext app;
@Autowired // FIXME ApplicationContext shouldn't be injected, find another way from config (?) @Autowired // FIXME ApplicationContext shouldn't be injected, find another way from config (?)
public EmailInviteContentGenerator(EmailConfig cfg, EmailInviteConfig invCfg, MatrixConfig mxCfg, ApplicationContext app) { public EmailNotificationGenerator(EmailConfig cfg, EmailTemplateConfig templateCfg, MatrixConfig mxCfg, ApplicationContext app) {
this.cfg = cfg; this.cfg = cfg;
this.invCfg = invCfg; this.templateCfg = templateCfg;
this.mxCfg = mxCfg; this.mxCfg = mxCfg;
this.app = app; this.app = app;
} }
@Override @Override
public String getMedium() { public String getId() {
return ThreePidMedium.Email.getId(); return "template";
}
private String getTemplateContent(String location) throws IOException {
InputStream is = StringUtils.startsWith(location, "classpath:") ?
app.getResource(location).getInputStream() : new FileInputStream(location);
return IOUtils.toString(is, StandardCharsets.UTF_8);
}
private String populateCommon(String content, ThreePid recipient) {
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
content = content.replace("%DOMAIN%", mxCfg.getDomain());
content = content.replace("%DOMAIN_PRETTY%", domainPretty);
content = content.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom());
content = content.replace("%FROM_NAME%", cfg.getIdentity().getName());
content = content.replace("%RECIPIENT_MEDIUM%", recipient.getMedium());
content = content.replace("%RECIPIENT_ADDRESS%", recipient.getAddress());
return content;
}
private String getTemplateAndPopulate(String location, ThreePid recipient) throws IOException {
return populateCommon(getTemplateContent(location), recipient);
} }
@Override @Override
public String generate(IThreePidInviteReply invite) { public String get(IThreePidInviteReply invite) {
if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) {
throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type");
}
try { try {
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain()); ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
String templateBody = getTemplateAndPopulate(templateCfg.getInvite(), tpid);
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", ""); String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId()); String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", ""); String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId()); String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
String templateBody = IOUtils.toString(
StringUtils.startsWith(invCfg.getTemplate(), "classpath:") ?
app.getResource(invCfg.getTemplate()).getInputStream() : new FileInputStream(invCfg.getTemplate()),
StandardCharsets.UTF_8);
templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain());
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getFrom());
templateBody = templateBody.replace("%FROM_NAME%", cfg.getName());
templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId()); templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
templateBody = templateBody.replace("%SENDER_NAME%", senderName); templateBody = templateBody.replace("%SENDER_NAME%", senderName);
templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId); templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
templateBody = templateBody.replace("%INVITE_MEDIUM%", invite.getInvite().getMedium()); templateBody = templateBody.replace("%INVITE_MEDIUM%", tpid.getMedium());
templateBody = templateBody.replace("%INVITE_ADDRESS%", invite.getInvite().getAddress()); templateBody = templateBody.replace("%INVITE_ADDRESS%", tpid.getAddress());
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId()); templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
templateBody = templateBody.replace("%ROOM_NAME%", roomName); templateBody = templateBody.replace("%ROOM_NAME%", roomName);
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId); templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
@@ -93,4 +107,14 @@ public class EmailInviteContentGenerator implements IInviteContentGenerator {
} }
} }
@Override
public String getForValidation(IThreePidSession session) {
return null;
}
@Override
public String getForRemotePublishingValidation(IThreePidSession session) {
return null;
}
} }

View File

@@ -0,0 +1,71 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.notification.email;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.notification.INotificationHandler;
import io.kamax.mxisd.threepid.connector.email.IEmailConnector;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class EmailNotificationHandler implements INotificationHandler {
private EmailConfig cfg;
private IEmailNotificationGenerator generator;
private IEmailConnector connector;
@Autowired
public EmailNotificationHandler(EmailConfig cfg, List<IEmailNotificationGenerator> generators, List<IEmailConnector> connectors) {
generator = generators.stream()
.filter(o -> StringUtils.equals(cfg.getGenerator(), o.getId()))
.findFirst()
.orElseThrow(() -> new ConfigurationException("Email notification generator [" + cfg.getGenerator() + "] could not be found"));
connector = connectors.stream()
.filter(o -> StringUtils.equals(cfg.getConnector(), o.getId()))
.findFirst()
.orElseThrow(() -> new ConfigurationException("Email sender connector [" + cfg.getConnector() + "] could not be found"));
}
@Override
public String getMedium() {
return ThreePidMedium.Email.getId();
}
@Override
public void notify(IThreePidInviteReply invite) {
}
@Override
public void notify(IThreePidSession session) {
}
}

View File

@@ -0,0 +1,33 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.notification.email;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.threepid.notification.INotificationGenerator;
public interface IEmailNotificationGenerator extends INotificationGenerator {
@Override
default String getMedium() {
return ThreePidMedium.Email.getId();
}
}

View File

@@ -29,8 +29,6 @@ public interface IThreePidSession {
String getId(); String getId();
String getHash();
Instant getCreationTime(); Instant getCreationTime();
String getServer(); String getServer();
@@ -43,6 +41,8 @@ public interface IThreePidSession {
Optional<String> getNextLink(); Optional<String> getNextLink();
String getToken();
void validate(String token); void validate(String token);
boolean isValidated(); boolean isValidated();

View File

@@ -113,6 +113,11 @@ public class ThreePidSession implements IThreePidSession {
return Optional.ofNullable(nextLink); return Optional.ofNullable(nextLink);
} }
@Override
public String getToken() {
return token;
}
public synchronized void setAttempt(int attempt) { public synchronized void setAttempt(int attempt) {
if (isValidated()) { if (isValidated()) {
throw new IllegalStateException(); throw new IllegalStateException();

View File

@@ -63,18 +63,25 @@ forward:
- "https://vector.im" - "https://vector.im"
threepid: threepid:
email:
connector:
active: 'smtp'
provider:
smtp:
port: 587
tls: 1
invite:
medium: medium:
email: email:
template: 'classpath:email/invite-template.eml' identity:
from: ''
name: ''
connector: 'smtp'
generator: 'template'
connectors:
smtp:
host: ''
port: 587
tls: 1
login: ''
password: ''
generators:
template:
invite: 'classpath:email/invite-template.eml'
session:
validation: 'classpath:email/validate-template.eml'
storage: storage:
backend: 'sqlite' backend: 'sqlite'

View File

@@ -0,0 +1,66 @@
Subject: Your Matrix Validation Token
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
Content-Type: text/plain; charset=UTF-8
Content-Disposition: inline
Hello,
We have received a request to link this email address with a Matrix account.
If this was you who made this request, you may use the following link to complete the verification of your email address:
%VALIDATION_LINK%
If your client requires a code, the code is %VALIDATION_TOKEN%
If you aren't aware of making such a request, please disregard this email.
Regards,
%DOMAIN_PRETTY% Admins
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
Content-Type: text/html; charset=UTF-8
Content-Disposition: inline
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
body {
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
font-size: 12pt;
margin: 0px;
}
</style>
</head>
<body>
<p>Hello,</p>
<p>We have received a request to link this email address with a Matrix account.
If this was you who made this request, you may use the following link to
complete the verification of your email address:</p>
<p><a href=%VALIDATION_LINK%">Complete email verification</a></p>
<p>...or copy this link into your web browser:</p>
<p>%VALIDATION_LINK%</p>
<p>If your client requires a code, the code is %VALIDATION_TOKEN%</p>
<p>If you aren't aware of making such a request, please disregard this
email.</p>
<br>
<p>Regards,<br>
%DOMAIN_PRETTY% Admins</p>
</body>
</html>
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--