Refactor after first tests against synapse
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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<ThreePidValidation> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
24
src/main/groovy/io/kamax/mxisd/exception/MxisdException.java
Normal file
24
src/main/groovy/io/kamax/mxisd/exception/MxisdException.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.exception;
|
||||
|
||||
public class MxisdException extends RuntimeException {
|
||||
}
|
@@ -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.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);
|
||||
}
|
||||
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
|
@@ -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");
|
||||
}
|
||||
|
||||
|
@@ -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<IThreePidSessionDao> 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<IThreePidSessionDao> 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<SingleLookupReply> 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<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?
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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();
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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));
|
||||
|
@@ -29,7 +29,7 @@ public interface INotificationGenerator {
|
||||
|
||||
String getMedium();
|
||||
|
||||
String get(IThreePidInviteReply invite);
|
||||
String getForInvite(IThreePidInviteReply invite);
|
||||
|
||||
String getForValidation(IThreePidSession session);
|
||||
|
||||
|
@@ -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("");
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -42,6 +42,7 @@ public class EmailNotificationHandler implements INotificationHandler {
|
||||
|
||||
@Autowired
|
||||
public EmailNotificationHandler(EmailConfig cfg, List<IEmailNotificationGenerator> generators, List<IEmailConnector> 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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -35,6 +35,8 @@ public interface IThreePidSession {
|
||||
|
||||
ThreePid getThreePid();
|
||||
|
||||
String getSecret();
|
||||
|
||||
int getAttempt();
|
||||
|
||||
void increaseAttempt();
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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:</p>
|
||||
|
||||
<p><a href=%VALIDATION_LINK%">Complete email verification</a></p>
|
||||
<p><a href="%VALIDATION_LINK%">Complete email verification</a></p>
|
||||
|
||||
<p>...or copy this link into your web browser:</p>
|
||||
|
||||
|
Reference in New Issue
Block a user