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