From cd6960fa80072c928b16e60b8165fb8b5a71da28 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 6 Sep 2017 04:17:46 +0200 Subject: [PATCH 01/28] Attempt to support invites, working in progress --- application.example.yaml | 40 ++++ build.gradle | 4 + .../io/kamax/mxisd/{lookup => }/ThreePid.java | 13 +- .../GoogleFirebaseAuthenticator.groovy | 148 +++--------- .../io/kamax/mxisd/config/FirebaseConfig.java | 26 +- .../invite/sender/EmailSenderConfig.java | 113 +++++++++ .../v1/DefaultExceptionHandler.java | 51 ++++ .../controller/v1/InvitationController.groovy | 33 ++- .../controller/v1/SessionController.groovy | 6 +- .../v1/io/ThreePidInviteReplyIO.java | 31 +++ .../exception/ConfigurationException.java | 16 ++ .../MappingAlreadyExistsException.java | 9 + .../mxisd/invitation/IThreePidInvite.java | 15 ++ .../invitation/IThreePidInviteReply.java | 11 + .../mxisd/invitation/InvitationManager.java | 226 ++++++++++++++++++ .../mxisd/invitation/ThreePidInvite.java | 39 +++ .../mxisd/invitation/ThreePidInviteReply.java | 30 +++ .../invitation/sender/EmailInviteSender.java | 82 +++++++ .../invitation/sender/IInviteSender.java | 11 + .../kamax/mxisd/lookup/ThreePidMapping.java | 10 + .../mxisd/lookup/ThreePidValidation.java | 20 ++ .../provider/GoogleFirebaseProvider.groovy | 172 +++++++++++++ .../lookup/strategy/LookupStrategy.groovy | 2 + .../RecursivePriorityLookupStrategy.groovy | 21 +- .../kamax/mxisd/mapping/MappingManager.java | 6 +- .../mxisd/signature/SignatureManager.groovy | 1 + .../spring/ConfigurationFailureAnalyzer.java | 6 +- 27 files changed, 999 insertions(+), 143 deletions(-) rename src/main/groovy/io/kamax/mxisd/{lookup => }/ThreePid.java (50%) create mode 100644 src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java create mode 100644 src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java create mode 100644 src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java create mode 100644 src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java create mode 100644 src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java create mode 100644 src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java create mode 100644 src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy diff --git a/application.example.yaml b/application.example.yaml index 2f54ce5..9538ebe 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -190,3 +190,43 @@ forward: servers: - "https://matrix.org" - "https://vector.im" + + + +# Configure the invite components +invite: + + # Configure invite senders for the various 3PID type + sender: + + # E-mail invite sender + email: + + # SMTP host + host: "smtp.kamax.io" + + # SMTP port + port: 587 + + # TLS mode for the connection. + # + # Possible values: + # 0 Disable TLS entirely + # 1 Enable TLS if supported by server + # 2 Force TLS and fail if not available + tls: 1 + + # Login for SMTP + login: "matrix-identity@example.org" + + # Password for the account + password: "ThePassword" + + # The e-mail to send as. If empty, will be the same as login + email: "matrix-identity@example.org" + + # The display name used in the e-mail + name: "Matrix Identity" + + # The MIME content to send + contentPath: "/absolute/path/to/file" diff --git a/build.gradle b/build.gradle index df0d430..0bce02a 100644 --- a/build.gradle +++ b/build.gradle @@ -97,6 +97,10 @@ dependencies { // Phone numbers validation compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1' + // E-mail sending + compile 'com.sun.mail:javax.mail:1.5.6' + compile 'javax.mail:javax.mail-api:1.5.6' + // Google Firebase Authentication backend compile 'com.google.firebase:firebase-admin:5.3.0' diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePid.java b/src/main/groovy/io/kamax/mxisd/ThreePid.java similarity index 50% rename from src/main/groovy/io/kamax/mxisd/lookup/ThreePid.java rename to src/main/groovy/io/kamax/mxisd/ThreePid.java index 6ecf4ab..d5585e9 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/ThreePid.java +++ b/src/main/groovy/io/kamax/mxisd/ThreePid.java @@ -1,17 +1,14 @@ -package io.kamax.mxisd.lookup; - -import java.time.Instant; +package io.kamax.mxisd; +// FIXME this should be in matrix-java-sdk public class ThreePid { private String medium; private String address; - private Instant validation; - public ThreePid(String medium, String address, Instant validation) { + public ThreePid(String medium, String address) { this.medium = medium; this.address = address; - this.validation = validation; } public String getMedium() { @@ -22,8 +19,4 @@ public class ThreePid { return address; } - public Instant getValidation() { - return validation; - } - } diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy b/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy index 0d00c08..2d78013 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy @@ -2,14 +2,16 @@ package io.kamax.mxisd.auth.provider import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions -import com.google.firebase.auth.* +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseCredential +import com.google.firebase.auth.FirebaseCredentials +import com.google.firebase.auth.FirebaseToken import com.google.firebase.internal.NonNull import com.google.firebase.tasks.OnFailureListener import com.google.firebase.tasks.OnSuccessListener import io.kamax.matrix.ThreePidMedium -import io.kamax.mxisd.GlobalProvider import io.kamax.mxisd.auth.UserAuthResult -import io.kamax.mxisd.lookup.SingleLookupRequest +import io.kamax.mxisd.invitation.InvitationManager import io.kamax.mxisd.lookup.ThreePidMapping import org.apache.commons.lang.StringUtils import org.slf4j.Logger @@ -17,11 +19,10 @@ import org.slf4j.LoggerFactory import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import java.util.function.Consumer import java.util.regex.Matcher import java.util.regex.Pattern -public class GoogleFirebaseAuthenticator implements GlobalProvider { +public class GoogleFirebaseAuthenticator implements AuthenticatorProvider { private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class); @@ -32,12 +33,15 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider { private FirebaseApp fbApp; private FirebaseAuth fbAuth; - public GoogleFirebaseAuthenticator(boolean isEnabled) { + private InvitationManager invMgr; + + public GoogleFirebaseAuthenticator(InvitationManager invMgr, boolean isEnabled) { this.isEnabled = isEnabled; + this.invMgr = invMgr; } - public GoogleFirebaseAuthenticator(String credsPath, String db, String domain) { - this(true); + public GoogleFirebaseAuthenticator(InvitationManager invMgr, String credsPath, String db, String domain) { + this(invMgr, true); this.domain = domain; try { fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db)); @@ -73,16 +77,6 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider { return isEnabled; } - @Override - public boolean isLocal() { - return true; - } - - @Override - public int getPriority() { - return 25; - } - private void waitOnLatch(CountDownLatch l) { try { l.await(30, TimeUnit.SECONDS); @@ -91,84 +85,6 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider { } } - private Optional findInternal(String medium, String address) { - UserRecord r; - CountDownLatch l = new CountDownLatch(1); - - OnSuccessListener success = new OnSuccessListener() { - @Override - void onSuccess(UserRecord result) { - log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid()) - r = result; - l.countDown() - } - }; - - OnFailureListener failure = new OnFailureListener() { - @Override - void onFailure(@NonNull Exception e) { - log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage()) - r = null; - l.countDown() - } - }; - - if (ThreePidMedium.Email.is(medium)) { - log.info("Performing E-mail 3PID lookup for {}", address) - fbAuth.getUserByEmail(address) - .addOnSuccessListener(success) - .addOnFailureListener(failure); - waitOnLatch(l); - } else if (ThreePidMedium.PhoneNumber.is(medium)) { - log.info("Performing msisdn 3PID lookup for {}", address) - fbAuth.getUserByPhoneNumber(address) - .addOnSuccessListener(success) - .addOnFailureListener(failure); - waitOnLatch(l); - } else { - log.info("{} is not a supported 3PID medium", medium); - r = null; - } - - return Optional.ofNullable(r); - } - - @Override - public Optional find(SingleLookupRequest request) { - Optional urOpt = findInternal(request.getType(), request.getThreePid()) - if (urOpt.isPresent()) { - return [ - address : request.getThreePid(), - medium : request.getType(), - mxid : "@${urOpt.get().getUid()}:${domain}", - not_before: 0, - not_after : 9223372036854775807, - ts : 0 - ] - } else { - return Optional.empty(); - } - } - - @Override - public List populate(List mappings) { - List results = new ArrayList<>(); - mappings.parallelStream().forEach(new Consumer() { - @Override - void accept(ThreePidMapping o) { - Optional urOpt = findInternal(o.getMedium(), o.getValue()); - if (urOpt.isPresent()) { - ThreePidMapping result = new ThreePidMapping(); - result.setMedium(o.getMedium()) - result.setValue(o.getValue()) - result.setMxid("@${urOpt.get().getUid()}:${domain}") - results.add(result) - } - } - }); - return results; - } - @Override public UserAuthResult authenticate(String id, String password) { if (!isEnabled()) { @@ -190,27 +106,37 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider { fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener() { @Override void onSuccess(FirebaseToken token) { - if (!StringUtils.equals(localpart, token.getUid())) { - log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid()); - result.failure(); - } + try { + if (!StringUtils.equals(localpart, token.getUid())) { + log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid()); + result.failure(); + } - log.info("{} was successfully authenticated", id); - result.success(id, token.getName()); - l.countDown() + log.info("{} was successfully authenticated", id); + result.success(id, token.getName()); + + if (StringUtils.isNotBlank(token.getEmail())) { + invMgr.publishMappingIfInvited(new ThreePidMapping(ThreePidMedium.Email.getId(), token.getEmail(), id)) + } + } finally { + l.countDown() + } } }).addOnFailureListener(new OnFailureListener() { @Override void onFailure(@NonNull Exception e) { - if (e instanceof IllegalArgumentException) { - log.info("Failure to authenticate {}: invalid firebase token", id); - } else { - log.info("Failure to authenticate {}: {}", id, e.getMessage(), e); - log.info("Exception", e); - } + try { + if (e instanceof IllegalArgumentException) { + log.info("Failure to authenticate {}: invalid firebase token", id); + } else { + log.info("Failure to authenticate {}: {}", id, e.getMessage(), e); + log.info("Exception", e); + } - result.failure(); - l.countDown() + result.failure(); + } finally { + l.countDown() + } } }); diff --git a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java b/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java index 6ad4791..3f65e42 100644 --- a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java @@ -1,7 +1,10 @@ package io.kamax.mxisd.config; -import io.kamax.mxisd.GlobalProvider; +import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.GoogleFirebaseAuthenticator; +import io.kamax.mxisd.invitation.InvitationManager; +import io.kamax.mxisd.lookup.provider.GoogleFirebaseProvider; +import io.kamax.mxisd.lookup.provider.IThreePidProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -20,6 +23,9 @@ public class FirebaseConfig { @Autowired private ServerConfig srvCfg; + @Autowired + private InvitationManager invMgr; + private boolean enabled; private String credentials; private String database; @@ -56,16 +62,24 @@ public class FirebaseConfig { log.info("Credentials: {}", getCredentials()); log.info("Database: {}", getDatabase()); } - - } @Bean - public GlobalProvider getProvider() { + public AuthenticatorProvider getAuthProvider() { if (!enabled) { - return new GoogleFirebaseAuthenticator(false); + return new GoogleFirebaseAuthenticator(invMgr, false); } else { - return new GoogleFirebaseAuthenticator(credentials, database, srvCfg.getName()); + return new GoogleFirebaseAuthenticator(invMgr, credentials, database, srvCfg.getName()); } } + + @Bean + public IThreePidProvider getLookupProvider() { + if (!enabled) { + return new GoogleFirebaseProvider(false); + } else { + return new GoogleFirebaseProvider(credentials, database, srvCfg.getName()); + } + } + } diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java new file mode 100644 index 0000000..555b2fc --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java @@ -0,0 +1,113 @@ +package io.kamax.mxisd.config.invite.sender; + +import io.kamax.mxisd.exception.ConfigurationException; +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(prefix = "invite.sender.email") +public class EmailSenderConfig { + + private Logger log = LoggerFactory.getLogger(EmailSenderConfig.class); + + private String host; + private int port; + private int tls; + private String login; + private String password; + private String email; + private String name; + private String contentPath; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getTls() { + return tls; + } + + public void setTls(int tls) { + this.tls = tls; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getContentPath() { + return contentPath; + } + + public void setContentPath(String contentPath) { + this.contentPath = contentPath; + } + + @PostConstruct + private void postConstruct() { + if (StringUtils.isBlank(getContentPath())) { + throw new ConfigurationException("invite.sender.email.contentPath"); + } + + File cp = new File(getContentPath()).getAbsoluteFile(); + if (!cp.exists() || !cp.isFile() || !cp.canRead()) { + throw new ConfigurationException("invite.sender.email.contentPath", getContentPath() + " does not exist, is not a file or cannot be read"); + } + + log.info("--- E-mail Invite Sender config ---"); + log.info("Host: {}", getHost()); + log.info("Port: {}", getPort()); + log.info("TLS Mode: {}", getTls()); + log.info("Login: {}", getLogin()); + log.info("Has password: {}", StringUtils.isBlank(getPassword())); + log.info("E-mail: {}", getEmail()); + log.info("Content path: {}", cp.getAbsolutePath()); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java b/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java new file mode 100644 index 0000000..91a1d72 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java @@ -0,0 +1,51 @@ +package io.kamax.mxisd.controller.v1; + +import io.kamax.mxisd.exception.BadRequestException; +import io.kamax.mxisd.exception.MappingAlreadyExistsException; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +@ControllerAdvice +@ResponseBody +@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE) +public class DefaultExceptionHandler { + + private Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class); + + static String handle(String erroCode, String error) { + return "{\"errcode\":\"" + erroCode + "\",\"error\":\"" + error + "\"}"; + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MissingServletRequestParameterException.class) + public String handle(MissingServletRequestParameterException e) { + return handle("M_INVALID_BODY", e.getMessage()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MappingAlreadyExistsException.class) + public String handle(MappingAlreadyExistsException e) { + return handle("M_ALREADY_EXISTS", e.getMessage()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(BadRequestException.class) + public String handle(BadRequestException e) { + return handle("M_BAD_REQUEST", e.getMessage()); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @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.")); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy index 46bd1fc..d2ccc34 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy @@ -20,10 +20,19 @@ package io.kamax.mxisd.controller.v1 -import io.kamax.mxisd.exception.NotImplementedException +import com.google.gson.Gson +import io.kamax.matrix.MatrixID +import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO +import io.kamax.mxisd.invitation.IThreePidInvite +import io.kamax.mxisd.invitation.IThreePidInviteReply +import io.kamax.mxisd.invitation.InvitationManager +import io.kamax.mxisd.invitation.ThreePidInvite +import io.kamax.mxisd.key.KeyManager import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import javax.servlet.http.HttpServletRequest @@ -35,11 +44,25 @@ class InvitationController { private Logger log = LoggerFactory.getLogger(InvitationController.class) - @RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST) - String store(HttpServletRequest request) { - log.error("{} was requested but not implemented", request.getRequestURL()) + @Autowired + private InvitationManager mgr - throw new NotImplementedException() + @Autowired + private KeyManager keyMgr + + private Gson gson = new Gson() + + @RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST) + String store( + HttpServletRequest request, + @RequestParam String sender, + @RequestParam String medium, + @RequestParam String address, + @RequestParam("room_id") String roomId) { + IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId) + IThreePidInviteReply reply = mgr.storeInvite(invite) + + return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()))) } } 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 c70d203..3fc9241 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy @@ -25,7 +25,7 @@ import com.google.gson.JsonObject 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.lookup.ThreePid +import io.kamax.mxisd.lookup.ThreePidValidation import io.kamax.mxisd.mapping.MappingManager import org.apache.commons.io.IOUtils import org.apache.commons.lang.StringUtils @@ -93,10 +93,10 @@ class SessionController { @RequestParam String sid, @RequestParam("client_secret") String secret) { log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()) - Optional result = mgr.getValidated(sid, secret) + Optional result = mgr.getValidated(sid, secret) if (result.isPresent()) { log.info("requested session was validated") - ThreePid pid = result.get() + ThreePidValidation pid = result.get() JsonObject obj = new JsonObject() obj.addProperty("medium", pid.getMedium()) diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java new file mode 100644 index 0000000..8967ecb --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java @@ -0,0 +1,31 @@ +package io.kamax.mxisd.controller.v1.io; + +import io.kamax.mxisd.invitation.IThreePidInviteReply; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ThreePidInviteReplyIO { + + private String token; + private List public_keys; + private String display_name; + + public ThreePidInviteReplyIO(IThreePidInviteReply reply, String pubKey) { + this.token = reply.getToken(); + this.public_keys = new ArrayList<>(Arrays.asList(new Key(pubKey))); + this.display_name = reply.getDisplayName(); + } + + public class Key { + private String key_validity_url; + private String public_key; + + public Key(String key) { + this.key_validity_url = "https://example.org/_matrix/fixme"; // FIXME have a proper URL even if synapse does not check + this.public_key = key; + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java b/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java index 1481990..8a2d158 100644 --- a/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java +++ b/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java @@ -1,11 +1,27 @@ package io.kamax.mxisd.exception; +import java.util.Optional; + public class ConfigurationException extends RuntimeException { private String key; + private String detailedMsg; public ConfigurationException(String key) { super("Invalid or empty value for configuration key " + key); } + public ConfigurationException(Throwable t) { + super(t.getMessage(), t); + } + + public ConfigurationException(String key, String detailedMsg) { + this(key); + this.detailedMsg = detailedMsg; + } + + public Optional getDetailedMessage() { + return Optional.ofNullable(detailedMsg); + } + } diff --git a/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java b/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java new file mode 100644 index 0000000..96bb741 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java @@ -0,0 +1,9 @@ +package io.kamax.mxisd.exception; + +public class MappingAlreadyExistsException extends RuntimeException { + + public MappingAlreadyExistsException() { + super("A mapping already exists for this 3PID"); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java new file mode 100644 index 0000000..48ab6d3 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java @@ -0,0 +1,15 @@ +package io.kamax.mxisd.invitation; + +import io.kamax.matrix._MatrixID; + +public interface IThreePidInvite { + + _MatrixID getSender(); + + String getMedium(); + + String getAddress(); + + String getRoomId(); + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java new file mode 100644 index 0000000..e9a6f3a --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java @@ -0,0 +1,11 @@ +package io.kamax.mxisd.invitation; + +public interface IThreePidInviteReply { + + IThreePidInvite getInvite(); + + String getToken(); + + String getDisplayName(); + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java new file mode 100644 index 0000000..7f994d0 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -0,0 +1,226 @@ +package io.kamax.mxisd.invitation; + +import com.google.gson.Gson; +import io.kamax.matrix.ThreePid; +import io.kamax.mxisd.exception.BadRequestException; +import io.kamax.mxisd.exception.MappingAlreadyExistsException; +import io.kamax.mxisd.invitation.sender.IInviteSender; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.strategy.LookupStrategy; +import io.kamax.mxisd.signature.SignatureManager; +import org.apache.commons.lang.RandomStringUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.TextParseException; +import org.xbill.DNS.Type; + +import javax.annotation.PostConstruct; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class InvitationManager { + + private Logger log = LoggerFactory.getLogger(InvitationManager.class); + + private Map invitations = new ConcurrentHashMap<>(); + + @Autowired + private LookupStrategy lookupMgr; + + @Autowired + private SignatureManager signMgr; + + private Map senders; + + private CloseableHttpClient client; + private Gson gson; + + @PostConstruct + private void postConstruct() { + gson = new Gson(); + + try { + HttpClientBuilder b = HttpClientBuilder.create(); + + // setup a Trust Strategy that allows all certificates. + // + SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { + public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + return true; + } + }).build(); + b.setSslcontext(sslContext); + + // don't check Hostnames, either. + // -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken + HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; + + // here's the special part: + // -- need to create an SSL Socket Factory, to use our weakened "trust strategy"; + // -- and create a Registry, to register it. + // + SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + // now, we create connection-manager using our Registry. + // -- allows multi-threaded use + PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + b.setConnectionManager(connMgr); + + // finally, build the HttpClient; + // -- done! + client = b.build(); + } catch (Exception e) { + // FIXME do better... + throw new RuntimeException(e); + } + } + + String getSrvRecordName(String domain) { + return "_matrix._tcp." + domain; + } + + // TODO use caching mechanism + // TODO export in matrix-java-sdk + Optional findHomeserverForDomain(String domain) { + log.debug("Performing SRV lookup for {}", domain); + String lookupDns = getSrvRecordName(domain); + log.info("Lookup name: {}", lookupDns); + + try { + SRVRecord[] records = (SRVRecord[]) new Lookup(lookupDns, Type.SRV).run(); + if (records != null) { + Arrays.sort(records, Comparator.comparingInt(SRVRecord::getPriority)); + for (SRVRecord record : records) { + log.info("Found SRV record: {}", record.toString()); + return Optional.of("https://" + record.getTarget().toString(true) + ":" + record.getPort()); + } + } else { + log.info("No SRV record for {}", lookupDns); + } + } catch (TextParseException e) { + log.warn("Unable to perform DNS SRV query for {}: {}", lookupDns, e.getMessage()); + } + + log.info("Performing basic lookup using domain name {}", domain); + return Optional.of("https://" + domain + ":8448"); + } + + @Autowired + public InvitationManager(List senderList) { + senders = new HashMap<>(); + senderList.forEach(sender -> senders.put(sender.getMedium(), sender)); + } + + public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync + IInviteSender sender = senders.get(invitation.getMedium()); + if (sender == null) { + throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported"); + } + + ThreePid pid = new ThreePid(invitation.getMedium(), invitation.getAddress()); + + log.info("Storing invite for {}:{} from {} in room {}", pid.getMedium(), pid.getAddress(), invitation.getSender(), invitation.getRoomId()); + if (invitations.containsKey(pid)) { + log.info("Invite is already pending for {}:{}, returning data", pid.getMedium(), pid.getAddress()); + return invitations.get(pid); + } + + SingleLookupRequest request = new SingleLookupRequest(); + request.setType(invitation.getMedium()); + request.setThreePid(invitation.getAddress()); + request.setRecursive(true); + request.setRequester("Internal"); + + Optional result = lookupMgr.findRecursive(request); + if (result.isPresent()) { + log.info("Mapping for {}:{} already exists, refusing to store invite", pid.getMedium(), pid.getAddress()); + throw new MappingAlreadyExistsException(); + } + + String token = RandomStringUtils.randomAlphanumeric(64); + String displayName = invitation.getAddress().substring(0, 3) + "..."; + + IThreePidInviteReply reply = new ThreePidInviteReply(invitation, token, displayName); + + log.info("Performing invite to {}:{}", pid.getMedium(), pid.getAddress()); + sender.send(reply); + + invitations.put(pid, reply); + log.info("A new invite has been created for {}:{}", pid.getMedium(), pid.getAddress()); + + return reply; + } + + public void publishMappingIfInvited(ThreePidMapping threePid) { + ThreePid key = new ThreePid(threePid.getMedium(), threePid.getValue()); + IThreePidInviteReply reply = invitations.get(key); + if (reply == null) { + log.info("{}:{} does not have a pending invite, no mapping to publish", threePid.getMedium(), threePid.getValue()); + return; + } + + log.info("{}:{} has an invite pending, publishing mapping", threePid.getMedium(), threePid.getValue()); + String domain = reply.getInvite().getSender().getDomain(); + log.info("Discovering HS for domain {}", domain); + Optional hsUrlOpt = findHomeserverForDomain(domain); + if (!hsUrlOpt.isPresent()) { + log.warn("No HS found for domain {} - ignoring publishing", domain); + } else { + HttpPost req = new HttpPost(hsUrlOpt.get() + "/_matrix/federation/v1/3pid/onbind"); + JSONObject obj = new JSONObject(); // TODO use Gson instead + obj.put("mxisd", threePid.getMxid()); + obj.put("token", reply.getToken()); + String mapping = gson.toJson(signMgr.signMessage(obj.toString())); // FIXME we shouldn't need to be doign this + + JSONObject content = new JSONObject(); // TODO use Gson instead + content.put("invites", Collections.singletonList(mapping)); + content.put("medium", threePid.getMedium()); + content.put("address", threePid.getValue()); + content.put("mxid", threePid.getMxid()); + + StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); + entity.setContentType("application/json"); + req.setEntity(entity); + try { + log.info("Posting onBind event to {}", req.getURI()); + CloseableHttpResponse response = client.execute(req); + response.close(); + } catch (IOException e) { + log.warn("Unable to tell HS {} about invite being mapped", domain, e); + } + } + + invitations.remove(key); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java new file mode 100644 index 0000000..38018cd --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java @@ -0,0 +1,39 @@ +package io.kamax.mxisd.invitation; + +import io.kamax.matrix._MatrixID; + +public class ThreePidInvite implements IThreePidInvite { + + private _MatrixID sender; + private String medium; + private String address; + private String roomId; + + public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId) { + this.sender = sender; + this.medium = medium; + this.address = address; + this.roomId = roomId; + } + + @Override + public _MatrixID getSender() { + return sender; + } + + @Override + public String getMedium() { + return medium; + } + + @Override + public String getAddress() { + return address; + } + + @Override + public String getRoomId() { + return roomId; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java new file mode 100644 index 0000000..296bd25 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java @@ -0,0 +1,30 @@ +package io.kamax.mxisd.invitation; + +public class ThreePidInviteReply implements IThreePidInviteReply { + + private IThreePidInvite invite; + private String token; + private String displayName; + + public ThreePidInviteReply(IThreePidInvite invite, String token, String displayName) { + this.invite = invite; + this.token = token; + this.displayName = displayName; + } + + @Override + public IThreePidInvite getInvite() { + return invite; + } + + @Override + public String getToken() { + return token; + } + + @Override + public String getDisplayName() { + return displayName; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java new file mode 100644 index 0000000..9699143 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java @@ -0,0 +1,82 @@ +package io.kamax.mxisd.invitation.sender; + +import com.sun.mail.smtp.SMTPTransport; +import io.kamax.matrix.ThreePidMedium; +import io.kamax.mxisd.config.invite.sender.EmailSenderConfig; +import io.kamax.mxisd.exception.ConfigurationException; +import io.kamax.mxisd.invitation.IThreePidInviteReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Date; + +@Component +public class EmailInviteSender implements IInviteSender { + + private Logger log = LoggerFactory.getLogger(EmailInviteSender.class); + + @Autowired + private EmailSenderConfig cfg; + + private Session session; + private InternetAddress sender; + + @PostConstruct + private void postConstruct() { + try { + session = Session.getInstance(System.getProperties()); + sender = new InternetAddress(cfg.getEmail(), cfg.getName()); + } catch (UnsupportedEncodingException e) { + // What are we supposed to do with this?! + throw new ConfigurationException(e); + } + } + + @Override + public String getMedium() { + return ThreePidMedium.Email.getId(); + } + + @Override + public void send(IThreePidInviteReply invite) { + if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) { + throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type"); + } + + try { + MimeMessage msg = new MimeMessage(session, new FileInputStream(cfg.getContentPath())); + msg.setHeader("X-Mailer", "mxisd"); + msg.setSentDate(new Date()); + msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); + msg.setFrom(sender); + + log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); + SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); + transport.setStartTLS(cfg.getTls() > 0); + transport.setRequireStartTLS(cfg.getTls() > 1); + + log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort()); + transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword()); + try { + transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress())); + log.info("Invite to {} was sent", invite.getInvite().getAddress()); + } finally { + transport.close(); + } + } catch (IOException | MessagingException e) { + throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java new file mode 100644 index 0000000..4ad7ccf --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java @@ -0,0 +1,11 @@ +package io.kamax.mxisd.invitation.sender; + +import io.kamax.mxisd.invitation.IThreePidInviteReply; + +public interface IInviteSender { + + String getMedium(); + + void send(IThreePidInviteReply invite); + +} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java index 741e56b..c87d1b4 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java +++ b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java @@ -28,6 +28,16 @@ public class ThreePidMapping { private String value; private String mxid; + public ThreePidMapping() { + // stub + } + + public ThreePidMapping(String medium, String value, String mxid) { + setMedium(medium); + setValue(value); + setMxid(mxid); + } + public String getMedium() { return medium; } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java new file mode 100644 index 0000000..cb78967 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java @@ -0,0 +1,20 @@ +package io.kamax.mxisd.lookup; + +import io.kamax.mxisd.ThreePid; + +import java.time.Instant; + +public class ThreePidValidation extends ThreePid { + + private Instant validation; + + public ThreePidValidation(String medium, String address, Instant validation) { + super(medium, address); + this.validation = validation; + } + + public Instant getValidation() { + return validation; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy new file mode 100644 index 0000000..8b0bf82 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy @@ -0,0 +1,172 @@ +package io.kamax.mxisd.lookup.provider + +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseCredential +import com.google.firebase.auth.FirebaseCredentials +import com.google.firebase.auth.UserRecord +import com.google.firebase.internal.NonNull +import com.google.firebase.tasks.OnFailureListener +import com.google.firebase.tasks.OnSuccessListener +import io.kamax.matrix.ThreePidMedium +import io.kamax.mxisd.lookup.SingleLookupRequest +import io.kamax.mxisd.lookup.ThreePidMapping +import org.apache.commons.lang.StringUtils +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import java.util.regex.Pattern + +public class GoogleFirebaseProvider implements IThreePidProvider { + + private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class); + + private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); + + private boolean isEnabled; + private String domain; + private FirebaseApp fbApp; + private FirebaseAuth fbAuth; + + public GoogleFirebaseProvider(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public GoogleFirebaseProvider(String credsPath, String db, String domain) { + this(true); + this.domain = domain; + try { + fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db)); + fbAuth = FirebaseAuth.getInstance(fbApp); + + log.info("Google Firebase Authentication is ready"); + } catch (IOException e) { + throw new RuntimeException("Error when initializing Firebase", e); + } + } + + private FirebaseCredential getCreds(String credsPath) throws IOException { + if (StringUtils.isNotBlank(credsPath)) { + return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath)); + } else { + return FirebaseCredentials.applicationDefault(); + } + } + + private FirebaseOptions getOpts(String credsPath, String db) throws IOException { + if (StringUtils.isBlank(db)) { + throw new IllegalArgumentException("Firebase database is not configured"); + } + + return new FirebaseOptions.Builder() + .setCredential(getCreds(credsPath)) + .setDatabaseUrl(db) + .build(); + } + + @Override + public boolean isEnabled() { + return isEnabled; + } + + @Override + public boolean isLocal() { + return true; + } + + @Override + public int getPriority() { + return 25; + } + + private void waitOnLatch(CountDownLatch l) { + try { + l.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("Interrupted while waiting for Firebase auth check"); + } + } + + private Optional findInternal(String medium, String address) { + UserRecord r; + CountDownLatch l = new CountDownLatch(1); + + OnSuccessListener success = new OnSuccessListener() { + @Override + void onSuccess(UserRecord result) { + log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid()) + r = result; + l.countDown() + } + }; + + OnFailureListener failure = new OnFailureListener() { + @Override + void onFailure(@NonNull Exception e) { + log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage()) + r = null; + l.countDown() + } + }; + + if (ThreePidMedium.Email.is(medium)) { + log.info("Performing E-mail 3PID lookup for {}", address) + fbAuth.getUserByEmail(address) + .addOnSuccessListener(success) + .addOnFailureListener(failure); + waitOnLatch(l); + } else if (ThreePidMedium.PhoneNumber.is(medium)) { + log.info("Performing msisdn 3PID lookup for {}", address) + fbAuth.getUserByPhoneNumber(address) + .addOnSuccessListener(success) + .addOnFailureListener(failure); + waitOnLatch(l); + } else { + log.info("{} is not a supported 3PID medium", medium); + r = null; + } + + return Optional.ofNullable(r); + } + + @Override + public Optional find(SingleLookupRequest request) { + Optional urOpt = findInternal(request.getType(), request.getThreePid()) + if (urOpt.isPresent()) { + return [ + address : request.getThreePid(), + medium : request.getType(), + mxid : "@${urOpt.get().getUid()}:${domain}", + not_before: 0, + not_after : 9223372036854775807, + ts : 0 + ] + } else { + return Optional.empty(); + } + } + + @Override + public List populate(List mappings) { + List results = new ArrayList<>(); + mappings.parallelStream().forEach(new Consumer() { + @Override + void accept(ThreePidMapping o) { + Optional urOpt = findInternal(o.getMedium(), o.getValue()); + if (urOpt.isPresent()) { + ThreePidMapping result = new ThreePidMapping(); + result.setMedium(o.getMedium()) + result.setValue(o.getValue()) + result.setMxid("@${urOpt.get().getUid()}:${domain}") + results.add(result) + } + } + }); + return results; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy index dee4646..1e1c8d0 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy @@ -31,6 +31,8 @@ interface LookupStrategy { Optional find(SingleLookupRequest request) + Optional findRecursive(SingleLookupRequest request) + List find(BulkLookupRequest requests) } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy index 4317adc..0aa7d1b 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy @@ -93,13 +93,17 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea } List listUsableProviders(ALookupRequest request) { + return listUsableProviders(request, false); + } + + List listUsableProviders(ALookupRequest request, boolean forceRecursive) { List usableProviders = new ArrayList<>() boolean canRecurse = isAllowedForRecursive(request.getRequester()) log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse) for (IThreePidProvider provider : providers) { - if (provider.isEnabled() && (provider.isLocal() || canRecurse)) { + if (provider.isEnabled() && (provider.isLocal() || canRecurse || forceRecursive)) { usableProviders.add(provider) } } @@ -117,9 +121,8 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea }).collect(Collectors.toList()) } - @Override - Optional find(SingleLookupRequest request) { - for (IThreePidProvider provider : listUsableProviders(request)) { + Optional find(SingleLookupRequest request, boolean forceRecursive) { + for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) { Optional lookupDataOpt = provider.find(request) if (lookupDataOpt.isPresent()) { return lookupDataOpt @@ -134,6 +137,16 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea return Optional.empty() } + @Override + Optional find(SingleLookupRequest request) { + return find(request, false) + } + + @Override + Optional findRecursive(SingleLookupRequest request) { + return find(request, true) + } + @Override List find(BulkLookupRequest request) { List mapToDo = new ArrayList<>(request.getMappings()) diff --git a/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java b/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java index 1b929e9..c631a81 100644 --- a/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java +++ b/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java @@ -1,7 +1,7 @@ package io.kamax.mxisd.mapping; import io.kamax.mxisd.exception.BadRequestException; -import io.kamax.mxisd.lookup.ThreePid; +import io.kamax.mxisd.lookup.ThreePidValidation; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,10 +72,10 @@ public class MappingManager { s.validationTimestamp = Instant.now(); } - public Optional getValidated(String sid, String secret) { + public Optional getValidated(String sid, String secret) { Session s = sessions.get(sid); if (s != null && StringUtils.equals(s.secret, secret)) { - return Optional.of(new ThreePid(s.medium, s.address, s.validationTimestamp)); + return Optional.of(new ThreePidValidation(s.medium, s.address, s.validationTimestamp)); } return Optional.empty(); diff --git a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy index 2f4fb4e..8eece94 100644 --- a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy +++ b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy @@ -40,6 +40,7 @@ class SignatureManager implements InitializingBean { private EdDSAEngine signEngine + // FIXME we need to return a proper object, or a string with the signature and all? Map signMessage(String message) { byte[] signRaw = signEngine.signOneShot(message.getBytes()) String sign = Base64.getEncoder().encodeToString(signRaw) diff --git a/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java b/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java index b6a0087..e0b7c78 100644 --- a/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java +++ b/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java @@ -8,7 +8,11 @@ public class ConfigurationFailureAnalyzer extends AbstractFailureAnalyzer Date: Wed, 6 Sep 2017 10:52:57 +0200 Subject: [PATCH 02/28] Add icons --- media/mx-id-icon-reverse.png | Bin 0 -> 8772 bytes media/mx-id-icon.png | Bin 0 -> 5817 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 media/mx-id-icon-reverse.png create mode 100644 media/mx-id-icon.png diff --git a/media/mx-id-icon-reverse.png b/media/mx-id-icon-reverse.png new file mode 100644 index 0000000000000000000000000000000000000000..79d6516ed236dc602887f17b02f4b164637498eb GIT binary patch literal 8772 zcmd5?2{@GR+E*%*B@|i1@RelWvu9*WlFA;E7=%f-Fi45AD`BjOl)}h131dsPu|)PQ zyX*{&3FCWSo#lVdch32)|Nr`yb9v{vp7*)e=f3avcR#P|eRBDdHUljuEfp0NgRai` zD^yg}!O#zOfQoA0f1IN^1ql0Z>S>>++6|2+RDrw0cXZ6$si^3VLBD-eNh$1Lk;X$; zUz29+@FAGE7}-@xf{KcdO830_wR;1L!#;_Yo~fr+NHNV9j(XAaABvlD^Ek(Db89i! zSY4aNqJTF+{bPyt3IFWDM|QVT1XTwZ79Xg{MSaLPJ6?RVPQW4jrUna(Arq6f+#{L7 zM=A#jW2OkaS`p^DZF!0_X?{yP=(+Dar~K!Ke)?B!^!TCjG@I$m@{VeIod4(7rzc$V z8?17a;@e+v^6pZkxf7n~`j7+)c~M0yUi0te7N)O`&ly;Uw<_oD)V;oNLTUVb=oR{1 zB_~X3s9Lxl7dfQDILg#{z0hFllb2UGj`9T=B*xR`{gvNDd1F+Zw?~BMOP|s_mH!8h zpiZ414!5GIJwKh~$yB^4A9Z#)XLrEcH2TAO&aUF^%~3-}9*V$-+O~Fu!)Elo&H|p* zAFnC%t2Qd*Ja0#tX?7o1`31O5t)Ny{#*}X=EG)n2;_{=Y@oA^T8%0|BIj@XphL8Db z@7!t*VeIQXzO^O$cHhs(zJ40%_S`W7X$k!8JZ+5fj56y7aUs#B>yk*G5T%y_ihsFo zvgyy--0LUN^gJUkTpu$TR`?6)A5C0)Is$n-g_pCA%-;W}*V!iOc&^IzmY2Fi^Bt&y znRe%dyq$|_RJT7s8K}34tVljWD0e6s z^w=i8tWx?GH?~kSS}A#3rO)dxCaa@P+?l?*>zP9{IHF0jeE(g@&iyVfA1}3Y>1-$F zyuUc5-<@07C$Bm}lTj|J*0PSW%HtoA0Oe&Ge9#bUMcguIW04 zY8p&}^XA}OE604V*tX-5zuB8dHLZcq;1;>&iSi~Xe1f}U--&;A-6=kDc7I@E-_~j4 z$E-IW?G2_GU}g$8c7TS*tme@0CsOhITKyf??6IOZuZrX-dDT-PN+P7^&`?j)ld!tzYNhM@Cq3Nx1;%#DON@};po!TSl$~*jlLasOQNov@Q z1-w|!Vu@8Yofqg~AwXUx= zn%xpMtMUqzj_}GyqFQjd1s0Cmw%SNG;f0?^m*3YeiLWvXFMM>#E3i=QeuC?ym7mC) z*dF9C?(-A`x?4+Y^UG^!%a@)R_nmBrCZenhYsJWK1zAmP-hJLWV`KBkG=tuJ>=WCj z_mw_YYj*FT=;`8p{(cR)&Mjm;ibBKp+8R>7en6d#WHnt}?SB-leX^%ug{5y-k5ZV; z#y;VXdAMYIeRIC@;qWkdIMTRpY|7_sQ7oO@PjXm32N(WY!~hoi93lLP_WiFRw*$;_ znS_gLKSxgUR$1^px@Jgk$A{*tht~g0gg8FY=0U@6ojk_X*op ztAMVmg<|%PhVi1Q*J%Cu>>AZ#jx2|^owl6|2`-=_Z3hHU9%#=_S|_GaC*VOqhCGj=p4Z|LL; zRqd?4Q#&SH-Lxno1~X<2TiIAXBn1^4*&#=+2oDjC&RE#=iZ;a>pFJJjFSm1-VsA~f zE_$t<^>F2meG4hTz{~MlP^5O2|8<+Exvg5+E!g1o8bV_L>8Qj($bwPI+# zVf1v^;i^T|n9>z1m9!Q#-{RAv1~Tpus1_6#$y)6NU3uptT`9rn?vBtjw$cx^tGr+7 zr$LN{gGr#8R&eGxD8!ivA}YP(k>y-%6D~vSALMW^9dc{_!V6D# zfF*gxZ-d|U@X5ulDJ0Y=CCe7ja~C{82^R*T^*n8+-ff1Ua|_aYOl^82Ny%R6sxMIQ z#B%!{9#eQI=G?SdLTsR`JW4o!zgEeq?=b4)s~&`-;#Z3bo^x z(B4@&UY)(d{V&z~Ukfqo+0XUip^cpI~IP91=QzhI_dCMG~W2UCab`+W;fvls()OPyEjG*#2;Q;aYgj+~k zX2gKUwRqRZ6V2SWxDh(Z_LFlAZs-gf7<}>L8N24|4onE0@+bheNUmU}&U{?fMa=g@ z@ZjRxMi;CH-ZUjL(}xE(>60~G+eCdF!f`4h+B3lma!q~}{7tcCL$iW8iR&g$EsQ8N zHUoiNaTHN3&jnuvoxktgcPyQ^2TbYg|bM(x-wQ)A8-Oji;% z`lU6)DvDnBCdEqa)~p1_DDG&4v6PjE_I^0!&7RD31&7RPtbdNG^uwvu@CDPqHOXoe zc#aYcLFfpSwnyDO9b7!0!Govg0}C2qw4;ZhNv0Q}Fbr2wUl1H)?`*N^<%d+OJBmp1 zy=>mPp>;A?W=e$><(*_`v|TXG_2ITZ*Qs8fV^yJ)G<@#%Qa8(Jb?ak4_xiyd?aa>L zq=I2Fw8)?XLSILE>8;1RvO|#ki99u77KGMNukgrHB+ytqU$4a%T->s}bR1z`n#l|z zJ1_tXj`31)I)Hf0L<<*KL_!SnhXj}q0~`8aOnR?fKWJ%t4WT1_Npe3t%Mz92AzbCD z4|D9-1*|d?DPg#+H-?Adt($a^&^nG4MKaHP`)N5bhwRe#H8B|<8jyzH87-OiCP;J| zn^dHV--N*<&#H8t8ClZ^IT`$Q`F@J|YJ9tt2s|smpuL0@MLrf-Wd+L_#g~<@xiCtJ z!LwYE>nj?%0lf~W&k^@U{iLo6k9sA!IU?pfFE{h>pH;dYaq-xd*CY4@+awq*eDPq1 zyE>zG&KHW1U&OaO{x^qn8gfO_tF@=eWnHr}@Mjy*LRY7sq>Y|*)rCJuig}WRi8i5h z^`_C#!Yw*>({+!XW7KwR-4Qx}8Rpo+EVL$L3wCzr0!Vl*pR!=qXpcRKC*Z+;eI1wh z8H~`CTW^8Uo%JGRLCr80OtLjsC{rgR8?q*(k5#D9+EwmoJ8=Adc1qdNP4euwMf5Iw{V7c$J(`VW{s@u8p{*a8Nai(Dq?_HsSg;dc3HlY-Bg5U0Ms$ZdcA z8G?$AIPzmjK=k!)Ua+SJ2)#t84}l0%Leh2Y09*11;SB09$6oI>Rt*SMlD2D^AjlDn z=6C>LT*0T7$!TonaOfw4weX?1WOsMh;qi9frKbs#-3s=dBn4q%>8@G9yfKMj`35Y0!?ijj{SB-vx>mRqK=&4x{b8fz26|VcS-WQI@D8sYFcI7qO7RV(5&UX+xsUxXEX5AC> zxpH#wtV{FK(&@9`WwWQGKg+4(&l^ry6qHHI`}69fpHixMcGJh8-8dp}3^7-F%7S!fK2SyiV4HBE0V9qQA9NVLQ7*fjgC5L`)Pp(J zYP*|E4N|H<1jn4Zw(A)h${x?u8IfRwpHc|vK4zVZT@~#c0w@|CfG+d(oDw%>@lPuk z?~&&BdW&u`ftZ6E)-KmuSluuM82#hTy>cO_ci8&$7*$NsFaQgm_0Ehe$z+1g;)Vf` z@3Li8KUmrH1~i=t{P?1(GlcC+`S;2fBQC%ko3X+p`4VT~Ssa2U6~lp&&}{eGs@Jj< zm|6O91u=E9RGC2V&lY(OWVH;mr~(N7KV*+DM(r^nK(kvo2=$TM1khZvMfUneFC?08 z5pfR2jDX+D8g@AVY9*c%THM;xbOCA~CCyMf%9DdSO&8#G?DYW4!Ixnij!NthZ|a{F zEZRT{e#k(aejE^KX3v6?^~(<}FMtwDB*YNl@T;?FS=8N~suMOp}2&hLwe+ zciVtXu@C)Vv?qW#1VX#m+x`p`kdzV`id z6j>JP`*$*+>> zDacwA-6eqY!_UgMq2ABKK|$q=9s~+q0CmpWg%PJR01E5liA3r%qdmBHhv5vHZj3np ze7D1JPmAt4m*sXTYEY)0cG;jz$%jq_f1S_e9;nMh&E?24BT8q7ef$h?B)_spG3Kc$ z(d!;tB#!)Jk?RGMlv~5( zNF2E{*m@W1UIO<$+l<;((sTTxZ`cZVI@TZ&3R z<8~om3`2LZD|5d(G1}ZhuYRmEUi#|e7pJmL2onoMi8qC4*v`98^1YL{ME<*{;lRO zo(Q}JXA!D@r@emyloc?*I+ol z0nhupP~HE`iZ{zqN#@xZ7m?v%S4=?G*sHQnaQu(P^#3#vZ+a7%t0z^|q@~>JS|Z#i zqoX_Rs9oEfH|1>^F9tgZs2B#oUZgQZ6|&vlQzmk z)mmA}0Rb7@hQZad{b#mrrB`@m29}AB66EhXrb%#zP%3$Ea1+7PQn|w3eZqC}k(s5*|UV35&>oR3>BBJd|k5uy;2z zRU0QnELpH)KaIHIQK^W_CR9zaY6StwUTNY+oLL%<^I#YgyJZovDb6g(Hex@dBh}ro z`Fb+>7u!Cwr=AMke3yf(PY1qHP_e_OFaF{JAiLB=IkcxsjO-XcV?h5PXbfvmU2T!B zy>^4AO@oUv^F=wy&PS%DP&s<$Np9QEi>-YjyO?KXm}D0zc_pOPv|R$BWeFpDSuiQJ z-V9?heWgW{d4Cn^WuVb&lXBTu)zaX&3$6vPAQ3tpvoJKSi=0;S5usCD$8mFvd?jiMhf(f83^9hdq;8n`` zxmqjPtgFE<(0Hk}UX>*S7NqVI5@2Ta%uz^60M#VE<$)D%|5(ws0usb$mTG{+vS8w? znjZRps3P7q@sjakflD~_Oa|J$W>Mhll|dVnPX|?WRRiuDAH`_08}VeHOwwKZ08Cj( zB>#a!-H~LH&|QSFtQTW@TVX}x3rOvj8U|x|*Wb3cN(RXx1v21FvfCvz%X5DGIp}Vi z&AzuJ?0(UHvOMzomX8mg61ybe@_0uacTOMV{IKR_mb639t_h>ZYZrP`WNzQCT8g`# zt`Whe3G=A2!}K(o&J#sERuoCV&&iY0{J#*eRGpP!*M3_jfX>w6s}XftwcQxw^%w z&b*&NQHA#%IV^CVW&v;}R0y+uMb~3F&xA3}KAkAc>X~0mW!$@qlG5(k7Wnbn&cqh7 z=%!7vXf@eR%m5n26ogH>eJdw+_+5c9cTmse*@Ymh$#?DYdX;J*He8CueD{&oOIl(42R=@=$nHjA zG9y{Q{+q%?@;7|7IUs>})K}kC*}63j2wSaa;Ofh4H1C^O6X zZhQ9AK-CmKy7L_5cpfLwnnvMTXy}f-@6V~ybe?5W;xJl?Kv05~whT>ItHP6Jt z=>>4iVs3}-0qK{3PZ~3Dz_8RL&!oHoXsbTWHm1L4(=Oc{>s18#B!)@zzg;o#Rz&g& zaO8)qeGh?hGSPxDoZrAd=b~qY3fFo% zF$sGwECZO!zvrcDL1a}+;EIGub*A|j4d+>Vr&K;ea^vL~6C&$qvi2{c5BV98(^tKf z3%0;Tfn(q8BsFjeN3u)rf8mOpS}d_9W{F6ef(RW%St*&oDHgew zV|b&}e&*vsf8%q5Cbmenc34o&4>q%r%M5AL+Ow+>;Hf9x)%S9l9awH|pl1hUb;`SsfuTud*kBE?c40cl|P3+Ir#n4GriIYgf5mQHPU&{g? zoL3%HNH-vzte1%t?gfyK@BE9;=W9DFXYwNYI`IdtYR!FgkG zb6KHI3iJ@(Gl9#yB-40%M;?NJ&sj#KSv=B*rzI?x08Y5i%$~`bJ@Yh6-w?WLZ=P-E zlx=71)%c?yt~ANfFHnvoEht}pjSf6+CL6}oR|uL&Wyr$C<<{@VLFl!<98+lX4h2E` z;-O*nTqzaWOZZqQ+qO8>UC;A6{Ao>ATX5mR)Xf*=uqwN$8WjZSC|>i;N#!`;I=}sJ K`FzBU2mcAw%10pp literal 0 HcmV?d00001 diff --git a/media/mx-id-icon.png b/media/mx-id-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b279610748c8fc95bbbc198a69b1fe65c618ef73 GIT binary patch literal 5817 zcmd5=cT|(vwhu*(A{s6-2#63-id3nBG(kX==AdGvsgO_<@Ft9q1cOKfrAm212oN2{ zaus1DC}ar5j7m!p0g)m_NP-FogkJCY;`{gAyVhHG-Epmxl~eZFzu(?xpL6ziZh3jQ z$Zk>I0)xS1U5_6583vOgLqE7Q=<#;3_#OO7UO4V@1h$@>$*u>j&6kg&a4?vRJoJ-* z6%_6SotvUv-JLcKZQcksl~PD99e}}pI_7%h@X6So@sGsYMdh{lJE8}}K93>QGS+vt+1nggzEu%_1jaawS z*NO3hd_9xxW6>F$>49aO?T(r~(lF$Irr5(Je%OL=fc@nq)>*?1=6L?N`M22KY2WyP z>5k06`dRexrQ)TFflKdbbu$m`d})N<;s~WCdqVthMDUxC3z}oG)Lfew=J*8y!a@7n z#3k+n)hH@W@NIp0IDXCQZA1C5cB{OFdP=ShJq8ovfeE>SdC*vtj3~Hv@6?3UR0E<; zQDjA*AI4kH+s;On7;A~JM+N-Bv{|YgQaYTjqf0mT*{5`;+j6f9e{|RAe$e}cZZj`j zi?eMW+4VmqqU&t=BV@#*wjRH{ui0+A)ag%pO*?ab&k@aR9t?ZhtW}|%LpM_61PScn z304u!?)V@~@Oj&u+3#(R`{EO~R;9&EX{@bQaG2*TlRs#noq3gp*RcciJ)0I`uZYW5$_{+V zn`V{%-9??;;H6x=$T&DJEMsaAw=X?i*OsC87lEtJF)F~I`SD7Khv&Du{Sz%r<6cfC zLNelQ&Tz$EyXNJJM)m$L@0jf~aOB(FHA0qkzV61U)-(OIi|h;_R&hKaZyD!4r}|s( zp+hmssz~XcIf+jSizM58oy5z&RJwM(k);E?(mjgX`k1-zPnG)nF4N&S4YIvTIuFe- zNj4`PI*wyJlXJLRB;fZltZU5UbQMSH-O_>b)yq?Y(k7Z2LFKBY##eEdnMI5sJ-wgx znAyn&i~?!|wCT^zYI|ypEvQNWe9Tak&JLYi>B1yt0iB#}tuffkK@ZEh8#JBa30J7a ze9Onr+U*K_-O~_~U{%hGHo7?rx=JJ43O?pcn7V8)?K#eyy0<8U`$~aT9MXM8~ zW>E8^0mD|LV!3#W*~$dqq!OGX#sWLtNPj18)}X_#Tpa9+D0T+B;iHjOVAIWw+}Z;% zI~9@Ae+*Xpy=!8mx~GDn$ve))r>ga;@_zyYceQ-b7%AaW&j~VO^k(y-8zF^8NcJfK zaa?v#vpj0Bqjf8o= zPbjE;Hi_T83uyPSG$%4NK^I|mq?Dx<5OosZXVbUTV^(YNufER_j|D-^=JcBW8A_HrUHF&y&e)+{0C!mpl(+fnqfv^ z8X%4PWKxRff+tLLz=@#Zuss9V(zwfz4E8%+r@MX>Uu+MSL=_j@)=tV#7c0V(+S*?T ziBO$T4j#7t$xMu1x2I4ZT1q$Z*2J$J87PTW%tzkxtXTRj*HnI(e?839FB z#efW^8mMnjKmt0S749JbM5hnA7Mc>-?Ke|-KwMiq)zq(SOBR9I3Feg6vNfFYs(C8Rdee|sdTBgGJ?fCVQ0d+qd0 zr4A(wij55S8}%pO%&G%AE#sf%f$JW1Rj`DcAqTX)V+mCw#Zt*Q&o&WeVJ;llAetSF z%Mq`%b<1ziE>*#%LOy@gB?}gWxtVei+It!C`f>ePwwI9L7_g>Lx6oqKpzU_3?&Jco zWv`YpwDE^=H+a+`6X2M)s!vklanEl!1JV=K#!`@-&wC1rZlsD&oUr#hY^QFz znL?+>>rJV&*a}FW?nFyhDh>Y!3aT6El0MxFAZ-S!KMm>WkOZw9D6Sd1`_8VoZBGN7 zT~V6-K=;>bLm(cAHsXLvN$KnAEa^{UvcvsP|pDIEd1?#j@{=?uS~ zQT?&5rYC)g2+0jI{8Ni@B~?axG`2w!xF5|4dmdu&e^&iHAj8-+ku8KL*fqt7`reov z)KXMY256&?cTqN})TIwGtV+c9F&!x!>Fn-X7%!v=m8i%;9Jyirs|xC^jiA z**Rfg%8T1f-*h0cQMjjMI0X@!?1FW*Ev`Gve7!1)T1y4#Q_tWbhIQA2c&{kje4JU1 z8%SFmF9y{WX7XPguK##~fn3g4LrN!c?ISsz4D+wagD;Z#ILkhNDCr6Ej^xUs3d|hm zbgQjX`()urqYvgGPLZ6ai_YeNZ@)l(Sn(*?J55kGh0J6orV5 zsiW%a$Jz~0W%{~7{uh7MWMCydUWFD$xuesqpJS{8^2cFg+Y*!gCC2+T+Nu3BnAjxr z8N$1qx|#cSSG)6{5l_`Fm$6Y$a%+{|tQd!j0DHM}V)58EGb#NM;z!BELCeKETS3%4<+4?~d!bCX zq(4rZ#tC(Nd)C$kZW>CsGoo!Wn1XoJ-i*?LBKhI7#k{`TA7u}J#fR<)ZCHux$!ipk zk2387^A7vTN&aHBPpySk0P^whN>AHDMM^@()LU(@S7msgi5#&2Nge-7e~wZT;I%X} z!pBEpRG^eU`ho%18p_kJ5^&D_cM`>)2TFNe0uBWYW$L^!ra?JJ8p`e?ocSGvIYD9x z%Yz|8L*q8+^MIO&J?p`f5GyW|^lbf134$iJl2`B^O)~U69gx(QZQqnEATy1UL#hve z=5B8ha~DMed&XG>EZmpofj%||X~~-340a~rQ&rTqCeU%1g4u>}_|Ux`>&clJ%c`aG zGOC_AOCZTbLz&|@(~f@2sGq}JdV`LGN{F9kaQFi>;Q{&>A#H=h(O%hD!)r)0w)7AB z+5z5R{u{3y|BpA74*{+1-Xr6Cyd5MI6_(@~eYyXZDCY}!Bawua{iv@PfBE~x|3% Date: Wed, 6 Sep 2017 15:00:43 +0200 Subject: [PATCH 03/28] Working prototype --- application.example.yaml | 6 + .../io/kamax/mxisd/config/ServerConfig.groovy | 38 ++++++ .../controller/v1/InvitationController.groovy | 6 +- .../mxisd/controller/v1/KeyController.groovy | 13 +- .../v1/io/ThreePidInviteReplyIO.java | 31 ++++- .../mxisd/invitation/InvitationManager.java | 112 ++++++++---------- .../mxisd/signature/SignatureManager.groovy | 13 ++ 7 files changed, 147 insertions(+), 72 deletions(-) diff --git a/application.example.yaml b/application.example.yaml index 9538ebe..c57656c 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -12,6 +12,12 @@ server: # e.g. domain name in e-mails. name: 'example.org' + # Public URL to reach this identity server + # + # This is used with 3PID invites in room and other Homeserver key verification workflow. + # If left unconfigured, it will be generated from the server name + # publicUrl: 'https://example.org' + key: diff --git a/src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy index 3b14e96..d29b4ce 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy +++ b/src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy @@ -22,6 +22,8 @@ package io.kamax.mxisd.config import io.kamax.mxisd.exception.ConfigurationException import org.apache.commons.lang.StringUtils +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.beans.factory.InitializingBean import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Configuration @@ -30,7 +32,11 @@ import org.springframework.context.annotation.Configuration @ConfigurationProperties(prefix = "server") class ServerConfig implements InitializingBean { + private Logger log = LoggerFactory.getLogger(ServerConfig.class); + private String name + private int port + private String publicUrl String getName() { return name @@ -40,11 +46,43 @@ class ServerConfig implements InitializingBean { this.name = name } + int getPort() { + return port + } + + void setPort(int port) { + this.port = port + } + + String getPublicUrl() { + return publicUrl + } + + void setPublicUrl(String publicUrl) { + this.publicUrl = publicUrl + } + @Override void afterPropertiesSet() throws Exception { if (StringUtils.isBlank(getName())) { throw new ConfigurationException("server.name") } + + if (StringUtils.isBlank(getPublicUrl())) { + log.warn("Public URL is empty, generating from name {}", getName()) + publicUrl = "https://${getName()}" + } + + try { + new URL(getPublicUrl()) + } catch (MalformedURLException e) { + log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "")) + } + + log.info("--- Server config ---") + log.info("Name: {}", getName()) + log.info("Port: {}", getPort()) + log.info("Public URL: {}", getPublicUrl()) } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy index d2ccc34..ee40c95 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy @@ -22,6 +22,7 @@ package io.kamax.mxisd.controller.v1 import com.google.gson.Gson import io.kamax.matrix.MatrixID +import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO import io.kamax.mxisd.invitation.IThreePidInvite import io.kamax.mxisd.invitation.IThreePidInviteReply @@ -50,6 +51,9 @@ class InvitationController { @Autowired private KeyManager keyMgr + @Autowired + private ServerConfig srvCfg + private Gson gson = new Gson() @RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST) @@ -62,7 +66,7 @@ class InvitationController { IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId) IThreePidInviteReply reply = mgr.storeInvite(invite) - return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()))) + return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl())) } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy index f2e0a9e..f74b5bf 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy @@ -24,11 +24,13 @@ import groovy.json.JsonOutput import io.kamax.mxisd.exception.BadRequestException import io.kamax.mxisd.exception.NotImplementedException import io.kamax.mxisd.key.KeyManager +import org.apache.commons.lang.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import javax.servlet.http.HttpServletRequest @@ -49,6 +51,7 @@ class KeyController { throw new BadRequestException("Invalid algorithm: " + keyType) } + log.info("Key {}:{} was requested", keyType, keyId) return JsonOutput.toJson([ public_key: keyMgr.getPublicKeyBase64(keyId) ]) @@ -62,10 +65,14 @@ class KeyController { } @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET) - String checkKeyValidity(HttpServletRequest request) { - log.error("{} was requested but not implemented", request.getRequestURL()) + String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) { + log.info("Validating public key {}", pubKey) - throw new NotImplementedException() + // TODO do in manager + boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex())) + return JsonOutput.toJson( + valid: valid + ) } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java index 8967ecb..53c6a34 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java @@ -2,8 +2,7 @@ package io.kamax.mxisd.controller.v1.io; import io.kamax.mxisd.invitation.IThreePidInviteReply; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; public class ThreePidInviteReplyIO { @@ -12,20 +11,40 @@ public class ThreePidInviteReplyIO { private List public_keys; private String display_name; - public ThreePidInviteReplyIO(IThreePidInviteReply reply, String pubKey) { + public ThreePidInviteReplyIO(IThreePidInviteReply reply, String pubKey, String publicUrl) { this.token = reply.getToken(); - this.public_keys = new ArrayList<>(Arrays.asList(new Key(pubKey))); + this.public_keys = Collections.singletonList(new Key(pubKey, publicUrl)); this.display_name = reply.getDisplayName(); } + public String getToken() { + return token; + } + + public List getPublic_keys() { + return public_keys; + } + + public String getDisplay_name() { + return display_name; + } + public class Key { private String key_validity_url; private String public_key; - public Key(String key) { - this.key_validity_url = "https://example.org/_matrix/fixme"; // FIXME have a proper URL even if synapse does not check + public Key(String key, String publicUrl) { + this.key_validity_url = publicUrl + "/_matrix/identity/api/v1/pubkey/isvalid"; this.public_key = key; } + + public String getKey_validity_url() { + return key_validity_url; + } + + public String getPublic_key() { + return public_key; + } } } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 7f994d0..3d54558 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -12,17 +12,14 @@ import io.kamax.mxisd.signature.SignatureManager; import org.apache.commons.lang.RandomStringUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; +import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,8 +35,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -65,40 +60,12 @@ public class InvitationManager { private void postConstruct() { gson = new Gson(); + // FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver try { - HttpClientBuilder b = HttpClientBuilder.create(); - - // setup a Trust Strategy that allows all certificates. - // - SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { - public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { - return true; - } - }).build(); - b.setSslcontext(sslContext); - - // don't check Hostnames, either. - // -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken - HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; - - // here's the special part: - // -- need to create an SSL Socket Factory, to use our weakened "trust strategy"; - // -- and create a Registry, to register it. - // + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + HostnameVerifier hostnameVerifier = new NoopHostnameVerifier(); SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build(); - - // now, we create connection-manager using our Registry. - // -- allows multi-threaded use - PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - b.setConnectionManager(connMgr); - - // finally, build the HttpClient; - // -- done! - client = b.build(); + client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build(); } catch (Exception e) { // FIXME do better... throw new RuntimeException(e); @@ -196,31 +163,52 @@ public class InvitationManager { if (!hsUrlOpt.isPresent()) { log.warn("No HS found for domain {} - ignoring publishing", domain); } else { - HttpPost req = new HttpPost(hsUrlOpt.get() + "/_matrix/federation/v1/3pid/onbind"); - JSONObject obj = new JSONObject(); // TODO use Gson instead - obj.put("mxisd", threePid.getMxid()); - obj.put("token", reply.getToken()); - String mapping = gson.toJson(signMgr.signMessage(obj.toString())); // FIXME we shouldn't need to be doign this + // TODO this is needed as this will block if called during authentication cycle due to synapse implementation + new Thread(() -> { // FIXME need to make this retryable and within a general background working pool + HttpPost req = new HttpPost(hsUrlOpt.get() + "/_matrix/federation/v1/3pid/onbind"); + // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr + JSONObject obj = new JSONObject(); // TODO use Gson instead + obj.put("mxid", threePid.getMxid()); + obj.put("token", reply.getToken()); + obj.put("signatures", signMgr.signMessageJson(obj.toString())); - JSONObject content = new JSONObject(); // TODO use Gson instead - content.put("invites", Collections.singletonList(mapping)); - content.put("medium", threePid.getMedium()); - content.put("address", threePid.getValue()); - content.put("mxid", threePid.getMxid()); + JSONObject objUp = new JSONObject(); + objUp.put("mxid", threePid.getMxid()); + objUp.put("medium", threePid.getMedium()); + objUp.put("address", threePid.getValue()); + objUp.put("sender", reply.getInvite().getSender().getId()); + objUp.put("room_id", reply.getInvite().getRoomId()); + objUp.put("signed", obj); - StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); - entity.setContentType("application/json"); - req.setEntity(entity); - try { - log.info("Posting onBind event to {}", req.getURI()); - CloseableHttpResponse response = client.execute(req); - response.close(); - } catch (IOException e) { - log.warn("Unable to tell HS {} about invite being mapped", domain, e); - } + String mapping = gson.toJson(objUp); // FIXME we shouldn't need to be doign this + + JSONObject content = new JSONObject(); // TODO use Gson instead + JSONArray invites = new JSONArray(); + invites.put(objUp); + content.put("invites", invites); + content.put("medium", threePid.getMedium()); + content.put("address", threePid.getValue()); + content.put("mxid", threePid.getMxid()); + + content.put("signatures", signMgr.signMessageJson(content.toString())); + + log.info("Will send following JSON to {}: {}", domain, content.toString()); + StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); + entity.setContentType("application/json"); + req.setEntity(entity); + try { + log.info("Posting onBind event to {}", req.getURI()); + CloseableHttpResponse response = client.execute(req); + log.info("Answer code: {}", response.getStatusLine().getStatusCode()); + response.close(); + } catch (IOException e) { + log.warn("Unable to tell HS {} about invite being mapped", domain, e); + } + invitations.remove(key); + }).start(); } - invitations.remove(key); + } } diff --git a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy index 8eece94..72dede2 100644 --- a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy +++ b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy @@ -23,6 +23,7 @@ package io.kamax.mxisd.signature import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.key.KeyManager import net.i2p.crypto.eddsa.EdDSAEngine +import org.json.JSONObject import org.springframework.beans.factory.InitializingBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -51,6 +52,18 @@ class SignatureManager implements InitializingBean { ] } + JSONObject signMessageJson(String message) { + byte[] signRaw = signEngine.signOneShot(message.getBytes()) + String sign = Base64.getEncoder().encodeToString(signRaw) + + JSONObject keySignature = new JSONObject() + keySignature.put("ed25519:${keyMgr.getCurrentIndex()}", sign) + JSONObject signature = new JSONObject() + signature.put("${srvCfg.getName()}", keySignature) + + return signature + } + @Override void afterPropertiesSet() throws Exception { signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm())) From 8294990b1f0ae447d8829146cd22075690932180 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 6 Sep 2017 15:04:17 +0200 Subject: [PATCH 04/28] Cosmetic changes - Add missing license headers - Remove unused class --- .../groovy/io/kamax/mxisd/GlobalProvider.java | 7 ------- src/main/groovy/io/kamax/mxisd/ThreePid.java | 20 +++++++++++++++++++ .../io/kamax/mxisd/auth/AuthManager.java | 20 +++++++++++++++++++ .../io/kamax/mxisd/auth/UserAuthResult.java | 20 +++++++++++++++++++ .../auth/provider/AuthenticatorProvider.java | 20 +++++++++++++++++++ .../GoogleFirebaseAuthenticator.groovy | 20 +++++++++++++++++++ .../mxisd/auth/provider/LdapAuthProvider.java | 20 +++++++++++++++++++ .../io/kamax/mxisd/config/FirebaseConfig.java | 20 +++++++++++++++++++ .../config/RecursiveLookupBridgeConfig.groovy | 20 +++++++++++++++++++ .../invite/sender/EmailSenderConfig.java | 20 +++++++++++++++++++ .../config/ldap/LdapAttributeConfig.java | 20 +++++++++++++++++++ .../config/ldap/LdapAttributeUidConfig.java | 20 +++++++++++++++++++ .../mxisd/config/ldap/LdapAuthConfig.java | 20 +++++++++++++++++++ .../config/ldap/LdapConnectionConfig.java | 20 +++++++++++++++++++ .../mxisd/config/ldap/LdapIdentityConfig.java | 20 +++++++++++++++++++ .../v1/DefaultExceptionHandler.java | 20 +++++++++++++++++++ .../mxisd/controller/v1/StatusController.java | 20 +++++++++++++++++++ .../v1/io/GenericTokenRequestJson.java | 20 +++++++++++++++++++ .../v1/io/SessionEmailTokenRequestJson.java | 20 +++++++++++++++++++ .../v1/io/SessionPhoneTokenRequestJson.java | 20 +++++++++++++++++++ .../v1/io/ThreePidInviteReplyIO.java | 20 +++++++++++++++++++ .../exception/ConfigurationException.java | 20 +++++++++++++++++++ .../MappingAlreadyExistsException.java | 20 +++++++++++++++++++ .../mxisd/invitation/IThreePidInvite.java | 20 +++++++++++++++++++ .../invitation/IThreePidInviteReply.java | 20 +++++++++++++++++++ .../mxisd/invitation/InvitationManager.java | 20 +++++++++++++++++++ .../mxisd/invitation/ThreePidInvite.java | 20 +++++++++++++++++++ .../mxisd/invitation/ThreePidInviteReply.java | 20 +++++++++++++++++++ .../invitation/sender/EmailInviteSender.java | 20 +++++++++++++++++++ .../invitation/sender/IInviteSender.java | 20 +++++++++++++++++++ .../mxisd/lookup/ThreePidValidation.java | 20 +++++++++++++++++++ .../provider/GoogleFirebaseProvider.groovy | 20 +++++++++++++++++++ .../kamax/mxisd/mapping/MappingManager.java | 20 +++++++++++++++++++ .../kamax/mxisd/mapping/MappingSession.java | 20 +++++++++++++++++++ .../spring/ConfigurationFailureAnalyzer.java | 20 +++++++++++++++++++ 35 files changed, 680 insertions(+), 7 deletions(-) delete mode 100644 src/main/groovy/io/kamax/mxisd/GlobalProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/GlobalProvider.java b/src/main/groovy/io/kamax/mxisd/GlobalProvider.java deleted file mode 100644 index 9e68b5d..0000000 --- a/src/main/groovy/io/kamax/mxisd/GlobalProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.kamax.mxisd; - -import io.kamax.mxisd.auth.provider.AuthenticatorProvider; -import io.kamax.mxisd.lookup.provider.IThreePidProvider; - -public interface GlobalProvider extends AuthenticatorProvider, IThreePidProvider { -} diff --git a/src/main/groovy/io/kamax/mxisd/ThreePid.java b/src/main/groovy/io/kamax/mxisd/ThreePid.java index d5585e9..04e685a 100644 --- a/src/main/groovy/io/kamax/mxisd/ThreePid.java +++ b/src/main/groovy/io/kamax/mxisd/ThreePid.java @@ -1,3 +1,23 @@ +/* + * 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; // FIXME this should be in matrix-java-sdk diff --git a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java b/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java index bb6e367..dd14f0b 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java +++ b/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java @@ -1,3 +1,23 @@ +/* + * 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.auth; import io.kamax.mxisd.auth.provider.AuthenticatorProvider; diff --git a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java b/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java index 16e1cf6..efa278c 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java +++ b/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java @@ -1,3 +1,23 @@ +/* + * 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.auth; public class UserAuthResult { diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java b/src/main/groovy/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java index bdcf72a..f9aefb6 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java @@ -1,3 +1,23 @@ +/* + * 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.auth.provider; import io.kamax.mxisd.auth.UserAuthResult; diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy b/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy index 2d78013..94ff111 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy @@ -1,3 +1,23 @@ +/* + * 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.auth.provider import com.google.firebase.FirebaseApp diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java b/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java index ff2ddc4..6edeaa6 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java @@ -1,3 +1,23 @@ +/* + * 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.auth.provider; import io.kamax.matrix.MatrixID; diff --git a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java b/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java index 3f65e42..9480347 100644 --- a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config; import io.kamax.mxisd.auth.provider.AuthenticatorProvider; diff --git a/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy index a51e12f..9830376 100644 --- a/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy +++ b/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy @@ -1,3 +1,23 @@ +/* + * 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.config import org.slf4j.Logger diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java index 555b2fc..4a4d962 100644 --- a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config.invite.sender; import io.kamax.mxisd.exception.ConfigurationException; diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java index f8139ee..9ef4324 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config.ldap; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java index a56044a..67630de 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config.ldap; import io.kamax.mxisd.lookup.provider.LdapProvider; diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java index 011500e..ffea425 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config.ldap; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java index 7f85e5d..a4fa419 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config.ldap; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java index c6839e4..749530f 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java @@ -1,3 +1,23 @@ +/* + * 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.config.ldap; import org.springframework.boot.context.properties.ConfigurationProperties; 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 91a1d72..9922370 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java @@ -1,3 +1,23 @@ +/* + * 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.controller.v1; import io.kamax.mxisd.exception.BadRequestException; diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/StatusController.java b/src/main/groovy/io/kamax/mxisd/controller/v1/StatusController.java index 516ff03..26ba03d 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/StatusController.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/StatusController.java @@ -1,3 +1,23 @@ +/* + * 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.controller.v1; import org.springframework.http.MediaType; diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java index 4304460..f7d1b35 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java @@ -1,3 +1,23 @@ +/* + * 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.controller.v1.io; import io.kamax.mxisd.mapping.MappingSession; diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java index e3c7f65..3901045 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java @@ -1,3 +1,23 @@ +/* + * 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.controller.v1.io; public class SessionEmailTokenRequestJson extends GenericTokenRequestJson { diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java index ebf728f..b66e881 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java @@ -1,3 +1,23 @@ +/* + * 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.controller.v1.io; import com.google.i18n.phonenumbers.NumberParseException; diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java index 53c6a34..7e88e2d 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java @@ -1,3 +1,23 @@ +/* + * 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.controller.v1.io; import io.kamax.mxisd.invitation.IThreePidInviteReply; diff --git a/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java b/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java index 8a2d158..0e1b20b 100644 --- a/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java +++ b/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java @@ -1,3 +1,23 @@ +/* + * 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 java.util.Optional; diff --git a/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java b/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java index 96bb741..d722182 100644 --- a/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java +++ b/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java @@ -1,3 +1,23 @@ +/* + * 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 MappingAlreadyExistsException extends RuntimeException { diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java index 48ab6d3..3b7ff47 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java @@ -1,3 +1,23 @@ +/* + * 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.invitation; import io.kamax.matrix._MatrixID; diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java index e9a6f3a..cbe351f 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java @@ -1,3 +1,23 @@ +/* + * 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.invitation; public interface IThreePidInviteReply { diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 3d54558..30db078 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -1,3 +1,23 @@ +/* + * 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.invitation; import com.google.gson.Gson; diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java index 38018cd..acfb90f 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java @@ -1,3 +1,23 @@ +/* + * 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.invitation; import io.kamax.matrix._MatrixID; diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java index 296bd25..5a02c2e 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java @@ -1,3 +1,23 @@ +/* + * 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.invitation; public class ThreePidInviteReply implements IThreePidInviteReply { diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java index 9699143..72045f0 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java @@ -1,3 +1,23 @@ +/* + * 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.invitation.sender; import com.sun.mail.smtp.SMTPTransport; diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java index 4ad7ccf..14af7fd 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/IInviteSender.java @@ -1,3 +1,23 @@ +/* + * 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.invitation.sender; import io.kamax.mxisd.invitation.IThreePidInviteReply; diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java index cb78967..8d3b4a0 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java +++ b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java @@ -1,3 +1,23 @@ +/* + * 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.lookup; import io.kamax.mxisd.ThreePid; diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy index 8b0bf82..30e89a7 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy @@ -1,3 +1,23 @@ +/* + * 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.lookup.provider import com.google.firebase.FirebaseApp diff --git a/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java b/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java index c631a81..2e7d879 100644 --- a/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java +++ b/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java @@ -1,3 +1,23 @@ +/* + * 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.mapping; import io.kamax.mxisd.exception.BadRequestException; diff --git a/src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java b/src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java index cf5d6a7..7d647d3 100644 --- a/src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java +++ b/src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java @@ -1,3 +1,23 @@ +/* + * 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.mapping; public interface MappingSession { diff --git a/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java b/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java index e0b7c78..97fabf2 100644 --- a/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java +++ b/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java @@ -1,3 +1,23 @@ +/* + * 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.spring; import io.kamax.mxisd.exception.ConfigurationException; From 1e3b832186b97576bdd7abfc603bd312b57bf487 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 6 Sep 2017 15:56:45 +0200 Subject: [PATCH 05/28] Fix example data --- application.example.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.example.yaml b/application.example.yaml index c57656c..ca027bd 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -209,7 +209,7 @@ invite: email: # SMTP host - host: "smtp.kamax.io" + host: "smtp.example.org" # SMTP port port: 587 From 158060a3b0c877f399cdd5f031337e0e03e6b350 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 6 Sep 2017 23:08:47 +0200 Subject: [PATCH 06/28] Add some placeholders handling for e-mail template --- application.example.yaml | 6 +++++- .../controller/v1/InvitationController.groovy | 6 +++++- .../kamax/mxisd/invitation/IThreePidInvite.java | 4 ++++ .../kamax/mxisd/invitation/InvitationManager.java | 13 +++++++++---- .../io/kamax/mxisd/invitation/ThreePidInvite.java | 15 +++++++++++++++ .../invitation/sender/EmailInviteSender.java | 13 ++++++++++--- 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/application.example.yaml b/application.example.yaml index ca027bd..7d82e2c 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -234,5 +234,9 @@ invite: # The display name used in the e-mail name: "Matrix Identity" - # The MIME content to send + # The MIME content to send, UTF-8 expected + # + # The following placeholders are possible: + # - %SENDER_DISPLAY_NAME% + # - %ROOM_NAME% contentPath: "/absolute/path/to/file" diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy index ee40c95..aadf3ed 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy @@ -63,7 +63,11 @@ class InvitationController { @RequestParam String medium, @RequestParam String address, @RequestParam("room_id") String roomId) { - IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId) + Map parameters = new HashMap<>() + for (String key : request.getParameterMap().keySet()) { + parameters.put(key, request.getParameter(key)); + } + IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters) IThreePidInviteReply reply = mgr.storeInvite(invite) return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl())) diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java index 3b7ff47..62b43dd 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java @@ -22,6 +22,8 @@ package io.kamax.mxisd.invitation; import io.kamax.matrix._MatrixID; +import java.util.Map; + public interface IThreePidInvite { _MatrixID getSender(); @@ -32,4 +34,6 @@ public interface IThreePidInvite { String getRoomId(); + Map getProperties(); + } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 30db078..7f654c5 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -29,6 +29,7 @@ import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.signature.SignatureManager; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.RandomStringUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -184,7 +185,8 @@ public class InvitationManager { log.warn("No HS found for domain {} - ignoring publishing", domain); } else { // TODO this is needed as this will block if called during authentication cycle due to synapse implementation - new Thread(() -> { // FIXME need to make this retryable and within a general background working pool + + new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool HttpPost req = new HttpPost(hsUrlOpt.get() + "/_matrix/federation/v1/3pid/onbind"); // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr JSONObject obj = new JSONObject(); // TODO use Gson instead @@ -200,7 +202,7 @@ public class InvitationManager { objUp.put("room_id", reply.getInvite().getRoomId()); objUp.put("signed", obj); - String mapping = gson.toJson(objUp); // FIXME we shouldn't need to be doign this + String mapping = gson.toJson(objUp); // FIXME we shouldn't need to be doing this JSONObject content = new JSONObject(); // TODO use Gson instead JSONArray invites = new JSONArray(); @@ -212,14 +214,17 @@ public class InvitationManager { content.put("signatures", signMgr.signMessageJson(content.toString())); - log.info("Will send following JSON to {}: {}", domain, content.toString()); StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); entity.setContentType("application/json"); req.setEntity(entity); try { log.info("Posting onBind event to {}", req.getURI()); CloseableHttpResponse response = client.execute(req); - log.info("Answer code: {}", response.getStatusLine().getStatusCode()); + int statusCode = response.getStatusLine().getStatusCode(); + log.info("Answer code: {}", statusCode); + if (statusCode >= 400) { + log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); + } response.close(); } catch (IOException e) { log.warn("Unable to tell HS {} about invite being mapped", domain, e); diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java index acfb90f..1294c87 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java @@ -22,18 +22,28 @@ package io.kamax.mxisd.invitation; import io.kamax.matrix._MatrixID; +import java.util.HashMap; +import java.util.Map; + public class ThreePidInvite implements IThreePidInvite { private _MatrixID sender; private String medium; private String address; private String roomId; + private Map properties; public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId) { this.sender = sender; this.medium = medium; this.address = address; this.roomId = roomId; + this.properties = new HashMap<>(); + } + + public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId, Map properties) { + this(sender, medium, address, roomId); + this.properties = properties; } @Override @@ -56,4 +66,9 @@ public class ThreePidInvite implements IThreePidInvite { return roomId; } + @Override + public Map getProperties() { + return properties; + } + } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java index 72045f0..49f7b9a 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java @@ -25,6 +25,7 @@ import io.kamax.matrix.ThreePidMedium; import io.kamax.mxisd.config.invite.sender.EmailSenderConfig; import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.invitation.IThreePidInviteReply; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +40,7 @@ import javax.mail.internet.MimeMessage; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.Date; @Component @@ -75,11 +77,16 @@ public class EmailInviteSender implements IInviteSender { } try { - MimeMessage msg = new MimeMessage(session, new FileInputStream(cfg.getContentPath())); - msg.setHeader("X-Mailer", "mxisd"); + String templateBody = IOUtils.toString(new FileInputStream(cfg.getContentPath()), StandardCharsets.UTF_8); + templateBody = + templateBody.replace("%SENDER_DISPLAY_NAME%", invite.getInvite().getProperties().get("sender_display_name")) + .replace("%ROOM_NAME%", invite.getInvite().getProperties().get("room_name")); + + MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8)); + msg.setHeader("X-Mailer", "mxisd"); // TODO set version msg.setSentDate(new Date()); - msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); msg.setFrom(sender); + msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); From 45a81e5979a8ed90516081e8c8bd5c6d59ac2264 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 00:07:49 +0200 Subject: [PATCH 07/28] Add sane defaults --- .gitignore | 2 +- .../invite/sender/EmailSenderConfig.java | 20 +++++------ .../config/ldap/LdapAttributeUidConfig.java | 11 ------ .../kamax/mxisd/config/ldap/LdapConfig.groovy | 5 +++ src/main/resources/application.yaml | 35 +++++++++++++++++++ 5 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 src/main/resources/application.yaml diff --git a/.gitignore b/.gitignore index 59aba00..5a642a0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ out/ .idea/ # Local dev config -application.yaml +/application.yaml diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java index 4a4d962..886f062 100644 --- a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java @@ -111,15 +111,6 @@ public class EmailSenderConfig { @PostConstruct private void postConstruct() { - if (StringUtils.isBlank(getContentPath())) { - throw new ConfigurationException("invite.sender.email.contentPath"); - } - - File cp = new File(getContentPath()).getAbsoluteFile(); - if (!cp.exists() || !cp.isFile() || !cp.canRead()) { - throw new ConfigurationException("invite.sender.email.contentPath", getContentPath() + " does not exist, is not a file or cannot be read"); - } - log.info("--- E-mail Invite Sender config ---"); log.info("Host: {}", getHost()); log.info("Port: {}", getPort()); @@ -127,7 +118,16 @@ public class EmailSenderConfig { log.info("Login: {}", getLogin()); log.info("Has password: {}", StringUtils.isBlank(getPassword())); log.info("E-mail: {}", getEmail()); - log.info("Content path: {}", cp.getAbsolutePath()); + if (StringUtils.isBlank(getContentPath())) { + log.warn("invite.sender.contentPath is empty! Will not send invites"); + } else { + File cp = new File(getContentPath()).getAbsoluteFile(); + if (!cp.exists() || !cp.isFile() || !cp.canRead()) { + throw new ConfigurationException("invite.sender.email.contentPath", getContentPath() + " does not exist, is not a file or cannot be read"); + } else { + log.info("Content path: {}", cp.getAbsolutePath()); + } + } } } diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java index 67630de..57547b8 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java @@ -20,13 +20,9 @@ package io.kamax.mxisd.config.ldap; -import io.kamax.mxisd.lookup.provider.LdapProvider; -import org.apache.commons.lang.StringUtils; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; -import javax.annotation.PostConstruct; - @Configuration @ConfigurationProperties(prefix = "ldap.attribute.uid") public class LdapAttributeUidConfig { @@ -50,11 +46,4 @@ public class LdapAttributeUidConfig { this.value = value; } - @PostConstruct - public void postConstruct() { - if (!StringUtils.equals(LdapProvider.UID, getType()) && !StringUtils.equals(LdapProvider.MATRIX_ID, getType())) { - throw new IllegalArgumentException("Unsupported LDAP UID type: " + getType()); - } - } - } diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy index 55fd3d7..946c287 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy @@ -21,6 +21,7 @@ package io.kamax.mxisd.config.ldap import groovy.json.JsonOutput +import io.kamax.mxisd.lookup.provider.LdapProvider import org.apache.commons.lang.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -110,6 +111,10 @@ class LdapConfig { throw new IllegalStateException("Attribute UID value cannot be empty") } + String uidType = attribute.getUid().getType(); + if (!StringUtils.equals(LdapProvider.UID, uidType) && !StringUtils.equals(LdapProvider.MATRIX_ID, uidType)) { + throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType) + } log.info("Conn: {}", JsonOutput.toJson(conn)) log.info("Host: {}", conn.getHost()) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..a394e42 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,35 @@ +# -- Default values for important properties -- + +server: + port: 8090 + +lookup: + recursive: + enabled: true + allowedCidr: + - '127.0.0.0/8' + - '10.0.0.0/8' + - '172.16.0.0/12' + - '192.168.0.0/16' + - '::1/128' + + bridge: + enabled: false + recursiveOnly: true + +ldap: + enabled: false + +forward: + servers: + - "https://matrix.org" + - "https://vector.im" + +firebase: + enabled: false + +invite: + sender: + email: + tls: 1 + name: "mxisd Identity Server" From e2a097b2d0baafcd8e2046ca5b3a125bdc5c7e56 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 01:16:43 +0200 Subject: [PATCH 08/28] Avoid header that could be considered as a profile --- src/main/resources/application.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index a394e42..8c78f35 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,5 +1,3 @@ -# -- Default values for important properties -- - server: port: 8090 From d2d5f80b445d46b6d640b4c936dd6a7d1e21a7b6 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 01:21:49 +0200 Subject: [PATCH 09/28] Don't fail if template is not found and hope for the best --- .../kamax/mxisd/config/invite/sender/EmailSenderConfig.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java index 886f062..80f91ad 100644 --- a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java @@ -20,7 +20,6 @@ package io.kamax.mxisd.config.invite.sender; -import io.kamax.mxisd.exception.ConfigurationException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -122,10 +121,9 @@ public class EmailSenderConfig { log.warn("invite.sender.contentPath is empty! Will not send invites"); } else { File cp = new File(getContentPath()).getAbsoluteFile(); + log.info("Content path: {}", cp.getAbsolutePath()); if (!cp.exists() || !cp.isFile() || !cp.canRead()) { - throw new ConfigurationException("invite.sender.email.contentPath", getContentPath() + " does not exist, is not a file or cannot be read"); - } else { - log.info("Content path: {}", cp.getAbsolutePath()); + log.warn(getContentPath() + " does not exist, is not a file or cannot be read"); } } } From ba723f8523bfcf6b316014981e54466cd7fca847 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 01:53:42 +0200 Subject: [PATCH 10/28] Don't fail on non-existing config key --- .../lookup/strategy/RecursivePriorityLookupStrategy.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy index 0aa7d1b..d8e0607 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy @@ -129,7 +129,11 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea } } - if (recursiveCfg.getBridge().getEnabled() && (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))) { + if ( + recursiveCfg.getBridge() != null && + recursiveCfg.getBridge().getEnabled() && + (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester())) + ) { log.info("Using bridge failover for lookup") return bridge.find(request) } From 2ce3bab3b6a8a9dcaa9dcec94582ee6ed3a74dfc Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 02:04:48 +0200 Subject: [PATCH 11/28] Properly handle force recursive lookup during invite --- .../lookup/strategy/RecursivePriorityLookupStrategy.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy index d8e0607..8cfdd28 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy @@ -99,7 +99,7 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea List listUsableProviders(ALookupRequest request, boolean forceRecursive) { List usableProviders = new ArrayList<>() - boolean canRecurse = isAllowedForRecursive(request.getRequester()) + boolean canRecurse = forceRecursive || isAllowedForRecursive(request.getRequester()) log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse) for (IThreePidProvider provider : providers) { From 3cde0b9c23b5929dd059009a0f5433c4386f42df Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 17:44:37 +0200 Subject: [PATCH 12/28] Fix typo --- application.example.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.example.yaml b/application.example.yaml index 7d82e2c..a4caab4 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -131,7 +131,7 @@ ldap: baseDn: 'CN=Users,DC=example,DC=org' # How to map Matrix attributes with LDAP attributes when performing lookup/auth - attributes: + attribute: # The username/login that will be looked up or used to build Matrix IDs uid: From cb0ffe0575a4a9139b6d4f9531d2d9b4d00c1370 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 7 Sep 2017 17:45:48 +0200 Subject: [PATCH 13/28] Remove debug log --- src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy index 946c287..66ca6e5 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy @@ -116,7 +116,6 @@ class LdapConfig { throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType) } - log.info("Conn: {}", JsonOutput.toJson(conn)) log.info("Host: {}", conn.getHost()) log.info("Port: {}", conn.getPort()) log.info("Bind DN: {}", conn.getBindDn()) From 55b759a31c9c4e015b809ff2a80f40b496e2e13f Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Tue, 12 Sep 2017 02:24:58 +0200 Subject: [PATCH 14/28] Enhance e-mail invitations - Built-in e-mail template - More template placeholders --- application.example.yaml | 24 ++++- .../invite/sender/EmailSenderConfig.java | 28 +++--- .../mxisd/invitation/InvitationManager.java | 2 +- .../invitation/sender/EmailInviteSender.java | 33 ++++++- src/main/resources/application.yaml | 15 ++- src/main/resources/email/invite-template.eml | 91 +++++++++++++++++++ 6 files changed, 168 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/email/invite-template.eml diff --git a/application.example.yaml b/application.example.yaml index a4caab4..0db19e2 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -234,9 +234,25 @@ invite: # The display name used in the e-mail name: "Matrix Identity" - # The MIME content to send, UTF-8 expected + # The E-mail template to use. + # + # The template is expected to be a full e-mail body, including client headers, using MIME and UTF-8 encoding. + # The following headers will be set by mxisd directly and should not be present in the template: + # - From + # - To + # - Date + # - Message-Id + # - X-Mailer # # The following placeholders are possible: - # - %SENDER_DISPLAY_NAME% - # - %ROOM_NAME% - contentPath: "/absolute/path/to/file" + # - %DOMAIN% Domain name as per server.name config item + # - %DOMAIN_PRETTY% Word capitalize version of the domain. e.g. example.org -> Example.org + # - %FROM_EMAIL% Value of this section's email config item + # - %FROM_NAME% Value of this section's name config item + # - %SENDER_ID% Matrix ID of the invitation sender + # - %SENDER_NAME% Display name of the invitation sender, empty if not available + # - %SENDER_NAME_OR_ID% Value of %SENDER_NAME% or, if empty, value of %SENDER_ID% + # - %ROOM_ID% ID of the room where the invitation took place + # - %ROOM_NAME% Name of the room, empty if not available + # - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID% + template: "/absolute/path/to/file" diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java index 80f91ad..d6e12a8 100644 --- a/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/invite/sender/EmailSenderConfig.java @@ -42,7 +42,7 @@ public class EmailSenderConfig { private String password; private String email; private String name; - private String contentPath; + private String template; public String getHost() { return host; @@ -100,12 +100,12 @@ public class EmailSenderConfig { this.name = name; } - public String getContentPath() { - return contentPath; + public String getTemplate() { + return template; } - public void setContentPath(String contentPath) { - this.contentPath = contentPath; + public void setTemplate(String template) { + this.template = template; } @PostConstruct @@ -117,14 +117,18 @@ public class EmailSenderConfig { log.info("Login: {}", getLogin()); log.info("Has password: {}", StringUtils.isBlank(getPassword())); log.info("E-mail: {}", getEmail()); - if (StringUtils.isBlank(getContentPath())) { - log.warn("invite.sender.contentPath is empty! Will not send invites"); - } else { - File cp = new File(getContentPath()).getAbsoluteFile(); - log.info("Content path: {}", cp.getAbsolutePath()); - if (!cp.exists() || !cp.isFile() || !cp.canRead()) { - log.warn(getContentPath() + " does not exist, is not a file or cannot be read"); + if (!StringUtils.startsWith(getTemplate(), "classpath:")) { + if (StringUtils.isBlank(getTemplate())) { + log.warn("invite.sender.template 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"); } } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 7f654c5..7287030 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -137,7 +137,7 @@ public class InvitationManager { ThreePid pid = new ThreePid(invitation.getMedium(), invitation.getAddress()); - log.info("Storing invite for {}:{} from {} in room {}", pid.getMedium(), pid.getAddress(), invitation.getSender(), invitation.getRoomId()); + log.info("Handling invite for {}:{} from {} in room {}", pid.getMedium(), pid.getAddress(), invitation.getSender(), invitation.getRoomId()); if (invitations.containsKey(pid)) { log.info("Invite is already pending for {}:{}, returning data", pid.getMedium(), pid.getAddress()); return invitations.get(pid); diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java index 49f7b9a..f22a1b6 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java @@ -22,13 +22,17 @@ package io.kamax.mxisd.invitation.sender; import com.sun.mail.smtp.SMTPTransport; import io.kamax.matrix.ThreePidMedium; +import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.invite.sender.EmailSenderConfig; import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.invitation.IThreePidInviteReply; 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; import javax.annotation.PostConstruct; @@ -37,7 +41,6 @@ import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; @@ -51,6 +54,12 @@ public class EmailInviteSender implements IInviteSender { @Autowired private EmailSenderConfig cfg; + @Autowired + private ServerConfig srvCfg; + + @Autowired + private ApplicationContext app; + private Session session; private InternetAddress sender; @@ -77,16 +86,30 @@ public class EmailInviteSender implements IInviteSender { } try { - String templateBody = IOUtils.toString(new FileInputStream(cfg.getContentPath()), StandardCharsets.UTF_8); - templateBody = - templateBody.replace("%SENDER_DISPLAY_NAME%", invite.getInvite().getProperties().get("sender_display_name")) - .replace("%ROOM_NAME%", invite.getInvite().getProperties().get("room_name")); + String domainPretty = WordUtils.capitalizeFully(srvCfg.getName()); + 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 templateBody = IOUtils.toString(app.getResource(cfg.getTemplate()).getInputStream(), StandardCharsets.UTF_8); + templateBody = templateBody.replace("%DOMAIN%", srvCfg.getName()); + templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty); + templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail()); + templateBody = templateBody.replace("%FROM_NAME%", cfg.getName()); + 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("%ROOM_ID%", invite.getInvite().getRoomId()); + templateBody = templateBody.replace("%ROOM_NAME%", roomName); + templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId); MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8)); msg.setHeader("X-Mailer", "mxisd"); // TODO set version msg.setSentDate(new Date()); msg.setFrom(sender); msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); + msg.saveChanges(); log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8c78f35..74984af 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,3 +1,11 @@ +logging: + level: + org: + springframework: "WARN" + apache: + catalina: "WARN" + directory: "WARN" + server: port: 8090 @@ -18,16 +26,17 @@ lookup: ldap: enabled: false +firebase: + enabled: false + forward: servers: - "https://matrix.org" - "https://vector.im" -firebase: - enabled: false - invite: sender: email: tls: 1 name: "mxisd Identity Server" + template: "classpath:email/invite-template.eml" diff --git a/src/main/resources/email/invite-template.eml b/src/main/resources/email/invite-template.eml new file mode 100644 index 0000000..0d85320 --- /dev/null +++ b/src/main/resources/email/invite-template.eml @@ -0,0 +1,91 @@ +Subject: You have been invited to %DOMAIN% +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ" + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ +Content-Type: text/plain; charset=UTF-8 +Content-Disposition: inline + +Hi, + +%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on +Matrix. To join the conversation, register an account on http://%DOMAIN% + + +About Matrix: + +Matrix is an open standard for interoperable, decentralised, real-time communication +over IP, supporting group chat, file transfer, voice and video calling, integrations to +other apps, bridges to other communication systems and much more. It can be used to power +Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication. + +Thanks, + +%DOMAIN_PRETTY% Admins + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ +Content-Type: multipart/related; + boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR"; + type="text/html" + +--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR +Content-Type: text/html; charset=UTF-8 +Content-Disposition: inline + + + + + + + + + + + + + +
+

Hi,

+ +

%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on +Matrix. To join the conversation, register an account on %DOMAIN%.

+ +
+

About Matrix:

+ +

Matrix is an open standard for interoperable, decentralised, real-time communication + over IP, supporting group chat, file transfer, voice and video calling, integrations to + other apps, bridges to other communication systems and much more. It can be used to power + Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication.

+ +

Thanks,

+ +

%DOMAIN_PRETTY% Admins

+
+ + +--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR-- + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ-- From 09b789dfc2b04cde524774a586e86cbb58c4b680 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Tue, 12 Sep 2017 19:47:01 +0200 Subject: [PATCH 15/28] Refactor logic, preparing to generalize post-login publish of mappings --- src/main/groovy/io/kamax/mxisd/ThreePid.java | 5 + .../io/kamax/mxisd/auth/AuthManager.java | 12 ++ .../io/kamax/mxisd/auth/UserAuthResult.java | 22 ++++ .../GoogleFirebaseAuthenticator.groovy | 73 ++++++++----- .../io/kamax/mxisd/config/FirebaseConfig.java | 8 +- .../mxisd/invitation/InvitationManager.java | 103 ++++++++---------- .../kamax/mxisd/lookup/ThreePidMapping.java | 5 + .../provider/GoogleFirebaseProvider.groovy | 2 +- .../lookup/strategy/LookupStrategy.groovy | 2 + .../RecursivePriorityLookupStrategy.groovy | 10 ++ 10 files changed, 151 insertions(+), 91 deletions(-) diff --git a/src/main/groovy/io/kamax/mxisd/ThreePid.java b/src/main/groovy/io/kamax/mxisd/ThreePid.java index 04e685a..d51ecd6 100644 --- a/src/main/groovy/io/kamax/mxisd/ThreePid.java +++ b/src/main/groovy/io/kamax/mxisd/ThreePid.java @@ -39,4 +39,9 @@ public class ThreePid { return address; } + @Override + public String toString() { + return getMedium() + ":" + getAddress(); + } + } diff --git a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java b/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java index dd14f0b..61f33e7 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java +++ b/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java @@ -20,7 +20,10 @@ package io.kamax.mxisd.auth; +import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.auth.provider.AuthenticatorProvider; +import io.kamax.mxisd.invitation.InvitationManager; +import io.kamax.mxisd.lookup.ThreePidMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +40,9 @@ public class AuthManager { @Autowired private List providers = new ArrayList<>(); + @Autowired + private InvitationManager invMgr; + public UserAuthResult authenticate(String id, String password) { for (AuthenticatorProvider provider : providers) { if (!provider.isEnabled()) { @@ -45,6 +51,12 @@ public class AuthManager { UserAuthResult result = provider.authenticate(id, password); if (result.isSuccess()) { + log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName()); + for (ThreePid pid : result.getThreePids()) { + log.info("Processing {} for {}", pid, id); + invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid())); + } + return result; } } diff --git a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java b/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java index efa278c..f51f2bc 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java +++ b/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java @@ -20,11 +20,19 @@ package io.kamax.mxisd.auth; +import io.kamax.matrix.ThreePidMedium; +import io.kamax.mxisd.ThreePid; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class UserAuthResult { private boolean success; private String mxid; private String displayName; + private List threePids = new ArrayList<>(); public UserAuthResult failure() { success = false; @@ -66,4 +74,18 @@ public class UserAuthResult { this.displayName = displayName; } + public UserAuthResult withThreePid(ThreePidMedium medium, String address) { + return withThreePid(medium.getId(), address); + } + + public UserAuthResult withThreePid(String medium, String address) { + threePids.add(new ThreePid(medium, address)); + + return this; + } + + public List getThreePids() { + return Collections.unmodifiableList(threePids); + } + } diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy b/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy index 94ff111..7d57cce 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/GoogleFirebaseAuthenticator.groovy @@ -22,17 +22,12 @@ package io.kamax.mxisd.auth.provider import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.FirebaseCredential -import com.google.firebase.auth.FirebaseCredentials -import com.google.firebase.auth.FirebaseToken +import com.google.firebase.auth.* import com.google.firebase.internal.NonNull import com.google.firebase.tasks.OnFailureListener import com.google.firebase.tasks.OnSuccessListener import io.kamax.matrix.ThreePidMedium import io.kamax.mxisd.auth.UserAuthResult -import io.kamax.mxisd.invitation.InvitationManager -import io.kamax.mxisd.lookup.ThreePidMapping import org.apache.commons.lang.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -46,25 +41,31 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider { private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class); - private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); + private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); // FIXME use matrix-java-sdk private boolean isEnabled; private String domain; private FirebaseApp fbApp; private FirebaseAuth fbAuth; - private InvitationManager invMgr; - - public GoogleFirebaseAuthenticator(InvitationManager invMgr, boolean isEnabled) { - this.isEnabled = isEnabled; - this.invMgr = invMgr; + private void waitOnLatch(UserAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) { + try { + l.await(timeout, unit); + } catch (InterruptedException e) { + log.warn("Interrupted while waiting for " + purpose); + result.failure(); + } } - public GoogleFirebaseAuthenticator(InvitationManager invMgr, String credsPath, String db, String domain) { - this(invMgr, true); + public GoogleFirebaseAuthenticator(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public GoogleFirebaseAuthenticator(String credsPath, String db, String domain) { + this(true); this.domain = domain; try { - fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db)); + fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "AuthenticationProvider"); fbAuth = FirebaseAuth.getInstance(fbApp); log.info("Google Firebase Authentication is ready"); @@ -130,14 +131,42 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider { if (!StringUtils.equals(localpart, token.getUid())) { log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid()); result.failure(); + return; } log.info("{} was successfully authenticated", id); result.success(id, token.getName()); - if (StringUtils.isNotBlank(token.getEmail())) { - invMgr.publishMappingIfInvited(new ThreePidMapping(ThreePidMedium.Email.getId(), token.getEmail(), id)) - } + log.info("Fetching profile for {}", id); + CountDownLatch userRecordLatch = new CountDownLatch(1); + fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener() { + @Override + void onSuccess(UserRecord user) { + try { + if (StringUtils.isNotBlank(user.getEmail())) { + result.withThreePid(ThreePidMedium.Email, user.getEmail()); + } + + if (StringUtils.isNotBlank(user.getPhoneNumber())) { + result.withThreePid(ThreePidMedium.PhoneNumber, user.getPhoneNumber()); + } + } finally { + userRecordLatch.countDown(); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + void onFailure(@NonNull Exception e) { + try { + log.warn("Unable to fetch Firebase user profile for {}", id); + result.failure(); + } finally { + userRecordLatch.countDown(); + } + } + }); + + waitOnLatch(result, userRecordLatch, 30, TimeUnit.SECONDS, "Firebase user profile"); } finally { l.countDown() } @@ -160,13 +189,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider { } }); - try { - l.await(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.warn("Interrupted while waiting for Firebase auth check"); - result.failure(); - } - + waitOnLatch(result, l, 30, TimeUnit.SECONDS, "Firebase auth check"); return result; } diff --git a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java b/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java index 9480347..b3799f8 100644 --- a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java @@ -22,7 +22,6 @@ package io.kamax.mxisd.config; import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.GoogleFirebaseAuthenticator; -import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.lookup.provider.GoogleFirebaseProvider; import io.kamax.mxisd.lookup.provider.IThreePidProvider; import org.slf4j.Logger; @@ -43,9 +42,6 @@ public class FirebaseConfig { @Autowired private ServerConfig srvCfg; - @Autowired - private InvitationManager invMgr; - private boolean enabled; private String credentials; private String database; @@ -87,9 +83,9 @@ public class FirebaseConfig { @Bean public AuthenticatorProvider getAuthProvider() { if (!enabled) { - return new GoogleFirebaseAuthenticator(invMgr, false); + return new GoogleFirebaseAuthenticator(false); } else { - return new GoogleFirebaseAuthenticator(invMgr, credentials, database, srvCfg.getName()); + return new GoogleFirebaseAuthenticator(credentials, database, srvCfg.getName()); } } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 7287030..5d7510c 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -25,7 +25,6 @@ import io.kamax.matrix.ThreePid; import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.MappingAlreadyExistsException; import io.kamax.mxisd.invitation.sender.IInviteSender; -import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.signature.SignatureManager; @@ -99,7 +98,7 @@ public class InvitationManager { // TODO use caching mechanism // TODO export in matrix-java-sdk - Optional findHomeserverForDomain(String domain) { + String findHomeserverForDomain(String domain) { log.debug("Performing SRV lookup for {}", domain); String lookupDns = getSrvRecordName(domain); log.info("Lookup name: {}", lookupDns); @@ -110,7 +109,7 @@ public class InvitationManager { Arrays.sort(records, Comparator.comparingInt(SRVRecord::getPriority)); for (SRVRecord record : records) { log.info("Found SRV record: {}", record.toString()); - return Optional.of("https://" + record.getTarget().toString(true) + ":" + record.getPort()); + return "https://" + record.getTarget().toString(true) + ":" + record.getPort(); } } else { log.info("No SRV record for {}", lookupDns); @@ -120,7 +119,7 @@ public class InvitationManager { } log.info("Performing basic lookup using domain name {}", domain); - return Optional.of("https://" + domain + ":8448"); + return "https://" + domain + ":8448"; } @Autowired @@ -143,13 +142,7 @@ public class InvitationManager { return invitations.get(pid); } - SingleLookupRequest request = new SingleLookupRequest(); - request.setType(invitation.getMedium()); - request.setThreePid(invitation.getAddress()); - request.setRecursive(true); - request.setRequester("Internal"); - - Optional result = lookupMgr.findRecursive(request); + Optional result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true); if (result.isPresent()) { log.info("Mapping for {}:{} already exists, refusing to store invite", pid.getMedium(), pid.getAddress()); throw new MappingAlreadyExistsException(); @@ -180,60 +173,52 @@ public class InvitationManager { log.info("{}:{} has an invite pending, publishing mapping", threePid.getMedium(), threePid.getValue()); String domain = reply.getInvite().getSender().getDomain(); log.info("Discovering HS for domain {}", domain); - Optional hsUrlOpt = findHomeserverForDomain(domain); - if (!hsUrlOpt.isPresent()) { - log.warn("No HS found for domain {} - ignoring publishing", domain); - } else { - // TODO this is needed as this will block if called during authentication cycle due to synapse implementation + String hsUrlOpt = findHomeserverForDomain(domain); - new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool - HttpPost req = new HttpPost(hsUrlOpt.get() + "/_matrix/federation/v1/3pid/onbind"); - // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr - JSONObject obj = new JSONObject(); // TODO use Gson instead - obj.put("mxid", threePid.getMxid()); - obj.put("token", reply.getToken()); - obj.put("signatures", signMgr.signMessageJson(obj.toString())); + // TODO this is needed as this will block if called during authentication cycle due to synapse implementation + new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool + HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind"); + // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr + JSONObject obj = new JSONObject(); // TODO use Gson instead + obj.put("mxid", threePid.getMxid()); + obj.put("token", reply.getToken()); + obj.put("signatures", signMgr.signMessageJson(obj.toString())); - JSONObject objUp = new JSONObject(); - objUp.put("mxid", threePid.getMxid()); - objUp.put("medium", threePid.getMedium()); - objUp.put("address", threePid.getValue()); - objUp.put("sender", reply.getInvite().getSender().getId()); - objUp.put("room_id", reply.getInvite().getRoomId()); - objUp.put("signed", obj); + JSONObject objUp = new JSONObject(); + objUp.put("mxid", threePid.getMxid()); + objUp.put("medium", threePid.getMedium()); + objUp.put("address", threePid.getValue()); + objUp.put("sender", reply.getInvite().getSender().getId()); + objUp.put("room_id", reply.getInvite().getRoomId()); + objUp.put("signed", obj); - String mapping = gson.toJson(objUp); // FIXME we shouldn't need to be doing this + JSONObject content = new JSONObject(); // TODO use Gson instead + JSONArray invites = new JSONArray(); + invites.put(objUp); + content.put("invites", invites); + content.put("medium", threePid.getMedium()); + content.put("address", threePid.getValue()); + content.put("mxid", threePid.getMxid()); - JSONObject content = new JSONObject(); // TODO use Gson instead - JSONArray invites = new JSONArray(); - invites.put(objUp); - content.put("invites", invites); - content.put("medium", threePid.getMedium()); - content.put("address", threePid.getValue()); - content.put("mxid", threePid.getMxid()); + content.put("signatures", signMgr.signMessageJson(content.toString())); - content.put("signatures", signMgr.signMessageJson(content.toString())); - - StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); - entity.setContentType("application/json"); - req.setEntity(entity); - try { - log.info("Posting onBind event to {}", req.getURI()); - CloseableHttpResponse response = client.execute(req); - int statusCode = response.getStatusLine().getStatusCode(); - log.info("Answer code: {}", statusCode); - if (statusCode >= 400) { - log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); - } - response.close(); - } catch (IOException e) { - log.warn("Unable to tell HS {} about invite being mapped", domain, e); + StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); + entity.setContentType("application/json"); + req.setEntity(entity); + try { + log.info("Posting onBind event to {}", req.getURI()); + CloseableHttpResponse response = client.execute(req); + int statusCode = response.getStatusLine().getStatusCode(); + log.info("Answer code: {}", statusCode); + if (statusCode >= 400) { + log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); } - invitations.remove(key); - }).start(); - } - - + response.close(); + } catch (IOException e) { + log.warn("Unable to tell HS {} about invite being mapped", domain, e); + } + invitations.remove(key); + }).start(); } } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java index c87d1b4..5777ad7 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java +++ b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java @@ -21,6 +21,7 @@ package io.kamax.mxisd.lookup; import groovy.json.JsonOutput; +import io.kamax.mxisd.ThreePid; public class ThreePidMapping { @@ -32,6 +33,10 @@ public class ThreePidMapping { // stub } + public ThreePidMapping(ThreePid threePid, String mxid) { + this(threePid.getMedium(), threePid.getAddress(), mxid); + } + public ThreePidMapping(String medium, String value, String mxid) { setMedium(medium); setValue(value); diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy index 30e89a7..077974b 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy @@ -60,7 +60,7 @@ public class GoogleFirebaseProvider implements IThreePidProvider { this(true); this.domain = domain; try { - fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db)); + fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider"); fbAuth = FirebaseAuth.getInstance(fbApp); log.info("Google Firebase Authentication is ready"); diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy index 1e1c8d0..a4fd5e7 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy @@ -29,6 +29,8 @@ interface LookupStrategy { List getLocalProviders() + Optional find(String medium, String address, boolean recursive) + Optional find(SingleLookupRequest request) Optional findRecursive(SingleLookupRequest request) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy index 8cfdd28..39a6337 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy @@ -121,6 +121,16 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea }).collect(Collectors.toList()) } + @Override + Optional find(String medium, String address, boolean recursive) { + SingleLookupRequest req = new SingleLookupRequest(); + req.setType(medium) + req.setThreePid(address) + req.setRequester("Internal") + return find(req, recursive) + } + + @Override Optional find(SingleLookupRequest request, boolean forceRecursive) { for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) { Optional lookupDataOpt = provider.find(request) From 001de470ca63429bfc6599bf7d6b8c99351f9056 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Tue, 12 Sep 2017 19:48:39 +0200 Subject: [PATCH 16/28] Fix invalid add of @Override --- .../mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy index 39a6337..55eedb0 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy @@ -130,7 +130,6 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea return find(req, recursive) } - @Override Optional find(SingleLookupRequest request, boolean forceRecursive) { for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) { Optional lookupDataOpt = provider.find(request) From 548dace78c523ae54a5b4c21bf687223967b68e6 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 01:16:02 +0200 Subject: [PATCH 17/28] Properly handle invites with LDAP backend --- .../io/kamax/mxisd/auth/AuthManager.java | 2 + .../controller/v1/MappingController.groovy | 35 ++++-- .../v1/io/SingeLookupReplyJson.java | 75 +++++++++++ .../mxisd/invitation/InvitationManager.java | 74 +++++++++-- .../kamax/mxisd/lookup/SingleLookupReply.java | 116 ++++++++++++++++++ .../lookup/fetcher/IBridgeFetcher.groovy | 3 +- .../IRemoteIdentityServerFetcher.groovy | 4 +- .../mxisd/lookup/provider/BridgeFetcher.java | 7 +- .../lookup/provider/DnsLookupProvider.groovy | 5 +- .../lookup/provider/ForwarderProvider.groovy | 5 +- .../provider/GoogleFirebaseProvider.groovy | 22 ++-- .../lookup/provider/IThreePidProvider.groovy | 3 +- .../mxisd/lookup/provider/LdapProvider.groovy | 12 +- .../RemoteIdentityServerFetcher.groovy | 16 ++- .../lookup/strategy/LookupStrategy.groovy | 7 +- .../RecursivePriorityLookupStrategy.groovy | 15 +-- .../mxisd/signature/SignatureManager.groovy | 25 ++-- 17 files changed, 344 insertions(+), 82 deletions(-) create mode 100644 src/main/groovy/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java create mode 100644 src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java diff --git a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java b/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java index 61f33e7..6dbfff2 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java +++ b/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java @@ -57,6 +57,8 @@ public class AuthManager { invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid())); } + invMgr.lookupMappingsForInvites(); + return result; } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy index 050914b..fdd9ef3 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy @@ -20,18 +20,20 @@ package io.kamax.mxisd.controller.v1 +import com.google.gson.Gson +import com.google.gson.JsonObject import groovy.json.JsonOutput import groovy.json.JsonSlurper -import io.kamax.mxisd.lookup.ALookupRequest -import io.kamax.mxisd.lookup.BulkLookupRequest -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping +import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson +import io.kamax.mxisd.lookup.* import io.kamax.mxisd.lookup.strategy.LookupStrategy import io.kamax.mxisd.signature.SignatureManager import org.apache.commons.lang.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @@ -42,10 +44,13 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET import static org.springframework.web.bind.annotation.RequestMethod.POST @RestController +@CrossOrigin +@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) class MappingController { private Logger log = LoggerFactory.getLogger(MappingController.class) private JsonSlurper json = new JsonSlurper() + private Gson gson = new Gson() @Autowired private LookupStrategy strategy @@ -62,7 +67,7 @@ class MappingController { } } - @RequestMapping(value = "/_matrix/identity/api/v1/lookup", method = GET) + @RequestMapping(value = "/lookup", method = GET) String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) { SingleLookupRequest lookupRequest = new SingleLookupRequest() setRequesterInfo(lookupRequest, request) @@ -71,22 +76,26 @@ class MappingController { log.info("Got request from {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.isRecursive()) - Optional lookupOpt = strategy.find(lookupRequest) + Optional lookupOpt = strategy.find(lookupRequest) if (!lookupOpt.isPresent()) { log.info("No mapping was found, return empty JSON object") return JsonOutput.toJson([]) } - def lookup = lookupOpt.get() - if (lookup['signatures'] == null) { - log.info("lookup is not signed yet, we sign it") - lookup['signatures'] = signMgr.signMessage(JsonOutput.toJson(lookup)) - } + SingleLookupReply lookup = lookupOpt.get() + if (lookup.isSigned()) { + log.info("Lookup is already signed, sending as-is") + return lookup.getBody(); + } else { + log.info("Lookup is not signed, signing") + JsonObject obj = new Gson().toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject() + obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj))) - return JsonOutput.toJson(lookup) + return gson.toJson(obj) + } } - @RequestMapping(value = "/_matrix/identity/api/v1/bulk_lookup", method = POST) + @RequestMapping(value = "/bulk_lookup", method = POST) String bulkLookup(HttpServletRequest request) { BulkLookupRequest lookupRequest = new BulkLookupRequest() setRequesterInfo(lookupRequest, request) diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java new file mode 100644 index 0000000..5627664 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java @@ -0,0 +1,75 @@ +/* + * 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.controller.v1.io; + +import io.kamax.mxisd.lookup.SingleLookupReply; + +import java.util.HashMap; +import java.util.Map; + +public class SingeLookupReplyJson { + + private String address; + private String medium; + private String mxid; + private long not_after; + private long not_before; + private long ts; + private Map> signatures = new HashMap<>(); + + public SingeLookupReplyJson(SingleLookupReply reply) { + this.address = reply.getRequest().getThreePid(); + this.medium = reply.getRequest().getType(); + this.mxid = reply.getMxid().getId(); + this.not_after = reply.getNotAfter().toEpochMilli(); + this.not_before = reply.getNotBefore().toEpochMilli(); + this.ts = reply.getTimestamp().toEpochMilli(); + } + + public String getAddress() { + return address; + } + + public String getMedium() { + return medium; + } + + public String getMxid() { + return mxid; + } + + public long getNot_after() { + return not_after; + } + + public long getNot_before() { + return not_before; + } + + public long getTs() { + return ts; + } + + public boolean isSigned() { + return signatures != null && !signatures.isEmpty(); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 5d7510c..bdb2266 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -25,6 +25,7 @@ import io.kamax.matrix.ThreePid; import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.MappingAlreadyExistsException; import io.kamax.mxisd.invitation.sender.IInviteSender; +import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.signature.SignatureManager; @@ -51,12 +52,15 @@ import org.xbill.DNS.TextParseException; import org.xbill.DNS.Type; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; @Component public class InvitationManager { @@ -92,6 +96,15 @@ public class InvitationManager { } } + @PreDestroy + private void preDestroy() { + ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES); + } + + private String getId(IThreePidInviteReply reply) { + return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress(); + } + String getSrvRecordName(String domain) { return "_matrix._tcp." + domain; } @@ -162,15 +175,28 @@ public class InvitationManager { return reply; } + public void lookupMappingsForInvites() { + log.info("Checking for existing mapping for pending invites"); + for (IThreePidInviteReply reply : invitations.values()) { + log.info("Processing invite {}", getId(reply)); + ForkJoinPool.commonPool().submit(new MappingChecker(reply)); + } + } + public void publishMappingIfInvited(ThreePidMapping threePid) { ThreePid key = new ThreePid(threePid.getMedium(), threePid.getValue()); IThreePidInviteReply reply = invitations.get(key); if (reply == null) { log.info("{}:{} does not have a pending invite, no mapping to publish", threePid.getMedium(), threePid.getValue()); - return; + } else { + log.info("{}:{} has an invite pending, publishing mapping", threePid.getMedium(), threePid.getValue()); + publishMapping(reply, threePid.getMxid()); } + } - log.info("{}:{} has an invite pending, publishing mapping", threePid.getMedium(), threePid.getValue()); + private void publishMapping(IThreePidInviteReply reply, String mxid) { + String medium = reply.getInvite().getMedium(); + String address = reply.getInvite().getAddress(); String domain = reply.getInvite().getSender().getDomain(); log.info("Discovering HS for domain {}", domain); String hsUrlOpt = findHomeserverForDomain(domain); @@ -180,14 +206,14 @@ public class InvitationManager { HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind"); // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr JSONObject obj = new JSONObject(); // TODO use Gson instead - obj.put("mxid", threePid.getMxid()); + obj.put("mxid", mxid); obj.put("token", reply.getToken()); obj.put("signatures", signMgr.signMessageJson(obj.toString())); JSONObject objUp = new JSONObject(); - objUp.put("mxid", threePid.getMxid()); - objUp.put("medium", threePid.getMedium()); - objUp.put("address", threePid.getValue()); + objUp.put("mxid", mxid); + objUp.put("medium", medium); + objUp.put("address", address); objUp.put("sender", reply.getInvite().getSender().getId()); objUp.put("room_id", reply.getInvite().getRoomId()); objUp.put("signed", obj); @@ -196,9 +222,9 @@ public class InvitationManager { JSONArray invites = new JSONArray(); invites.put(objUp); content.put("invites", invites); - content.put("medium", threePid.getMedium()); - content.put("address", threePid.getValue()); - content.put("mxid", threePid.getMxid()); + content.put("medium", medium); + content.put("address", address); + content.put("mxid", mxid); content.put("signatures", signMgr.signMessageJson(content.toString())); @@ -217,8 +243,36 @@ public class InvitationManager { } catch (IOException e) { log.warn("Unable to tell HS {} about invite being mapped", domain, e); } - invitations.remove(key); + invitations.remove(new ThreePid(medium, address)); + log.info("Removed invite from internal store"); }).start(); } + private class MappingChecker implements Runnable { + + private IThreePidInviteReply reply; + + public MappingChecker(IThreePidInviteReply reply) { + this.reply = reply; + } + + @Override + public void run() { + try { + log.info("Searching for mapping created since invite {} was created", getId(reply)); + Optional result = lookupMgr.find(reply.getInvite().getMedium(), reply.getInvite().getAddress(), true); + log.info("Result: {}", result); + if (result.isPresent()) { + SingleLookupReply lookup = result.get(); + log.info("Found mapping for pending invite {}", getId(reply)); + publishMapping(reply, lookup.getMxid().getId()); + } else { + log.info("No mapping for pending invite {}", getId(reply)); + } + } catch (Throwable t) { + log.error("Unable to process invite", t); + } + } + } + } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java b/src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java new file mode 100644 index 0000000..d3a212c --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java @@ -0,0 +1,116 @@ +/* + * 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.lookup; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import io.kamax.matrix.MatrixID; +import io.kamax.matrix._MatrixID; +import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson; + +import java.time.Instant; + +public class SingleLookupReply { + + private static Gson gson = new Gson(); + + private boolean isRecursive; + private boolean isSigned; + private String body; + private SingleLookupRequest request; + private _MatrixID mxid; + private Instant notBefore; + private Instant notAfter; + private Instant timestamp; + + public static SingleLookupReply fromRecursive(SingleLookupRequest request, String body) { + SingleLookupReply reply = new SingleLookupReply(); + reply.isRecursive = true; + reply.request = request; + reply.body = body; + + try { + SingeLookupReplyJson json = gson.fromJson(body, SingeLookupReplyJson.class); + reply.mxid = new MatrixID(json.getMxid()); + reply.notAfter = Instant.ofEpochMilli(json.getNot_after()); + reply.notBefore = Instant.ofEpochMilli(json.getNot_before()); + reply.timestamp = Instant.ofEpochMilli(json.getTs()); + reply.isSigned = json.isSigned(); + } catch (JsonSyntaxException e) { + // stub - we only want to try, nothing more + } + + return reply; + } + + private SingleLookupReply() { + // stub + } + + public SingleLookupReply(SingleLookupRequest request, String mxid) { + this(request, new MatrixID(mxid)); + } + + public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid) { + this(request, mxid, Instant.now(), Instant.ofEpochMilli(0), Instant.ofEpochMilli(253402300799000L)); + } + + public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid, Instant timestamp, Instant notBefore, Instant notAfter) { + this.request = request; + this.mxid = mxid; + this.timestamp = timestamp; + this.notBefore = notBefore; + this.notAfter = notAfter; + } + + public boolean isRecursive() { + return isRecursive; + } + + public boolean isSigned() { + return isSigned; + } + + public String getBody() { + return body; + } + + public SingleLookupRequest getRequest() { + return request; + } + + public _MatrixID getMxid() { + return mxid; + } + + public Instant getNotBefore() { + return notBefore; + } + + public Instant getNotAfter() { + return notAfter; + } + + public Instant getTimestamp() { + return timestamp; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy b/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy index a3d3624..bd445bb 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy @@ -20,12 +20,13 @@ package io.kamax.mxisd.lookup.fetcher +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping interface IBridgeFetcher { - Optional find(SingleLookupRequest request) + Optional find(SingleLookupRequest request) List populate(List mappings) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy b/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy index 2b45959..aeb81d5 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy @@ -20,13 +20,15 @@ package io.kamax.mxisd.lookup.fetcher +import io.kamax.mxisd.lookup.SingleLookupReply +import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping interface IRemoteIdentityServerFetcher { boolean isUsable(String remote) - Optional find(String remote, String type, String threePid) + Optional find(String remote, SingleLookupRequest request) List find(String remote, List mappings) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/BridgeFetcher.java b/src/main/groovy/io/kamax/mxisd/lookup/provider/BridgeFetcher.java index ec1c8cd..bb8a85f 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/BridgeFetcher.java +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/BridgeFetcher.java @@ -21,6 +21,7 @@ package io.kamax.mxisd.lookup.provider; import io.kamax.mxisd.config.RecursiveLookupBridgeConfig; +import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher; @@ -46,16 +47,16 @@ public class BridgeFetcher implements IBridgeFetcher { private RemoteIdentityServerFetcher fetcher; @Override - public Optional find(SingleLookupRequest request) { + public Optional find(SingleLookupRequest request) { Optional mediumUrl = Optional.ofNullable(cfg.getMappings().get(request.getType())); if (mediumUrl.isPresent() && !StringUtils.isBlank(mediumUrl.get())) { log.info("Using specific medium bridge lookup URL {}", mediumUrl.get()); - return fetcher.find(mediumUrl.get(), request.getType(), request.getThreePid()); + return fetcher.find(mediumUrl.get(), request); } else if (!StringUtils.isBlank(cfg.getServer())) { log.info("Using generic bridge lookup URL {}", cfg.getServer()); - return fetcher.find(cfg.getServer(), request.getType(), request.getThreePid()); + return fetcher.find(cfg.getServer(), request); } else { log.info("No bridge lookup URL found/configured, skipping"); diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy index a7076a1..40aec25 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy @@ -21,6 +21,7 @@ package io.kamax.mxisd.lookup.provider import io.kamax.mxisd.config.ServerConfig +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher @@ -124,7 +125,7 @@ class DnsLookupProvider implements IThreePidProvider { } @Override - Optional find(SingleLookupRequest request) { + Optional find(SingleLookupRequest request) { if (!StringUtils.equals("email", request.getType())) { // TODO use enum log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid()) return Optional.empty() @@ -137,7 +138,7 @@ class DnsLookupProvider implements IThreePidProvider { Optional baseUrl = findIdentityServerForDomain(domain) if (baseUrl.isPresent()) { - return fetcher.find(baseUrl.get(), request.getType().toString(), request.getThreePid()) + return fetcher.find(baseUrl.get(), request) } return Optional.empty() diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy index f2253dc..1ef903a 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy @@ -21,6 +21,7 @@ package io.kamax.mxisd.lookup.provider import io.kamax.mxisd.config.ForwardConfig +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher @@ -56,9 +57,9 @@ class ForwarderProvider implements IThreePidProvider { } @Override - Optional find(SingleLookupRequest request) { + Optional find(SingleLookupRequest request) { for (String root : cfg.getServers()) { - Optional answer = fetcher.find(root, request.getType(), request.getThreePid()) + Optional answer = fetcher.find(root, request) if (answer.isPresent()) { return answer } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy index 077974b..30d3c7b 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy @@ -30,6 +30,7 @@ import com.google.firebase.internal.NonNull import com.google.firebase.tasks.OnFailureListener import com.google.firebase.tasks.OnSuccessListener import io.kamax.matrix.ThreePidMedium +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping import org.apache.commons.lang.StringUtils @@ -88,6 +89,10 @@ public class GoogleFirebaseProvider implements IThreePidProvider { .build(); } + private String getMxid(UserRecord record) { + return "@${record.getUid()}:${domain}"; + } + @Override public boolean isEnabled() { return isEnabled; @@ -154,20 +159,13 @@ public class GoogleFirebaseProvider implements IThreePidProvider { } @Override - public Optional find(SingleLookupRequest request) { + public Optional find(SingleLookupRequest request) { Optional urOpt = findInternal(request.getType(), request.getThreePid()) if (urOpt.isPresent()) { - return [ - address : request.getThreePid(), - medium : request.getType(), - mxid : "@${urOpt.get().getUid()}:${domain}", - not_before: 0, - not_after : 9223372036854775807, - ts : 0 - ] - } else { - return Optional.empty(); + return Optional.of(new SingleLookupReply(request, getMxid(urOpt.get()))); } + + return Optional.empty(); } @Override @@ -181,7 +179,7 @@ public class GoogleFirebaseProvider implements IThreePidProvider { ThreePidMapping result = new ThreePidMapping(); result.setMedium(o.getMedium()) result.setValue(o.getValue()) - result.setMxid("@${urOpt.get().getUid()}:${domain}") + result.setMxid(getMxid(urOpt.get())) results.add(result) } } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy index 445afda..614cc04 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy @@ -20,6 +20,7 @@ package io.kamax.mxisd.lookup.provider +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping @@ -34,7 +35,7 @@ interface IThreePidProvider { */ int getPriority() // Should not be here but let's KISS for now - Optional find(SingleLookupRequest request) + Optional find(SingleLookupRequest request) List populate(List mappings) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy index 1984f32..c2cc220 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy @@ -22,6 +22,7 @@ package io.kamax.mxisd.lookup.provider import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.config.ldap.LdapConfig +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping import org.apache.commons.lang.StringUtils @@ -131,7 +132,7 @@ class LdapProvider implements IThreePidProvider { } @Override - Optional find(SingleLookupRequest request) { + Optional find(SingleLookupRequest request) { log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}") LdapConnection conn = getConn() @@ -140,14 +141,7 @@ class LdapProvider implements IThreePidProvider { Optional mxid = lookup(conn, request.getType(), request.getThreePid()) if (mxid.isPresent()) { - return Optional.of([ - address : request.getThreePid(), - medium : request.getType(), - mxid : mxid.get(), - not_before: 0, - not_after : 9223372036854775807, - ts : 0 - ]) + return Optional.of(new SingleLookupReply(request, mxid.get())); } } finally { conn.close() diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy index 36a38de..7435879 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy @@ -24,6 +24,8 @@ import groovy.json.JsonException import groovy.json.JsonOutput import groovy.json.JsonSlurper import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest +import io.kamax.mxisd.lookup.SingleLookupReply +import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher import org.apache.http.HttpEntity @@ -77,24 +79,26 @@ public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher } @Override - Optional find(String remote, String type, String threePid) { - log.info("Looking up {} 3PID {} using {}", type, threePid, remote) + Optional find(String remote, SingleLookupRequest request) { + log.info("Looking up {} 3PID {} using {}", request.getType(), request.getThreePid(), remote) HttpURLConnection rootSrvConn = (HttpURLConnection) new URL( - "${remote}/_matrix/identity/api/v1/lookup?medium=${type}&address=${threePid}" + "${remote}/_matrix/identity/api/v1/lookup?medium=${request.getType()}&address=${request.getThreePid()}" ).openConnection() try { - def output = json.parseText(rootSrvConn.getInputStream().getText()) + String outputRaw = rootSrvConn.getInputStream().getText() + def output = json.parseText(outputRaw) if (output['address']) { log.info("Found 3PID mapping: {}", output) - return Optional.of(output) + + return Optional.of(SingleLookupReply.fromRecursive(request, outputRaw)) } log.info("Empty 3PID mapping from {}", remote) return Optional.empty() } catch (IOException e) { - log.warn("Error looking up 3PID mapping {}: {}", threePid, e.getMessage()) + log.warn("Error looking up 3PID mapping {}: {}", request.getThreePid(), e.getMessage()) return Optional.empty() } catch (JsonException e) { log.warn("Invalid JSON answer from {}", remote) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy index a4fd5e7..37e4327 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy @@ -21,6 +21,7 @@ package io.kamax.mxisd.lookup.strategy import io.kamax.mxisd.lookup.BulkLookupRequest +import io.kamax.mxisd.lookup.SingleLookupReply import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.provider.IThreePidProvider @@ -29,11 +30,11 @@ interface LookupStrategy { List getLocalProviders() - Optional find(String medium, String address, boolean recursive) + Optional find(String medium, String address, boolean recursive) - Optional find(SingleLookupRequest request) + Optional find(SingleLookupRequest request) - Optional findRecursive(SingleLookupRequest request) + Optional findRecursive(SingleLookupRequest request) List find(BulkLookupRequest requests) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy index 55eedb0..e4ef776 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy @@ -22,10 +22,7 @@ package io.kamax.mxisd.lookup.strategy import edazdarevic.commons.net.CIDRUtils import io.kamax.mxisd.config.RecursiveLookupConfig -import io.kamax.mxisd.lookup.ALookupRequest -import io.kamax.mxisd.lookup.BulkLookupRequest -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping +import io.kamax.mxisd.lookup.* import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher import io.kamax.mxisd.lookup.provider.IThreePidProvider import org.slf4j.Logger @@ -122,7 +119,7 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea } @Override - Optional find(String medium, String address, boolean recursive) { + Optional find(String medium, String address, boolean recursive) { SingleLookupRequest req = new SingleLookupRequest(); req.setType(medium) req.setThreePid(address) @@ -130,9 +127,9 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea return find(req, recursive) } - Optional find(SingleLookupRequest request, boolean forceRecursive) { + Optional find(SingleLookupRequest request, boolean forceRecursive) { for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) { - Optional lookupDataOpt = provider.find(request) + Optional lookupDataOpt = provider.find(request) if (lookupDataOpt.isPresent()) { return lookupDataOpt } @@ -151,12 +148,12 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea } @Override - Optional find(SingleLookupRequest request) { + Optional find(SingleLookupRequest request) { return find(request, false) } @Override - Optional findRecursive(SingleLookupRequest request) { + Optional findRecursive(SingleLookupRequest request) { return find(request, true) } diff --git a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy index 72dede2..84c4ad9 100644 --- a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy +++ b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy @@ -20,6 +20,7 @@ package io.kamax.mxisd.signature +import com.google.gson.JsonObject import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.key.KeyManager import net.i2p.crypto.eddsa.EdDSAEngine @@ -41,20 +42,13 @@ class SignatureManager implements InitializingBean { private EdDSAEngine signEngine - // FIXME we need to return a proper object, or a string with the signature and all? - Map signMessage(String message) { + private String sign(String message) { byte[] signRaw = signEngine.signOneShot(message.getBytes()) - String sign = Base64.getEncoder().encodeToString(signRaw) - return [ - "${srvCfg.getName()}": [ - "ed25519:${keyMgr.getCurrentIndex()}": sign - ] - ] + return Base64.getEncoder().encodeToString(signRaw) } JSONObject signMessageJson(String message) { - byte[] signRaw = signEngine.signOneShot(message.getBytes()) - String sign = Base64.getEncoder().encodeToString(signRaw) + String sign = sign(message) JSONObject keySignature = new JSONObject() keySignature.put("ed25519:${keyMgr.getCurrentIndex()}", sign) @@ -64,6 +58,17 @@ class SignatureManager implements InitializingBean { return signature } + JsonObject signMessageGson(String message) { + String sign = sign(message) + + JsonObject keySignature = new JsonObject() + keySignature.addProperty("ed25519:${keyMgr.getCurrentIndex()}", sign) + JsonObject signature = new JsonObject() + signature.add("${srvCfg.getName()}", keySignature); + + return signature + } + @Override void afterPropertiesSet() throws Exception { signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm())) From 222f7f104a25f4c1020fd27052b8a1915a82d5be Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 01:35:11 +0200 Subject: [PATCH 18/28] Add regular pending invite mapping checks --- .../kamax/mxisd/invitation/InvitationManager.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index bdb2266..9e88968 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -79,6 +79,7 @@ public class InvitationManager { private CloseableHttpClient client; private Gson gson; + private Timer refreshTimer; @PostConstruct private void postConstruct() { @@ -94,6 +95,18 @@ public class InvitationManager { // FIXME do better... throw new RuntimeException(e); } + + refreshTimer = new Timer(); + refreshTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + lookupMappingsForInvites(); + } catch (Throwable t) { + log.error("Error when running background mapping refresh", t); + } + } + }, 5000L, TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)); // FIXME make configurable } @PreDestroy From d7258cd3c6e8da49296bd15ab5b3bbce4ac8fae0 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 02:28:22 +0200 Subject: [PATCH 19/28] Fix DNS lookup --- .../mxisd/invitation/InvitationManager.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 9e88968..9f231b3 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -46,10 +46,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.xbill.DNS.Lookup; -import org.xbill.DNS.SRVRecord; -import org.xbill.DNS.TextParseException; -import org.xbill.DNS.Type; +import org.xbill.DNS.*; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -130,10 +127,19 @@ public class InvitationManager { log.info("Lookup name: {}", lookupDns); try { - SRVRecord[] records = (SRVRecord[]) new Lookup(lookupDns, Type.SRV).run(); - if (records != null) { - Arrays.sort(records, Comparator.comparingInt(SRVRecord::getPriority)); - for (SRVRecord record : records) { + List srvRecords = new ArrayList<>(); + Record[] rawRecords = new Lookup(lookupDns, Type.SRV).run(); + if (rawRecords != null && rawRecords.length > 0) { + for (Record record : rawRecords) { + if (Type.SRV == record.getType()) { + srvRecords.add((SRVRecord) record); + } else { + log.info("Got non-SRV record: {}", record.toString()); + } + } + + srvRecords.sort(Comparator.comparingInt(SRVRecord::getPriority)); + for (SRVRecord record : srvRecords) { log.info("Found SRV record: {}", record.toString()); return "https://" + record.getTarget().toString(true) + ":" + record.getPort(); } From d7cf31fb9a3afee6bc90175ee4ff585b96c48926 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 02:28:37 +0200 Subject: [PATCH 20/28] Remove debug log statement --- src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 9f231b3..83e9459 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -280,7 +280,6 @@ public class InvitationManager { try { log.info("Searching for mapping created since invite {} was created", getId(reply)); Optional result = lookupMgr.find(reply.getInvite().getMedium(), reply.getInvite().getAddress(), true); - log.info("Result: {}", result); if (result.isPresent()) { SingleLookupReply lookup = result.get(); log.info("Found mapping for pending invite {}", getId(reply)); From 02c5523d6d4773a049e41d93cf72251ebdc72137 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 02:29:58 +0200 Subject: [PATCH 21/28] Handle bundled and external e-mail template properly --- .../io/kamax/mxisd/invitation/sender/EmailInviteSender.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java index f22a1b6..fff553b 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java @@ -41,6 +41,7 @@ import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; +import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; @@ -92,7 +93,10 @@ public class EmailInviteSender implements IInviteSender { String roomName = invite.getInvite().getProperties().getOrDefault("room_name", ""); String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId()); - String templateBody = IOUtils.toString(app.getResource(cfg.getTemplate()).getInputStream(), StandardCharsets.UTF_8); + String templateBody = IOUtils.toString( + StringUtils.startsWith(cfg.getTemplate(), "classpath:") ? + app.getResource(cfg.getTemplate()).getInputStream() : new FileInputStream(cfg.getTemplate()), + StandardCharsets.UTF_8); templateBody = templateBody.replace("%DOMAIN%", srvCfg.getName()); templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty); templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail()); From 84afb86b777cd882bf06e08031f3c98f6c0e8579 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 02:47:25 +0200 Subject: [PATCH 22/28] Properly handle default config file when running as systemd/sysv daemon --- build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build.gradle b/build.gradle index 0bce02a..e538ddf 100644 --- a/build.gradle +++ b/build.gradle @@ -115,6 +115,17 @@ springBoot { ] } +processResources { + doLast { + copy { + from('build/resources/main/application.yaml') { + rename 'application.yaml', 'mxisd.yaml' + } + into 'build/resources/main' + } + } +} + task buildDeb(dependsOn: build) { doLast { def v = gitVersion() From ec0a9c7b802dbf996460c1f09f67772b3e645efb Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 16:20:46 +0200 Subject: [PATCH 23/28] log user agent for lookup requests --- .../io/kamax/mxisd/controller/v1/MappingController.groovy | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy index fdd9ef3..faefd0f 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy @@ -65,6 +65,8 @@ class MappingController { if (lookupReq.isRecursive()) { lookupReq.setRecurseHosts(Arrays.asList(xff.split(","))) } + + lookupReq.setUserAgent(req.getHeader("USER-AGENT")) } @RequestMapping(value = "/lookup", method = GET) @@ -74,7 +76,7 @@ class MappingController { lookupRequest.setType(medium) lookupRequest.setThreePid(address) - log.info("Got request from {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.isRecursive()) + log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()) Optional lookupOpt = strategy.find(lookupRequest) if (!lookupOpt.isPresent()) { @@ -99,7 +101,7 @@ class MappingController { String bulkLookup(HttpServletRequest request) { BulkLookupRequest lookupRequest = new BulkLookupRequest() setRequesterInfo(lookupRequest, request) - log.info("Got request from {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.isRecursive()) + log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()) ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText()) List mappings = new ArrayList<>() From 808aed2bc39949ddd73ce135a58c71233451cadb Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 16:21:09 +0200 Subject: [PATCH 24/28] log user agent for lookup requests --- .../groovy/io/kamax/mxisd/lookup/ALookupRequest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ALookupRequest.java b/src/main/groovy/io/kamax/mxisd/lookup/ALookupRequest.java index a668fd8..92bb392 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/ALookupRequest.java +++ b/src/main/groovy/io/kamax/mxisd/lookup/ALookupRequest.java @@ -26,6 +26,7 @@ public abstract class ALookupRequest { private String id; private String requester; + private String userAgent; private boolean isRecursive; private List recurseHosts; @@ -45,6 +46,14 @@ public abstract class ALookupRequest { this.requester = requester; } + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } + public boolean isRecursive() { return isRecursive; } From c00adcf575bba3200f060499375d92a618d765ea Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 16:25:21 +0200 Subject: [PATCH 25/28] Be consistent with annotations --- .../controller/v1/InvitationController.groovy | 6 +++++- .../mxisd/controller/v1/KeyController.groovy | 14 +++++++------- .../mxisd/controller/v1/SessionController.groovy | 16 ++++++++-------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy index aadf3ed..e83b00d 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy @@ -32,6 +32,8 @@ import io.kamax.mxisd.key.KeyManager import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @@ -41,6 +43,8 @@ import javax.servlet.http.HttpServletRequest import static org.springframework.web.bind.annotation.RequestMethod.POST @RestController +@CrossOrigin +@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) class InvitationController { private Logger log = LoggerFactory.getLogger(InvitationController.class) @@ -56,7 +60,7 @@ class InvitationController { private Gson gson = new Gson() - @RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST) + @RequestMapping(value = "/store-invite", method = POST) String store( HttpServletRequest request, @RequestParam String sender, diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy index f74b5bf..7c583e7 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy @@ -28,16 +28,16 @@ import org.apache.commons.lang.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.* import javax.servlet.http.HttpServletRequest import static org.springframework.web.bind.annotation.RequestMethod.GET @RestController +@CrossOrigin +@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) class KeyController { private Logger log = LoggerFactory.getLogger(KeyController.class) @@ -45,7 +45,7 @@ class KeyController { @Autowired private KeyManager keyMgr - @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/{keyType}:{keyId}", method = GET) + @RequestMapping(value = "/pubkey/{keyType}:{keyId}", method = GET) String getKey(@PathVariable String keyType, @PathVariable int keyId) { if (!"ed25519".contentEquals(keyType)) { throw new BadRequestException("Invalid algorithm: " + keyType) @@ -57,14 +57,14 @@ class KeyController { ]) } - @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/ephemeral/isvalid", method = GET) + @RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET) String checkEphemeralKeyValidity(HttpServletRequest request) { log.error("{} was requested but not implemented", request.getRequestURL()) throw new NotImplementedException() } - @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET) + @RequestMapping(value = "/pubkey/isvalid", method = GET) String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) { log.info("Validating public key {}", pubKey) 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 3fc9241..41aafcd 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy @@ -33,16 +33,16 @@ import org.apache.http.HttpStatus import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.* import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import java.nio.charset.StandardCharsets @RestController +@CrossOrigin +@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) class SessionController { @Autowired @@ -56,7 +56,7 @@ class SessionController { gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj) } - @RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/requestToken") + @RequestMapping(value = "/validate/{medium}/requestToken") String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) { log.info("Requested: {}", request.getRequestURL(), request.getQueryString()) @@ -77,7 +77,7 @@ class SessionController { return gson.toJson(obj) } - @RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/submitToken") + @RequestMapping(value = "/validate/{medium}/submitToken") String validate(HttpServletRequest request, @RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String token) { @@ -88,7 +88,7 @@ class SessionController { return "{}" } - @RequestMapping(value = "/_matrix/identity/api/v1/3pid/getValidated3pid") + @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()) @@ -115,7 +115,7 @@ class SessionController { } } - @RequestMapping(value = "/_matrix/identity/api/v1/3pid/bind") + @RequestMapping(value = "/3pid/bind") String bind(HttpServletRequest request, HttpServletResponse response, @RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) { String data = IOUtils.toString(request.getReader()) From 571506d1d2235f1a23b0bb145b6512004986a033 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 19:15:39 +0200 Subject: [PATCH 26/28] Add new placeholders for e-mail to access invited address and medium type --- application.example.yaml | 2 ++ .../io/kamax/mxisd/invitation/sender/EmailInviteSender.java | 2 ++ src/main/resources/email/invite-template.eml | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/application.example.yaml b/application.example.yaml index 0db19e2..67d8e3e 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -252,6 +252,8 @@ invite: # - %SENDER_ID% Matrix ID of the invitation sender # - %SENDER_NAME% Display name of the invitation sender, empty if not available # - %SENDER_NAME_OR_ID% Value of %SENDER_NAME% or, if empty, value of %SENDER_ID% + # - %INVITE_MEDIUM% Medium of the invite (e.g. email, msisdn) + # - %INVITE_ADDRESS% Address used to invite # - %ROOM_ID% ID of the room where the invitation took place # - %ROOM_NAME% Name of the room, empty if not available # - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID% diff --git a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java index fff553b..854c783 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/sender/EmailInviteSender.java @@ -104,6 +104,8 @@ public class EmailInviteSender implements IInviteSender { 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%", invite.getInvite().getMedium()); + templateBody = templateBody.replace("%INVITE_ADDRESS%", invite.getInvite().getAddress()); templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId()); templateBody = templateBody.replace("%ROOM_NAME%", roomName); templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId); diff --git a/src/main/resources/email/invite-template.eml b/src/main/resources/email/invite-template.eml index 0d85320..c422476 100644 --- a/src/main/resources/email/invite-template.eml +++ b/src/main/resources/email/invite-template.eml @@ -12,6 +12,9 @@ Hi, %SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on Matrix. To join the conversation, register an account on http://%DOMAIN% +You can also register an account on a public server, like Matrix.org, by going to +https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS% + About Matrix: @@ -69,6 +72,9 @@ pre, code {

%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on Matrix. To join the conversation, register an account on %DOMAIN%.

+

You can also register an account on a public server, like Matrix.org, by following +this link.

+

About Matrix:

From 9e6d3ab5dd063f896f3a8957b43e2343bd586e39 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Wed, 13 Sep 2017 21:27:29 +0200 Subject: [PATCH 27/28] Build error json object properly --- .../mxisd/controller/v1/DefaultExceptionHandler.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 9922370..1fb0f86 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java @@ -20,6 +20,8 @@ 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.MappingAlreadyExistsException; import org.apache.commons.lang.StringUtils; @@ -39,8 +41,13 @@ public class DefaultExceptionHandler { private Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class); + private static Gson gson = new Gson(); + static String handle(String erroCode, String error) { - return "{\"errcode\":\"" + erroCode + "\",\"error\":\"" + error + "\"}"; + JsonObject obj = new JsonObject(); + obj.addProperty("errcode", erroCode); + obj.addProperty("error", error); + return gson.toJson(obj); } @ResponseStatus(HttpStatus.BAD_REQUEST) From 5796982f2d6d9933ed96b712e61f7047764da4e5 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Thu, 14 Sep 2017 02:36:08 +0200 Subject: [PATCH 28/28] Add persistence storage for invites --- .gitignore | 3 + application.example.yaml | 23 +++- build.gradle | 12 ++ .../mxisd/config/SQLiteStorageConfig.java | 40 +++++++ .../io/kamax/mxisd/config/StorageConfig.java | 51 +++++++++ .../invitation/IThreePidInviteReply.java | 2 + .../mxisd/invitation/InvitationManager.java | 75 +++++++++---- .../mxisd/invitation/ThreePidInviteReply.java | 9 +- .../io/kamax/mxisd/storage/IStorage.java | 36 ++++++ .../storage/ormlite/OrmLiteSqliteStorage.java | 87 ++++++++++++++ .../OrmLiteSqliteStorageBeanFactory.java | 76 +++++++++++++ .../storage/ormlite/ThreePidInviteIO.java | 106 ++++++++++++++++++ src/main/resources/application.yaml | 3 + 13 files changed, 498 insertions(+), 25 deletions(-) create mode 100644 src/main/groovy/io/kamax/mxisd/config/SQLiteStorageConfig.java create mode 100644 src/main/groovy/io/kamax/mxisd/config/StorageConfig.java create mode 100644 src/main/groovy/io/kamax/mxisd/storage/IStorage.java create mode 100644 src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java create mode 100644 src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java create mode 100644 src/main/groovy/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java diff --git a/.gitignore b/.gitignore index 5a642a0..93fc794 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ out/ # Local dev config /application.yaml + +# Local dev storage +/mxisd.db diff --git a/application.example.yaml b/application.example.yaml index 67d8e3e..8b0a46c 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -244,7 +244,7 @@ invite: # - Message-Id # - X-Mailer # - # The following placeholders are possible: + # The following placeholders are available: # - %DOMAIN% Domain name as per server.name config item # - %DOMAIN_PRETTY% Word capitalize version of the domain. e.g. example.org -> Example.org # - %FROM_EMAIL% Value of this section's email config item @@ -258,3 +258,24 @@ invite: # - %ROOM_NAME% Name of the room, empty if not available # - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID% template: "/absolute/path/to/file" + + + +# Configure persistence settings +storage: + + # Configure the storage backend, usually a DB + # Possible built-in values: + # sqlite SQLite backend, default + # + #backend: 'sqlite' + + # Specific configuration for each provider, refer to their documentation for specifics. + provider: + + # Generic SQLite provider config + sqlite: + + # Path to the SQLite DB file, required + # + #database:'%SQLITE_DATABASE_PATH%' diff --git a/build.gradle b/build.gradle index e538ddf..2a53882 100644 --- a/build.gradle +++ b/build.gradle @@ -104,6 +104,12 @@ dependencies { // Google Firebase Authentication backend compile 'com.google.firebase:firebase-admin:5.3.0' + // ORMLite + compile 'com.j256.ormlite:ormlite-jdbc:5.0' + + // SQLite + compile 'org.xerial:sqlite-jdbc:3.20.0' + testCompile 'junit:junit:4.12' } @@ -161,6 +167,12 @@ task buildDeb(dependsOn: build) { value: "${debDataPath}/signing.key" ) + ant.replaceregexp( + file: "${debBuildConfPath}/${debConfFileName}", + match: "#?database:\\s*'%SQLITE_DATABASE_PATH%'", + replace: "database: '${debDataPath}/mxisd.db'" + ) + copy { from project.file('src/debian') into debBuildDebianPath diff --git a/src/main/groovy/io/kamax/mxisd/config/SQLiteStorageConfig.java b/src/main/groovy/io/kamax/mxisd/config/SQLiteStorageConfig.java new file mode 100644 index 0000000..8ca0680 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/SQLiteStorageConfig.java @@ -0,0 +1,40 @@ +/* + * 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.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties("storage.provider.sqlite") +public class SQLiteStorageConfig { + + private String database; + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/StorageConfig.java b/src/main/groovy/io/kamax/mxisd/config/StorageConfig.java new file mode 100644 index 0000000..247a424 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/StorageConfig.java @@ -0,0 +1,51 @@ +/* + * 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.config; + +import io.kamax.mxisd.exception.ConfigurationException; +import org.apache.commons.lang.StringUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +@Configuration +@ConfigurationProperties("storage") +public class StorageConfig { + + private String backend; + + public String getBackend() { + return backend; + } + + public void setBackend(String backend) { + this.backend = backend; + } + + @PostConstruct + private void postConstruct() { + if (StringUtils.isBlank(getBackend())) { + throw new ConfigurationException("storage.backend"); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java index cbe351f..cd80dba 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java @@ -22,6 +22,8 @@ package io.kamax.mxisd.invitation; public interface IThreePidInviteReply { + String getId(); + IThreePidInvite getInvite(); String getToken(); diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 83e9459..fd9560a 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -21,7 +21,7 @@ package io.kamax.mxisd.invitation; import com.google.gson.Gson; -import io.kamax.matrix.ThreePid; +import io.kamax.matrix.MatrixID; import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.MappingAlreadyExistsException; import io.kamax.mxisd.invitation.sender.IInviteSender; @@ -29,8 +29,11 @@ import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.signature.SignatureManager; +import io.kamax.mxisd.storage.IStorage; +import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang.StringUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.NoopHostnameVerifier; @@ -64,7 +67,10 @@ public class InvitationManager { private Logger log = LoggerFactory.getLogger(InvitationManager.class); - private Map invitations = new ConcurrentHashMap<>(); + private Map invitations = new ConcurrentHashMap<>(); + + @Autowired + private IStorage storage; @Autowired private LookupStrategy lookupMgr; @@ -78,10 +84,30 @@ public class InvitationManager { private Gson gson; private Timer refreshTimer; + private String getId(IThreePidInvite invite) { + return invite.getSender().getDomain() + invite.getMedium() + invite.getAddress(); + } + @PostConstruct private void postConstruct() { gson = new Gson(); + log.info("Loading saved invites"); + Collection ioList = storage.getInvites(); + ioList.forEach(io -> { + log.info("Processing invite {}", gson.toJson(io)); + ThreePidInvite invite = new ThreePidInvite( + new MatrixID(io.getSender()), + io.getMedium(), + io.getAddress(), + io.getRoomId(), + io.getProperties() + ); + + ThreePidInviteReply reply = new ThreePidInviteReply(getId(invite), invite, io.getToken(), ""); + invitations.put(reply.getId(), reply); + }); + // FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver try { SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); @@ -93,6 +119,7 @@ public class InvitationManager { throw new RuntimeException(e); } + log.info("Setting up invitation mapping refresh timer"); refreshTimer = new Timer(); refreshTimer.scheduleAtFixedRate(new TimerTask() { @Override @@ -166,30 +193,31 @@ public class InvitationManager { throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported"); } - ThreePid pid = new ThreePid(invitation.getMedium(), invitation.getAddress()); - - log.info("Handling invite for {}:{} from {} in room {}", pid.getMedium(), pid.getAddress(), invitation.getSender(), invitation.getRoomId()); - if (invitations.containsKey(pid)) { - log.info("Invite is already pending for {}:{}, returning data", pid.getMedium(), pid.getAddress()); - return invitations.get(pid); + String invId = getId(invitation); + log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId()); + if (invitations.containsKey(invId)) { // FIXME we need to lookup using the HS domain too!! + log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress()); + return invitations.get(invId); } Optional result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true); if (result.isPresent()) { - log.info("Mapping for {}:{} already exists, refusing to store invite", pid.getMedium(), pid.getAddress()); + log.info("Mapping for {}:{} already exists, refusing to store invite", invitation.getMedium(), invitation.getAddress()); throw new MappingAlreadyExistsException(); } String token = RandomStringUtils.randomAlphanumeric(64); String displayName = invitation.getAddress().substring(0, 3) + "..."; - IThreePidInviteReply reply = new ThreePidInviteReply(invitation, token, displayName); + IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName); - log.info("Performing invite to {}:{}", pid.getMedium(), pid.getAddress()); + log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress()); sender.send(reply); - invitations.put(pid, reply); - log.info("A new invite has been created for {}:{}", pid.getMedium(), pid.getAddress()); + log.info("Storing invite under ID {}", invId); + storage.insertInvite(reply); + invitations.put(invId, reply); + log.info("A new invite has been created for {}:{} on HS {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender().getDomain()); return reply; } @@ -203,13 +231,12 @@ public class InvitationManager { } public void publishMappingIfInvited(ThreePidMapping threePid) { - ThreePid key = new ThreePid(threePid.getMedium(), threePid.getValue()); - IThreePidInviteReply reply = invitations.get(key); - if (reply == null) { - log.info("{}:{} does not have a pending invite, no mapping to publish", threePid.getMedium(), threePid.getValue()); - } else { - log.info("{}:{} has an invite pending, publishing mapping", threePid.getMedium(), threePid.getValue()); - publishMapping(reply, threePid.getMxid()); + log.info("Looking up possible pending invites for {}:{}", threePid.getMedium(), threePid.getValue()); + for (IThreePidInviteReply reply : invitations.values()) { + if (StringUtils.equals(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equals(reply.getInvite().getAddress(), threePid.getValue())) { + log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(), reply.getInvite().getSender().getDomain()); + publishMapping(reply, threePid.getMxid()); + } } } @@ -255,15 +282,17 @@ public class InvitationManager { CloseableHttpResponse response = client.execute(req); int statusCode = response.getStatusLine().getStatusCode(); log.info("Answer code: {}", statusCode); - if (statusCode >= 400) { + if (statusCode >= 300) { log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); + } else { + invitations.remove(getId(reply)); + storage.deleteInvite(reply.getId()); + log.info("Removed invite from internal store"); } response.close(); } catch (IOException e) { log.warn("Unable to tell HS {} about invite being mapped", domain, e); } - invitations.remove(new ThreePid(medium, address)); - log.info("Removed invite from internal store"); }).start(); } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java index 5a02c2e..c67139e 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java @@ -22,16 +22,23 @@ package io.kamax.mxisd.invitation; public class ThreePidInviteReply implements IThreePidInviteReply { + private String id; private IThreePidInvite invite; private String token; private String displayName; - public ThreePidInviteReply(IThreePidInvite invite, String token, String displayName) { + public ThreePidInviteReply(String id, IThreePidInvite invite, String token, String displayName) { + this.id = id; this.invite = invite; this.token = token; this.displayName = displayName; } + @Override + public String getId() { + return id; + } + @Override public IThreePidInvite getInvite() { return invite; diff --git a/src/main/groovy/io/kamax/mxisd/storage/IStorage.java b/src/main/groovy/io/kamax/mxisd/storage/IStorage.java new file mode 100644 index 0000000..b77c50b --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/storage/IStorage.java @@ -0,0 +1,36 @@ +/* + * 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.storage; + +import io.kamax.mxisd.invitation.IThreePidInviteReply; +import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO; + +import java.util.Collection; + +public interface IStorage { + + Collection getInvites(); + + void insertInvite(IThreePidInviteReply data); + + void deleteInvite(String id); + +} diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java new file mode 100644 index 0000000..1a37579 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java @@ -0,0 +1,87 @@ +/* + * 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.storage.ormlite; + +import com.j256.ormlite.dao.CloseableWrappedIterable; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.jdbc.JdbcConnectionSource; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.TableUtils; +import io.kamax.mxisd.invitation.IThreePidInviteReply; +import io.kamax.mxisd.storage.IStorage; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class OrmLiteSqliteStorage implements IStorage { + + private Dao invDao; + + OrmLiteSqliteStorage(String path) { + try { + ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path); + invDao = DaoManager.createDao(connPool, ThreePidInviteIO.class); + TableUtils.createTableIfNotExists(connPool, ThreePidInviteIO.class); + } catch (SQLException e) { + throw new RuntimeException(e); // FIXME do better + } + } + + @Override + public Collection getInvites() { + try (CloseableWrappedIterable t = invDao.getWrappedIterable()) { + List ioList = new ArrayList<>(); + t.forEach(ioList::add); + return ioList; + } catch (IOException e) { + throw new RuntimeException(e); // FIXME do better + } + } + + @Override + public void insertInvite(IThreePidInviteReply data) { + try { + int updated = invDao.create(new ThreePidInviteIO(data)); + if (updated != 1) { + throw new RuntimeException("Unexpected row count after DB action: " + updated); + } + } catch (SQLException e) { + throw new RuntimeException(e); // FIXME do better + } + } + + @Override + public void deleteInvite(String id) { + try { + int updated = invDao.deleteById(id); + if (updated != 1) { + throw new RuntimeException("Unexpected row count after DB action: " + updated); + } + } catch (SQLException e) { + throw new RuntimeException(e); // FIXME do better + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java new file mode 100644 index 0000000..724dada --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java @@ -0,0 +1,76 @@ +/* + * 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.storage.ormlite; + +import io.kamax.mxisd.config.SQLiteStorageConfig; +import io.kamax.mxisd.config.StorageConfig; +import io.kamax.mxisd.exception.ConfigurationException; +import io.kamax.mxisd.storage.IStorage; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +@Component +public class OrmLiteSqliteStorageBeanFactory implements FactoryBean { + + @Autowired + private StorageConfig storagecfg; + + @Autowired + private SQLiteStorageConfig cfg; + + private OrmLiteSqliteStorage storage; + + @PostConstruct + private void postConstruct() { + if (StringUtils.equals("sqlite", storagecfg.getBackend())) { + if (StringUtils.isBlank(cfg.getDatabase())) { + throw new ConfigurationException("storage.provider.sqlite.database"); + } + + storage = new OrmLiteSqliteStorage(cfg.getDatabase()); + } + } + + @Override + public IStorage getObject() throws Exception { + if (storage == null) { + throw new FactoryBeanNotInitializedException(); + } + + return storage; + } + + @Override + public Class getObjectType() { + return OrmLiteSqliteStorage.class; + } + + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java new file mode 100644 index 0000000..91a9198 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java @@ -0,0 +1,106 @@ +/* + * 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.storage.ormlite; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; +import io.kamax.mxisd.invitation.IThreePidInviteReply; +import org.apache.commons.lang.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +@DatabaseTable(tableName = "invite_3pid") +public class ThreePidInviteIO { + + private static Gson gson = new Gson(); + + @DatabaseField(id = true) + private String id; + + @DatabaseField(canBeNull = false) + private String token; + + @DatabaseField(canBeNull = false) + private String sender; + + @DatabaseField(canBeNull = false) + private String medium; + + @DatabaseField(canBeNull = false) + private String address; + + @DatabaseField(canBeNull = false) + private String roomId; + + @DatabaseField + private String properties; + + public ThreePidInviteIO() { + // needed for ORMlite + } + + public ThreePidInviteIO(IThreePidInviteReply data) { + this.id = data.getId(); + this.token = data.getToken(); + this.sender = data.getInvite().getSender().getId(); + this.medium = data.getInvite().getMedium(); + this.address = data.getInvite().getAddress(); + this.roomId = data.getInvite().getRoomId(); + this.properties = gson.toJson(data.getInvite().getProperties()); + } + + public String getId() { + return id; + } + + public String getToken() { + return token; + } + + public String getSender() { + return sender; + } + + public String getMedium() { + return medium; + } + + public String getAddress() { + return address; + } + + public String getRoomId() { + return roomId; + } + + public Map getProperties() { + if (StringUtils.isBlank(properties)) { + return new HashMap<>(); + } + + return gson.fromJson(properties, new TypeToken>() { + }.getType()); + } + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 74984af..3f11732 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -40,3 +40,6 @@ invite: tls: 1 name: "mxisd Identity Server" template: "classpath:email/invite-template.eml" + +storage: + backend: 'sqlite'