diff --git a/src/main/groovy/io/kamax/mxisd/config/SessionConfig.java b/src/main/groovy/io/kamax/mxisd/config/SessionConfig.java
index fb51537..fd20b1e 100644
--- a/src/main/groovy/io/kamax/mxisd/config/SessionConfig.java
+++ b/src/main/groovy/io/kamax/mxisd/config/SessionConfig.java
@@ -20,16 +20,131 @@
package io.kamax.mxisd.config;
+import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
+import javax.annotation.PostConstruct;
+
@Configuration
@ConfigurationProperties("session")
public class SessionConfig {
private static Logger log = LoggerFactory.getLogger(SessionConfig.class);
+ public static class Policy {
+
+ public static class PolicyTemplate {
+
+ public static class PolicySource {
+
+ private boolean enabled;
+ private boolean toLocal;
+ private boolean toRemote;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean toLocal() {
+ return toLocal;
+ }
+
+ public void setToLocal(boolean toLocal) {
+ this.toLocal = toLocal;
+ }
+
+ public boolean toRemote() {
+ return toRemote;
+ }
+
+ public void setToRemote(boolean toRemote) {
+ this.toRemote = toRemote;
+ }
+
+ }
+
+ private boolean enabled;
+ private PolicySource forLocal = new PolicySource();
+ private PolicySource forRemote = new PolicySource();
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public PolicySource getForLocal() {
+ return forLocal;
+ }
+
+ public PolicySource forLocal() {
+ return forLocal;
+ }
+
+ public PolicySource getForRemote() {
+ return forRemote;
+ }
+
+ public PolicySource forRemote() {
+ return forRemote;
+ }
+ }
+
+ private PolicyTemplate bind = new PolicyTemplate();
+ private PolicyTemplate validation = new PolicyTemplate();
+
+ public PolicyTemplate getBind() {
+ return bind;
+ }
+
+ public void setBind(PolicyTemplate bind) {
+ this.bind = bind;
+ }
+
+ public PolicyTemplate getValidation() {
+ return validation;
+ }
+
+ public void setValidation(PolicyTemplate validation) {
+ this.validation = validation;
+ }
+
+ }
+
+ private MatrixConfig mxCfg;
+ private Policy policy = new Policy();
+
+ @Autowired
+ public SessionConfig(MatrixConfig mxCfg) {
+ this.mxCfg = mxCfg;
+ }
+
+ public MatrixConfig getMatrixCfg() {
+ return mxCfg;
+ }
+
+ public Policy getPolicy() {
+ return policy;
+ }
+
+ public void setPolicy(Policy policy) {
+ this.policy = policy;
+ }
+
+ @PostConstruct
+ public void build() {
+ log.info("--- Session config ---");
+ log.info("Global Policy: {}", new Gson().toJson(policy));
+ }
}
diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java b/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java
index 1fb0f86..8d03874 100644
--- a/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java
+++ b/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java
@@ -23,7 +23,9 @@ package io.kamax.mxisd.controller.v1;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.kamax.mxisd.exception.BadRequestException;
+import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
+import io.kamax.mxisd.exception.MatrixException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,6 +35,8 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.Instant;
@ControllerAdvice
@ResponseBody
@@ -50,6 +54,23 @@ public class DefaultExceptionHandler {
return gson.toJson(obj);
}
+ @ExceptionHandler(InternalServerError.class)
+ public String handle(InternalServerError e, HttpServletResponse response) {
+ if (StringUtils.isNotBlank(e.getInternalReason())) {
+ log.error("Reference #{} - {}", e.getReference(), e.getInternalReason());
+ } else {
+ log.error("Reference #{}", e);
+ }
+
+ return handleGeneric(e, response);
+ }
+
+ @ExceptionHandler(MatrixException.class)
+ public String handleGeneric(MatrixException e, HttpServletResponse response) {
+ response.setStatus(e.getStatus());
+ return handle(e.getErrorCode(), e.getError());
+ }
+
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public String handle(MissingServletRequestParameterException e) {
@@ -72,7 +93,14 @@ public class DefaultExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handle(HttpServletRequest req, RuntimeException e) {
log.error("Unknown error when handling {}", req.getRequestURL(), e);
- return handle("M_UNKNOWN", StringUtils.defaultIfBlank(e.getMessage(), "An uknown error occured. Contact the server administrator if this persists."));
+ return handle(
+ "M_UNKNOWN",
+ StringUtils.defaultIfBlank(
+ e.getMessage(),
+ "An internal server error occured. If this error persists, please contact support with reference #" +
+ Instant.now().toEpochMilli()
+ )
+ );
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy
index 1e107d7..f785e5a 100644
--- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy
+++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy
@@ -27,6 +27,7 @@ import io.kamax.mxisd.ThreePid
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
import io.kamax.mxisd.exception.BadRequestException
+import io.kamax.mxisd.exception.SessionNotValidatedException
import io.kamax.mxisd.invitation.InvitationManager
import io.kamax.mxisd.lookup.ThreePidValidation
import io.kamax.mxisd.session.SessionMananger
@@ -105,12 +106,10 @@ class SessionController {
@RequestMapping(value = "/3pid/getValidated3pid")
String check(HttpServletRequest request, HttpServletResponse response,
@RequestParam String sid, @RequestParam("client_secret") String secret) {
- log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
+ log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
- Optional result = mgr.getValidated(sid, secret)
- if (result.isPresent()) {
- log.info("requested session was validated")
- ThreePidValidation pid = result.get()
+ try {
+ ThreePidValidation pid = mgr.getValidated(sid, secret)
JsonObject obj = new JsonObject()
obj.addProperty("medium", pid.getMedium())
@@ -118,14 +117,9 @@ class SessionController {
obj.addProperty("validated_at", pid.getValidation().toEpochMilli())
return gson.toJson(obj);
- } else {
- log.info("requested session was not validated")
-
- JsonObject obj = new JsonObject()
- obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED")
- obj.addProperty("error", "sid, secret or session not valid")
- response.setStatus(HttpStatus.SC_BAD_REQUEST)
- return gson.toJson(obj)
+ } catch (SessionNotValidatedException e) {
+ log.info("Session {} was requested but has not yet been validated", sid);
+ throw e;
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java b/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java
index d3ca848..5680075 100644
--- a/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java
+++ b/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java
@@ -20,16 +20,35 @@
package io.kamax.mxisd.exception;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
+import org.apache.http.HttpStatus;
import java.time.Instant;
-@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
-public class InternalServerError extends RuntimeException {
+public class InternalServerError extends MatrixException {
+
+ private String reference = Long.toString(Instant.now().toEpochMilli());
+ private String internalReason;
public InternalServerError() {
- super("An internal server error occured. If this error persists, please contact support with reference #" + Instant.now().toEpochMilli());
+ super(
+ HttpStatus.SC_INTERNAL_SERVER_ERROR,
+ "M_UNKNOWN",
+ "An internal server error occured. If this error persists, please contact support with reference #" +
+ Instant.now().toEpochMilli()
+ );
+ }
+
+ public InternalServerError(String internalReason) {
+ this();
+ this.internalReason = internalReason;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getInternalReason() {
+ return internalReason;
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/MatrixException.java b/src/main/groovy/io/kamax/mxisd/exception/MatrixException.java
new file mode 100644
index 0000000..7565b15
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/MatrixException.java
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+
+package io.kamax.mxisd.exception;
+
+public abstract class MatrixException extends MxisdException {
+
+ private int status;
+ private String errorCode;
+ private String error;
+
+ public MatrixException(int status, String errorCode, String error) {
+ this.status = status;
+ this.errorCode = errorCode;
+ this.error = error;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public String getErrorCode() {
+ return errorCode;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/MxisdException.java b/src/main/groovy/io/kamax/mxisd/exception/MxisdException.java
new file mode 100644
index 0000000..e6088f3
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/MxisdException.java
@@ -0,0 +1,24 @@
+/*
+ * 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 .
+ */
+
+package io.kamax.mxisd.exception;
+
+public class MxisdException extends RuntimeException {
+}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/NotAllowedException.java b/src/main/groovy/io/kamax/mxisd/exception/NotAllowedException.java
new file mode 100644
index 0000000..b3a1f77
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/NotAllowedException.java
@@ -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 .
+ */
+
+package io.kamax.mxisd.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.FORBIDDEN)
+public class NotAllowedException extends RuntimeException {
+
+ public NotAllowedException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/SessionNotValidatedException.java b/src/main/groovy/io/kamax/mxisd/exception/SessionNotValidatedException.java
new file mode 100644
index 0000000..6524ba6
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/SessionNotValidatedException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 .
+ */
+
+package io.kamax.mxisd.exception;
+
+import org.apache.http.HttpStatus;
+
+public class SessionNotValidatedException extends MatrixException {
+
+ public SessionNotValidatedException() {
+ super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/notification/INotificationHandler.java b/src/main/groovy/io/kamax/mxisd/notification/INotificationHandler.java
index 0b622df..6138784 100644
--- a/src/main/groovy/io/kamax/mxisd/notification/INotificationHandler.java
+++ b/src/main/groovy/io/kamax/mxisd/notification/INotificationHandler.java
@@ -27,8 +27,8 @@ public interface INotificationHandler {
String getMedium();
- void notify(IThreePidInviteReply invite);
+ void sendForInvite(IThreePidInviteReply invite);
- void notify(IThreePidSession session);
+ void sendForValidation(IThreePidSession session);
}
diff --git a/src/main/groovy/io/kamax/mxisd/notification/NotificationManager.java b/src/main/groovy/io/kamax/mxisd/notification/NotificationManager.java
index 63d72e4..2178530 100644
--- a/src/main/groovy/io/kamax/mxisd/notification/NotificationManager.java
+++ b/src/main/groovy/io/kamax/mxisd/notification/NotificationManager.java
@@ -55,14 +55,14 @@ public class NotificationManager {
}
public void sendForInvite(IThreePidInviteReply invite) {
- ensureMedium(invite.getInvite().getMedium()).notify(invite);
+ ensureMedium(invite.getInvite().getMedium()).sendForInvite(invite);
}
public void sendForValidation(IThreePidSession session) {
- ensureMedium(session.getThreePid().getMedium()).notify(session);
+ ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
}
- public void sendforRemotePublish(IThreePidSession session) {
+ public void sendforRemoteValidation(IThreePidSession session) {
throw new NotImplementedException("Remote publish of 3PID bind");
}
diff --git a/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java b/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java
index d6072d7..0273062 100644
--- a/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java
+++ b/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java
@@ -22,9 +22,11 @@ package io.kamax.mxisd.session;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.ThreePid;
+import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.SessionConfig;
import io.kamax.mxisd.exception.InvalidCredentialsException;
-import io.kamax.mxisd.lookup.SingleLookupReply;
+import io.kamax.mxisd.exception.NotAllowedException;
+import io.kamax.mxisd.exception.SessionNotValidatedException;
import io.kamax.mxisd.lookup.ThreePidValidation;
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.notification.NotificationManager;
@@ -39,7 +41,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
-import java.util.UUID;
@Component
public class SessionMananger {
@@ -52,13 +53,26 @@ public class SessionMananger {
private NotificationManager notifMgr;
@Autowired
- public SessionMananger(SessionConfig cfg, IStorage storage, LookupStrategy lookup, NotificationManager notifMgr) {
+ public SessionMananger(SessionConfig cfg, MatrixConfig mxCfg, IStorage storage, LookupStrategy lookup, NotificationManager notifMgr) {
this.cfg = cfg;
this.storage = storage;
this.lookup = lookup;
this.notifMgr = notifMgr;
}
+ private boolean isLocal(ThreePid tpid) {
+ if (!ThreePidMedium.Email.is(tpid.getMedium())) { // We can only handle E-mails for now
+ return false;
+ }
+
+ String domain = tpid.getAddress().split("@")[1];
+ return StringUtils.equalsIgnoreCase(cfg.getMatrixCfg().getDomain(), domain);
+ }
+
+ private boolean isKnownLocal(ThreePid tpid) {
+ return lookup.findLocal(tpid.getMedium(), tpid.getAddress()).isPresent();
+ }
+
private ThreePidSession getSession(String sid, String secret) {
Optional dao = storage.getThreePidSession(sid);
if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) {
@@ -71,12 +85,17 @@ public class SessionMananger {
private ThreePidSession getSessionIfValidated(String sid, String secret) {
ThreePidSession session = getSession(sid, secret);
if (!session.isValidated()) {
- throw new IllegalStateException("Session " + sid + " has not been validated");
+ throw new SessionNotValidatedException();
}
return session;
}
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
+ SessionConfig.Policy.PolicyTemplate policy = cfg.getPolicy().getValidation();
+ if (!policy.isEnabled()) {
+ throw new NotAllowedException("Validating 3PID is disabled globally");
+ }
+
synchronized (this) {
log.info("Server {} is asking to create session for {} (Attempt #{}) - Next link: {}", server, tpid, attempt, nextLink);
Optional dao = storage.findThreePidSession(tpid, secret);
@@ -94,17 +113,46 @@ public class SessionMananger {
return session.getId();
} else {
log.info("No existing session for {}", tpid);
+
+ boolean isLocalDomain = isLocal(tpid);
+ log.info("Is 3PID bound to local domain? {}", isLocalDomain);
+
+ if (isLocalDomain && (!policy.forLocal().isEnabled() || !policy.forLocal().toLocal())) {
+ throw new NotAllowedException("Validating local 3PID is not allowed");
+ }
+
+ // We lookup if the 3PID is already known locally.
+ boolean knownLocal = isKnownLocal(tpid);
+ log.info("Mapping with {} is " + (knownLocal ? "already" : "not") + " known locally", tpid);
+
+ if (!isLocalDomain && (
+ !policy.forRemote().isEnabled() || (
+ !policy.forRemote().toLocal() &&
+ !policy.forRemote().toRemote()
+ )
+ )) {
+ throw new NotAllowedException("Validating unknown remote 3PID is not allowed");
+ }
+
String sessionId;
do {
- sessionId = UUID.randomUUID().toString().replace("-", "");
+ sessionId = Long.toString(System.currentTimeMillis());
} while (storage.getThreePidSession(sessionId).isPresent());
String token = RandomStringUtils.randomNumeric(6);
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
- notifMgr.sendForValidation(session);
- log.info("Sent validation notification to {}", tpid);
+ // This might need a configuration by medium type?
+ if (!isLocalDomain) {
+ if (policy.forRemote().toLocal() && policy.forRemote().toRemote()) {
+ log.info("Session {} for {}: sending local validation notification", sessionId, tpid);
+ notifMgr.sendForValidation(session);
+ } else {
+ log.info("Session {} for {}: sending remote-only validation notification", sessionId, tpid);
+ notifMgr.sendforRemoteValidation(session);
+ }
+ }
storage.insertThreePidSession(session.getDao());
log.info("Stored session {}", sessionId, tpid, server);
@@ -130,65 +178,8 @@ public class SessionMananger {
public void bind(String sid, String secret, String mxid) {
ThreePidSession session = getSessionIfValidated(sid, secret);
- log.info("Attempting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer());
-
- // We lookup if the 3PID is already known remotely.
- Optional rRemote = lookup.findRemote(session.getThreePid().getMedium(), session.getThreePid().getAddress());
- boolean knownRemote = rRemote.isPresent() && StringUtils.equals(rRemote.get().getMxid().getId(), mxid);
- log.info("Mapping {} -> {} is " + (knownRemote ? "already" : "not") + " known remotely", mxid, session.getThreePid());
-
- boolean isLocalDomain = false;
- if (ThreePidMedium.Email.is(session.getThreePid().getMedium())) {
- // TODO
- // 1. Extract domain from email
- // 2. set isLocalDomain
- isLocalDomain = session.getThreePid().getAddress().isEmpty(); // FIXME only for testing
- }
- if (knownRemote) {
- log.info("No further action needed for Mapping {} -> {}");
- return;
- }
-
- // We lookup if the 3PID is already known locally.
- Optional 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?
- if (knownLocal) { // 3PID is ony known local
- if (isLocalDomain) {
- // TODO
- // 1. Check if global publishing is enabled, allowed and offered. If one is no, return.
- // 2. Publish globally
- notifMgr.sendforRemotePublish(session);
- }
-
- if (System.currentTimeMillis() % 2 == 0) { // FIXME only for testing
- // TODO
- // 1. Check if configured to publish globally non-local domain. If no, return
- notifMgr.sendforRemotePublish(session);
- }
-
- // TODO
- // Proxy to configurable IS, by default Matrix.org
- //
- // Separate workflow, if user accepts to publish globally
- // 1. display page to the user that it is waiting for the confirmation
- // 2. call mxisd-specific endpoint to publish globally
- // 3. check regularly on client page for a binding
- // 4. when found, show page "Done globally!"
- notifMgr.sendforRemotePublish(session);
- } else {
- if (isLocalDomain) { // 3PID is not known anywhere but is a local domain
- // TODO
- // check if config says this should fail or silently accept.
- // Required to silently accept if the backend is synapse itself.
- } else { // 3PID is not known anywhere and is remote
- // TODO
- // Proxy to configurable IS, by default Matrix.org
- notifMgr.sendforRemotePublish(session);
- }
- }
+ log.info("Accepting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer());
+ // TODO perform this if request was proxied
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java b/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java
index dd5085e..34da70d 100644
--- a/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java
+++ b/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java
@@ -24,6 +24,8 @@ public interface IThreePidSessionDao {
String getId();
+ long getCreationTime();
+
String getServer();
String getMedium();
@@ -38,4 +40,8 @@ public interface IThreePidSessionDao {
String getToken();
+ boolean getValidated();
+
+ long getValidationTime();
+
}
diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java
index 0b557e1..c2b18c0 100644
--- a/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java
+++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java
@@ -31,6 +31,9 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
@DatabaseField(id = true)
private String id;
+ @DatabaseField(canBeNull = false)
+ private long creationTime;
+
@DatabaseField(canBeNull = false)
private String server;
@@ -52,12 +55,19 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
@DatabaseField(canBeNull = false)
private String token;
+ @DatabaseField
+ private boolean validated;
+
+ @DatabaseField
+ private long validationTime;
+
public ThreePidSessionDao() {
// stub for ORMLite
}
public ThreePidSessionDao(IThreePidSessionDao session) {
setId(session.getId());
+ setCreationTime(session.getCreationTime());
setServer(session.getServer());
setMedium(session.getMedium());
setAddress(session.getAddress());
@@ -65,6 +75,9 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
setAttempt(session.getAttempt());
setNextLink(session.getNextLink());
setToken(session.getToken());
+ setValidated(session.getValidated());
+ setValidationTime(session.getValidationTime());
+
}
public ThreePidSessionDao(ThreePid tpid, String secret) {
@@ -82,6 +95,15 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
this.id = id;
}
+ @Override
+ public long getCreationTime() {
+ return creationTime;
+ }
+
+ public void setCreationTime(long creationTime) {
+ this.creationTime = creationTime;
+ }
+
@Override
public String getServer() {
return server;
@@ -91,24 +113,6 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
this.server = server;
}
- @Override
- public String getSecret() {
- return secret;
- }
-
- public void setSecret(String secret) {
- this.secret = secret;
- }
-
- @Override
- public int getAttempt() {
- return attempt;
- }
-
- public void setAttempt(int attempt) {
- this.attempt = attempt;
- }
-
@Override
public String getMedium() {
return medium;
@@ -127,6 +131,24 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
this.address = address;
}
+ @Override
+ public String getSecret() {
+ return secret;
+ }
+
+ public void setSecret(String secret) {
+ this.secret = secret;
+ }
+
+ @Override
+ public int getAttempt() {
+ return attempt;
+ }
+
+ public void setAttempt(int attempt) {
+ this.attempt = attempt;
+ }
+
@Override
public String getNextLink() {
return nextLink;
@@ -144,4 +166,22 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
public void setToken(String token) {
this.token = token;
}
+
+ public boolean getValidated() {
+ return validated;
+ }
+
+ public void setValidated(boolean validated) {
+ this.validated = validated;
+ }
+
+ @Override
+ public long getValidationTime() {
+ return validationTime;
+ }
+
+ public void setValidationTime(long validationTime) {
+ this.validationTime = validationTime;
+ }
+
}
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java b/src/main/groovy/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java
index 8124bf7..18e0fae 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java
@@ -23,7 +23,9 @@ package io.kamax.mxisd.threepid.connector.email;
import com.sun.mail.smtp.SMTPTransport;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
+import io.kamax.mxisd.exception.InternalServerError;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -64,6 +66,10 @@ public class EmailSmtpConnector implements IEmailConnector {
@Override
public void send(String senderAddress, String senderName, String recipient, String content) {
+ if (StringUtils.isBlank(content)) {
+ throw new InternalServerError("Notification content is empty");
+ }
+
try {
InternetAddress sender = new InternetAddress(senderAddress, senderName);
MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(content, StandardCharsets.UTF_8));
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/INotificationGenerator.java b/src/main/groovy/io/kamax/mxisd/threepid/notification/INotificationGenerator.java
index 969a87b..c61dba8 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/notification/INotificationGenerator.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/notification/INotificationGenerator.java
@@ -29,7 +29,7 @@ public interface INotificationGenerator {
String getMedium();
- String get(IThreePidInviteReply invite);
+ String getForInvite(IThreePidInviteReply invite);
String getForValidation(IThreePidSession session);
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java b/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java
index b327bea..6ed62c3 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java
@@ -22,13 +22,19 @@ package io.kamax.mxisd.threepid.notification.email;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.MatrixConfig;
+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.controller.v1.IdentityAPIv1;
+import io.kamax.mxisd.exception.InternalServerError;
+import io.kamax.mxisd.exception.NotImplementedException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@@ -41,17 +47,22 @@ import java.nio.charset.StandardCharsets;
@Component
public class EmailNotificationGenerator implements IEmailNotificationGenerator {
+ private Logger log = LoggerFactory.getLogger(EmailNotificationGenerator.class);
+
private EmailConfig cfg;
private EmailTemplateConfig templateCfg;
private MatrixConfig mxCfg;
+ private ServerConfig srvCfg;
+
+ @Autowired
private ApplicationContext app;
- @Autowired // FIXME ApplicationContext shouldn't be injected, find another way from config (?)
- public EmailNotificationGenerator(EmailConfig cfg, EmailTemplateConfig templateCfg, MatrixConfig mxCfg, ApplicationContext app) {
+ @Autowired
+ public EmailNotificationGenerator(EmailTemplateConfig templateCfg, EmailConfig cfg, MatrixConfig mxCfg, ServerConfig srvCfg) {
this.cfg = cfg;
this.templateCfg = templateCfg;
this.mxCfg = mxCfg;
- this.app = app;
+ this.srvCfg = srvCfg;
}
@Override
@@ -59,10 +70,14 @@ public class EmailNotificationGenerator implements IEmailNotificationGenerator {
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 getTemplateContent(String location) {
+ try {
+ InputStream is = StringUtils.startsWith(location, "classpath:") ?
+ app.getResource(location).getInputStream() : new FileInputStream(location);
+ return IOUtils.toString(is, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new InternalServerError("Unable to read template content at " + location + ": " + e.getMessage());
+ }
}
private String populateCommon(String content, ThreePid recipient) {
@@ -77,44 +92,51 @@ public class EmailNotificationGenerator implements IEmailNotificationGenerator {
return content;
}
- private String getTemplateAndPopulate(String location, ThreePid recipient) throws IOException {
+ private String getTemplateAndPopulate(String location, ThreePid recipient) {
return populateCommon(getTemplateContent(location), recipient);
}
@Override
- public String get(IThreePidInviteReply invite) {
- try {
- ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
- String templateBody = getTemplateAndPopulate(templateCfg.getInvite(), tpid);
+ public String getForInvite(IThreePidInviteReply invite) {
+ 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 senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
- String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
- String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
+ String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
+ String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
+ String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
+ String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
- templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
- templateBody = templateBody.replace("%SENDER_NAME%", senderName);
- templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
- templateBody = templateBody.replace("%INVITE_MEDIUM%", tpid.getMedium());
- templateBody = templateBody.replace("%INVITE_ADDRESS%", tpid.getAddress());
- templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
- templateBody = templateBody.replace("%ROOM_NAME%", roomName);
- templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
+ templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
+ templateBody = templateBody.replace("%SENDER_NAME%", senderName);
+ templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
+ templateBody = templateBody.replace("%INVITE_MEDIUM%", tpid.getMedium());
+ templateBody = templateBody.replace("%INVITE_ADDRESS%", tpid.getAddress());
+ templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
+ templateBody = templateBody.replace("%ROOM_NAME%", roomName);
+ templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
- return templateBody;
- } catch (IOException e) {
- throw new RuntimeException("Unable to read template file", e);
- }
+ return templateBody;
}
@Override
public String getForValidation(IThreePidSession session) {
- return null;
+ log.info("Generating notification content for 3PID Session validation");
+ String templateBody = getTemplateAndPopulate(templateCfg.getSession().getValidation(), session.getThreePid());
+
+ String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.BASE +
+ "/validate/" + session.getThreePid().getMedium() +
+ "/submitToken?sid=" + session.getId() + "&client_secret=" + session.getSecret() +
+ "&token=" + session.getToken();
+
+ templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink);
+ templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken());
+
+ return templateBody;
}
@Override
public String getForRemotePublishingValidation(IThreePidSession session) {
- return null;
+ throw new NotImplementedException("");
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java b/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java
index e0d9cfe..024c6ec 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java
@@ -42,6 +42,7 @@ public class EmailNotificationHandler implements INotificationHandler {
@Autowired
public EmailNotificationHandler(EmailConfig cfg, List generators, List connectors) {
+ this.cfg = cfg;
generator = generators.stream()
.filter(o -> StringUtils.equals(cfg.getGenerator(), o.getId()))
.findFirst()
@@ -59,13 +60,23 @@ public class EmailNotificationHandler implements INotificationHandler {
}
@Override
- public void notify(IThreePidInviteReply invite) {
-
+ public void sendForInvite(IThreePidInviteReply invite) {
+ connector.send(
+ cfg.getIdentity().getFrom(),
+ cfg.getIdentity().getName(),
+ invite.getInvite().getAddress(),
+ generator.getForInvite(invite)
+ );
}
@Override
- public void notify(IThreePidSession session) {
-
+ public void sendForValidation(IThreePidSession session) {
+ connector.send(
+ cfg.getIdentity().getFrom(),
+ cfg.getIdentity().getName(),
+ session.getThreePid().getAddress(),
+ generator.getForValidation(session)
+ );
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java b/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java
index d5c7dfc..446d148 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java
@@ -35,6 +35,8 @@ public interface IThreePidSession {
ThreePid getThreePid();
+ String getSecret();
+
int getAttempt();
void increaseAttempt();
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java b/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java
index 83802da..dee9354 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java
@@ -53,6 +53,11 @@ public class ThreePidSession implements IThreePidSession {
dao.getNextLink(),
dao.getToken()
);
+ timestamp = Instant.ofEpochMilli(dao.getCreationTime());
+ isValidated = dao.getValidated();
+ if (isValidated) {
+ validationTimestamp = Instant.ofEpochMilli(dao.getValidationTime());
+ }
}
public ThreePidSession(String id, String server, ThreePid tPid, String secret, int attempt, String nextLink, String token) {
@@ -154,6 +159,11 @@ public class ThreePidSession implements IThreePidSession {
return id;
}
+ @Override
+ public long getCreationTime() {
+ return timestamp.toEpochMilli();
+ }
+
@Override
public String getServer() {
return server;
@@ -189,6 +199,16 @@ public class ThreePidSession implements IThreePidSession {
return token;
}
+ @Override
+ public boolean getValidated() {
+ return isValidated;
+ }
+
+ @Override
+ public long getValidationTime() {
+ return isValidated ? validationTimestamp.toEpochMilli() : 0;
+ }
+
};
}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 82c1fec..31e8c59 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -83,6 +83,17 @@ threepid:
session:
validation: 'classpath:email/validate-template.eml'
+session.policy.validation:
+ enabled: true
+ forLocal:
+ enabled: true
+ toLocal: true
+ toRemote: true
+ forRemote:
+ enabled: true
+ toLocal: true # This should not be changed unless you know exactly the implications!
+ toRemote: true
+
storage:
backend: 'sqlite'
diff --git a/src/main/resources/email/validate-template.eml b/src/main/resources/email/validate-template.eml
index 0761712..e5b1346 100644
--- a/src/main/resources/email/validate-template.eml
+++ b/src/main/resources/email/validate-template.eml
@@ -45,7 +45,7 @@ body {
If this was you who made this request, you may use the following link to
complete the verification of your email address:
-Complete email verification
+Complete email verification
...or copy this link into your web browser: