Compare commits

...

5 Commits

Author SHA1 Message Date
Maxime Dor
c816217b22 Send new invite notification to same user if rooms are different 2017-09-28 02:45:01 +02:00
Maxime Dor
2e7b5d2a87 Refactor packages (cosmetic) 2017-09-27 03:59:45 +02:00
Max Dor
09208d55d7 Merge pull request #33 from kamax-io/email-connector-sendgrid
Email connector sendgrid
2017-09-27 03:33:13 +02:00
Maxime Dor
05c76a657e Fix extra placeholders in smtp sender 2017-09-27 03:30:53 +02:00
Maxime Dor
f3bbc7c7c6 Add support for SendGrid as Email notification handler 2017-09-27 01:55:37 +02:00
38 changed files with 603 additions and 115 deletions

View File

@@ -119,6 +119,9 @@ dependencies {
// Twilio SDK for SMS // Twilio SDK for SMS
compile 'com.twilio.sdk:twilio:7.14.5' compile 'com.twilio.sdk:twilio:7.14.5'
// SendGrid SDK to send emails from GCE
compile 'com.sendgrid:sendgrid-java:2.2.2'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'com.github.tomakehurst:wiremock:2.8.0' testCompile 'com.github.tomakehurst:wiremock:2.8.0'
} }

View File

@@ -0,0 +1,202 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config.threepid.connector;
import io.kamax.mxisd.util.GsonUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("notification.handlers.sendgrid")
public class EmailSendGridConfig {
public static class EmailTemplate {
public static class EmailBody {
private String text;
private String html;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
}
private String subject;
private EmailBody body = new EmailBody();
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public EmailBody getBody() {
return body;
}
public void setBody(EmailBody body) {
this.body = body;
}
}
public static class Api {
private String key;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
public static class Identity {
private String from;
private String name;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class Templates {
public static class TemplateSession {
private EmailTemplate local = new EmailTemplate();
private EmailTemplate remote = new EmailTemplate();
public EmailTemplate getLocal() {
return local;
}
public void setLocal(EmailTemplate local) {
this.local = local;
}
public EmailTemplate getRemote() {
return remote;
}
public void setRemote(EmailTemplate remote) {
this.remote = remote;
}
}
private EmailTemplate invite = new EmailTemplate();
private TemplateSession session = new TemplateSession();
public EmailTemplate getInvite() {
return invite;
}
public void setInvite(EmailTemplate invite) {
this.invite = invite;
}
public TemplateSession getSession() {
return session;
}
public void setSession(TemplateSession session) {
this.session = session;
}
}
private Logger log = LoggerFactory.getLogger(EmailSendGridConfig.class);
private Api api = new Api();
private Identity identity = new Identity();
private Templates templates = new Templates();
public Api getApi() {
return api;
}
public void setApi(Api api) {
this.api = api;
}
public Identity getIdentity() {
return identity;
}
public void setIdentity(Identity identity) {
this.identity = identity;
}
public Templates getTemplates() {
return templates;
}
public void setTemplates(Templates templates) {
this.templates = templates;
}
@PostConstruct
public void build() {
log.info("--- Email SendGrid connector config ---");
log.info("API key configured?: {}", StringUtils.isNotBlank(api.getKey()));
log.info("Identity: {}", GsonUtil.build().toJson(identity));
log.info("Templates: {}", GsonUtil.build().toJson(templates));
}
}

View File

@@ -0,0 +1,57 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config.threepid.notification;
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.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties("notification")
public class NotificationConfig {
private Logger log = LoggerFactory.getLogger(NotificationConfig.class);
private Map<String, String> handler = new HashMap<>();
public Map<String, String> getHandler() {
return handler;
}
public void setHandler(Map<String, String> handler) {
this.handler = handler;
}
@PostConstruct
public void build() {
log.info("--- Notification config ---");
log.info("Handlers:");
handler.forEach((k, v) -> {
log.info("\t{}: {}", k, v);
});
}
}

View File

@@ -18,14 +18,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.auth.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.mxisd.auth.AuthManager; import io.kamax.mxisd.auth.AuthManager;
import io.kamax.mxisd.auth.UserAuthResult; import io.kamax.mxisd.auth.UserAuthResult;
import io.kamax.mxisd.controller.v1.io.CredentialsValidationResponse; import io.kamax.mxisd.controller.auth.v1.io.CredentialsValidationResponse;
import io.kamax.mxisd.exception.JsonMemberNotFoundException; import io.kamax.mxisd.exception.JsonMemberNotFoundException;
import io.kamax.mxisd.util.GsonParser; import io.kamax.mxisd.util.GsonParser;
import io.kamax.mxisd.util.GsonUtil; import io.kamax.mxisd.util.GsonUtil;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.auth.v1.io;
import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.ThreePid;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
public class IdentityAPIv1 { public class IdentityAPIv1 {

View File

@@ -18,12 +18,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO; import io.kamax.mxisd.controller.identity.v1.io.ThreePidInviteReplyIO;
import io.kamax.mxisd.invitation.IThreePidInvite; import io.kamax.mxisd.invitation.IThreePidInvite;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.InvitationManager;

View File

@@ -18,11 +18,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.mxisd.controller.v1.io.KeyValidityJson; import io.kamax.mxisd.controller.identity.v1.io.KeyValidityJson;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.key.KeyManager; import io.kamax.mxisd.key.KeyManager;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;

View File

@@ -18,11 +18,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson; import io.kamax.mxisd.controller.identity.v1.io.SingeLookupReplyJson;
import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.lookup.*; import io.kamax.mxisd.lookup.*;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;

View File

@@ -18,11 +18,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.ViewConfig; import io.kamax.mxisd.config.ViewConfig;
import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1; import io.kamax.mxisd.controller.identity.v1.remote.RemoteIdentityAPIv1;
import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.session.SessionMananger; import io.kamax.mxisd.session.SessionMananger;
import io.kamax.mxisd.session.ValidationResult; import io.kamax.mxisd.session.ValidationResult;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@@ -26,9 +26,9 @@ import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.ViewConfig; import io.kamax.mxisd.config.ViewConfig;
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson; import io.kamax.mxisd.controller.identity.v1.io.SessionEmailTokenRequestJson;
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson; import io.kamax.mxisd.controller.identity.v1.io.SessionPhoneTokenRequestJson;
import io.kamax.mxisd.controller.v1.io.SuccessStatusJson; import io.kamax.mxisd.controller.identity.v1.io.SuccessStatusJson;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.SessionNotValidatedException; import io.kamax.mxisd.exception.SessionNotValidatedException;
import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.InvitationManager;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1; package io.kamax.mxisd.controller.identity.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
public abstract class GenericTokenRequestJson { public abstract class GenericTokenRequestJson {

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
public class KeyValidityJson { public class KeyValidityJson {

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
public class RequestTokenResponse { public class RequestTokenResponse {

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
public class SessionEmailTokenRequestJson extends GenericTokenRequestJson { public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.PhoneNumberUtil;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
public class SuccessStatusJson { public class SuccessStatusJson {

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.io; package io.kamax.mxisd.controller.identity.v1.io;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.controller.v1.remote; package io.kamax.mxisd.controller.identity.v1.remote;
public class RemoteIdentityAPIv1 { public class RemoteIdentityAPIv1 {

View File

@@ -1,4 +1,4 @@
package io.kamax.mxisd.controller.v1.remote; package io.kamax.mxisd.controller.identity.v1.remote;
import io.kamax.mxisd.config.ViewConfig; import io.kamax.mxisd.config.ViewConfig;
import io.kamax.mxisd.exception.SessionNotValidatedException; import io.kamax.mxisd.exception.SessionNotValidatedException;
@@ -14,8 +14,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import static io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1.SESSION_CHECK; import static io.kamax.mxisd.controller.identity.v1.remote.RemoteIdentityAPIv1.SESSION_CHECK;
import static io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1.SESSION_REQUEST_TOKEN; import static io.kamax.mxisd.controller.identity.v1.remote.RemoteIdentityAPIv1.SESSION_REQUEST_TOKEN;
@Controller @Controller
public class RemoteSessionController { public class RemoteSessionController {

View File

@@ -206,9 +206,16 @@ public class InvitationManager {
String invId = getId(invitation); String invId = getId(invitation);
log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId()); log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId());
if (invitations.containsKey(invId)) { IThreePidInviteReply reply = invitations.get(invId);
if (reply != null) {
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress()); log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
return invitations.get(invId); if (!StringUtils.equals(invitation.getRoomId(), reply.getInvite().getRoomId())) {
log.info("Sending new notification as new invite room {} is different from the original {}", invitation.getRoomId(), reply.getInvite().getRoomId());
notifMgr.sendForInvite(new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName()));
} else {
// FIXME we should check attempt and send if bigger
}
return reply;
} }
Optional<?> result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true); Optional<?> result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true);
@@ -220,7 +227,7 @@ public class InvitationManager {
String token = RandomStringUtils.randomAlphanumeric(64); String token = RandomStringUtils.randomAlphanumeric(64);
String displayName = invitation.getAddress().substring(0, 3) + "..."; String displayName = invitation.getAddress().substring(0, 3) + "...";
IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName); reply = new ThreePidInviteReply(invId, invitation, token, displayName);
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress()); log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
notifMgr.sendForInvite(reply); notifMgr.sendForInvite(reply);

View File

@@ -24,7 +24,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID; import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson; import io.kamax.mxisd.controller.identity.v1.io.SingeLookupReplyJson;
import java.time.Instant; import java.time.Instant;

View File

@@ -23,7 +23,7 @@ package io.kamax.mxisd.lookup.provider;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest; import io.kamax.mxisd.controller.identity.v1.ClientBulkLookupRequest;
import io.kamax.mxisd.exception.InvalidResponseJsonException; import io.kamax.mxisd.exception.InvalidResponseJsonException;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;

View File

@@ -25,6 +25,8 @@ import io.kamax.mxisd.threepid.session.IThreePidSession;
public interface INotificationHandler { public interface INotificationHandler {
String getId();
String getMedium(); String getMedium();
void sendForInvite(IThreePidInviteReply invite); void sendForInvite(IThreePidInviteReply invite);

View File

@@ -20,9 +20,13 @@
package io.kamax.mxisd.notification; package io.kamax.mxisd.notification;
import io.kamax.mxisd.config.threepid.notification.NotificationConfig;
import io.kamax.mxisd.exception.NotImplementedException; import io.kamax.mxisd.exception.NotImplementedException;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession; import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -33,12 +37,25 @@ import java.util.Map;
@Component @Component
public class NotificationManager { public class NotificationManager {
private Logger log = LoggerFactory.getLogger(NotificationManager.class);
private Map<String, INotificationHandler> handlers; private Map<String, INotificationHandler> handlers;
@Autowired @Autowired
public NotificationManager(List<INotificationHandler> handlers) { public NotificationManager(NotificationConfig cfg, List<INotificationHandler> handlers) {
this.handlers = new HashMap<>(); this.handlers = new HashMap<>();
handlers.forEach(h -> this.handlers.put(h.getMedium(), h)); handlers.forEach(h -> {
log.info("Found handler {} for medium {}", h.getId(), h.getMedium());
String handlerId = cfg.getHandler().get(h.getMedium());
if (StringUtils.isBlank(handlerId) || StringUtils.equals(handlerId, h.getId())) {
this.handlers.put(h.getMedium(), h);
}
});
log.info("--- Notification handler ---");
this.handlers.forEach((k, v) -> {
log.info("\tHandler for {}: {}", k, v.getId());
});
} }
private INotificationHandler ensureMedium(String medium) { private INotificationHandler ensureMedium(String medium) {
@@ -46,7 +63,6 @@ public class NotificationManager {
if (handler == null) { if (handler == null) {
throw new NotImplementedException(medium + " is not a supported 3PID medium type"); throw new NotImplementedException(medium + " is not a supported 3PID medium type");
} }
return handler; return handler;
} }

View File

@@ -30,8 +30,8 @@ import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.SessionConfig; import io.kamax.mxisd.config.SessionConfig;
import io.kamax.mxisd.controller.v1.io.RequestTokenResponse; import io.kamax.mxisd.controller.identity.v1.io.RequestTokenResponse;
import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1; import io.kamax.mxisd.controller.identity.v1.remote.RemoteIdentityAPIv1;
import io.kamax.mxisd.exception.*; import io.kamax.mxisd.exception.*;
import io.kamax.mxisd.lookup.ThreePidValidation; import io.kamax.mxisd.lookup.ThreePidValidation;
import io.kamax.mxisd.matrix.IdentityServerUtils; import io.kamax.mxisd.matrix.IdentityServerUtils;

View File

@@ -0,0 +1,136 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.connector.email;
import com.sendgrid.SendGrid;
import com.sendgrid.SendGridException;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.threepid.connector.EmailSendGridConfig;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.notification.INotificationHandler;
import io.kamax.mxisd.threepid.notification.PlaceholderNotificationGenerator;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static com.sendgrid.SendGrid.Email;
import static com.sendgrid.SendGrid.Response;
import static io.kamax.mxisd.config.threepid.connector.EmailSendGridConfig.EmailTemplate;
@Component
public class EmailSendGridNotificationHandler extends PlaceholderNotificationGenerator implements INotificationHandler {
private Logger log = LoggerFactory.getLogger(EmailSendGridNotificationHandler.class);
private EmailSendGridConfig cfg;
private SendGrid sendgrid;
@Autowired
public EmailSendGridNotificationHandler(MatrixConfig mxCfg, ServerConfig srvCfg, EmailSendGridConfig cfg) {
super(mxCfg, srvCfg);
this.cfg = cfg;
this.sendgrid = new SendGrid(cfg.getApi().getKey());
}
@Override
public String getId() {
return "sendgrid";
}
@Override
public String getMedium() {
return ThreePidMedium.Email.getId();
}
protected Email getEmail() {
Email email = new Email();
email.setFrom(cfg.getIdentity().getFrom());
email.setFromName(cfg.getIdentity().getName());
return email;
}
private String getFromFile(String path) {
try {
return IOUtils.toString(new FileInputStream(path), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Couldn't create notification content using file " + path, e);
}
}
@Override
public void sendForInvite(IThreePidInviteReply invite) {
EmailTemplate template = cfg.getTemplates().getInvite();
Email email = getEmail();
email.setSubject(populateForInvite(invite, template.getSubject()));
email.setText(populateForInvite(invite, getFromFile(template.getBody().getText())));
email.setHtml(populateForInvite(invite, getFromFile(template.getBody().getHtml())));
send(invite.getInvite().getAddress(), email);
}
@Override
public void sendForValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
Email email = getEmail();
email.setSubject(populateForValidation(session, template.getSubject()));
email.setText(populateForValidation(session, getFromFile(template.getBody().getText())));
email.setHtml(populateForValidation(session, getFromFile(template.getBody().getHtml())));
send(session.getThreePid().getAddress(), email);
}
@Override
public void sendForRemoteValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
Email email = getEmail();
email.setSubject(populateForRemoteValidation(session, template.getSubject()));
email.setText(populateForRemoteValidation(session, getFromFile(template.getBody().getText())));
email.setHtml(populateForRemoteValidation(session, getFromFile(template.getBody().getHtml())));
send(session.getThreePid().getAddress(), email);
}
private void send(String recipient, Email email) {
try {
email.addTo(recipient);
email.setFrom(cfg.getIdentity().getFrom());
email.setFromName(cfg.getIdentity().getName());
Response response = sendgrid.send(email);
if (response.getStatus()) {
log.info("Successfully sent email to {} using SendGrid", recipient);
} else {
throw new RuntimeException("Error sending via SendGrid to " + recipient + ": " + response.getMessage());
}
} catch (SendGridException e) {
throw new RuntimeException("Unable to send e-mail invite via SendGrid to " + recipient, e);
}
}
}

View File

@@ -20,17 +20,14 @@
package io.kamax.mxisd.threepid.notification; package io.kamax.mxisd.threepid.notification;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.threepid.medium.GenericTemplateConfig; import io.kamax.mxisd.config.threepid.medium.GenericTemplateConfig;
import io.kamax.mxisd.controller.v1.IdentityAPIv1;
import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession; import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -43,39 +40,20 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@Component @Component
public abstract class GenericTemplateNotificationGenerator implements INotificationGenerator { public abstract class GenericTemplateNotificationGenerator extends PlaceholderNotificationGenerator implements INotificationGenerator {
private Logger log = LoggerFactory.getLogger(GenericTemplateNotificationGenerator.class); private Logger log = LoggerFactory.getLogger(GenericTemplateNotificationGenerator.class);
private MatrixConfig mxCfg;
private ServerConfig srvCfg;
private GenericTemplateConfig cfg; private GenericTemplateConfig cfg;
@Autowired @Autowired
private ApplicationContext app; private ApplicationContext app;
public GenericTemplateNotificationGenerator(MatrixConfig mxCfg, ServerConfig srvCfg, GenericTemplateConfig cfg) { public GenericTemplateNotificationGenerator(MatrixConfig mxCfg, ServerConfig srvCfg, GenericTemplateConfig cfg) {
this.mxCfg = mxCfg; super(mxCfg, srvCfg);
this.srvCfg = srvCfg;
this.cfg = cfg; this.cfg = cfg;
} }
protected String populateForCommon(String body, ThreePid recipient) {
return body;
}
private String populateCommon(String body, ThreePid recipient) {
body = populateForCommon(body, recipient);
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
body = body.replace("%DOMAIN%", mxCfg.getDomain());
body = body.replace("%DOMAIN_PRETTY%", domainPretty);
body = body.replace("%RECIPIENT_MEDIUM%", recipient.getMedium());
body = body.replace("%RECIPIENT_ADDRESS%", recipient.getAddress());
return body;
}
private String getTemplateContent(String location) { private String getTemplateContent(String location) {
try { try {
InputStream is = StringUtils.startsWith(location, "classpath:") ? InputStream is = StringUtils.startsWith(location, "classpath:") ?
@@ -86,65 +64,22 @@ public abstract class GenericTemplateNotificationGenerator implements INotificat
} }
} }
private String getTemplateAndPopulate(String location, ThreePid recipient) {
return populateCommon(getTemplateContent(location), recipient);
}
@Override @Override
public String getForInvite(IThreePidInviteReply invite) { public String getForInvite(IThreePidInviteReply invite) {
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress()); log.info("Generating notification content for 3PID invite");
String templateBody = getTemplateAndPopulate(cfg.getInvite(), tpid); return populateForInvite(invite, getTemplateContent(cfg.getInvite()));
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
templateBody = templateBody.replace("%SENDER_NAME%", senderName);
templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
templateBody = templateBody.replace("%INVITE_MEDIUM%", tpid.getMedium());
templateBody = templateBody.replace("%INVITE_ADDRESS%", tpid.getAddress());
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
templateBody = templateBody.replace("%ROOM_NAME%", roomName);
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
return templateBody;
} }
@Override @Override
public String getForValidation(IThreePidSession session) { public String getForValidation(IThreePidSession session) {
log.info("Generating notification content for 3PID Session validation"); log.info("Generating notification content for 3PID Session validation");
String templateBody = getTemplateAndPopulate(cfg.getSession().getValidation().getLocal(), session.getThreePid()); return populateForValidation(session, getTemplateContent(cfg.getSession().getValidation().getLocal()));
String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.getValidate(
session.getThreePid().getMedium(),
session.getId(),
session.getSecret(),
session.getToken());
templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink);
templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken());
return templateBody;
} }
@Override @Override
public String getForRemoteValidation(IThreePidSession session) { public String getForRemoteValidation(IThreePidSession session) {
log.info("Generating notification content for remote-only 3PID session"); log.info("Generating notification content for remote-only 3PID session");
String templateBody = getTemplateAndPopulate(cfg.getSession().getValidation().getRemote(), session.getThreePid()); return populateForRemoteValidation(session, getTemplateContent(cfg.getSession().getValidation().getRemote()));
String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.getValidate(
session.getThreePid().getMedium(),
session.getId(),
session.getSecret(),
session.getToken());
templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink);
templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken());
templateBody = templateBody.replace("%NEXT_URL%", validationLink);
return templateBody;
} }
} }

View File

@@ -0,0 +1,89 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.notification;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.controller.identity.v1.IdentityAPIv1;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
public abstract class PlaceholderNotificationGenerator {
private MatrixConfig mxCfg;
private ServerConfig srvCfg;
public PlaceholderNotificationGenerator(MatrixConfig mxCfg, ServerConfig srvCfg) {
this.mxCfg = mxCfg;
this.srvCfg = srvCfg;
}
protected String populateForCommon(String input, ThreePid recipient) {
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
return input
.replace("%DOMAIN%", mxCfg.getDomain())
.replace("%DOMAIN_PRETTY%", domainPretty)
.replace("%RECIPIENT_MEDIUM%", recipient.getMedium())
.replace("%RECIPIENT_ADDRESS%", recipient.getAddress());
}
protected String populateForInvite(IThreePidInviteReply invite, String input) {
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
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());
return populateForCommon(input, tpid)
.replace("%SENDER_ID%", invite.getInvite().getSender().getId())
.replace("%SENDER_NAME%", senderName)
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
.replace("%INVITE_MEDIUM%", tpid.getMedium())
.replace("%INVITE_ADDRESS%", tpid.getAddress())
.replace("%ROOM_ID%", invite.getInvite().getRoomId())
.replace("%ROOM_NAME%", roomName)
.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
}
protected String populateForValidation(IThreePidSession session, String input) {
String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.getValidate(
session.getThreePid().getMedium(),
session.getId(),
session.getSecret(),
session.getToken()
);
return populateForCommon(input, session.getThreePid())
.replace("%VALIDATION_LINK%", validationLink)
.replace("%VALIDATION_TOKEN%", session.getToken())
.replace("%NEXT_URL%", validationLink);
}
protected String populateForRemoteValidation(IThreePidSession session, String input) {
return populateForValidation(session, input);
}
}

View File

@@ -47,6 +47,7 @@ public class EmailNotificationGenerator extends GenericTemplateNotificationGener
@Override @Override
protected String populateForCommon(String body, ThreePid recipient) { protected String populateForCommon(String body, ThreePid recipient) {
body = super.populateForCommon(body, recipient);
body = body.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom()); body = body.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom());
body = body.replace("%FROM_NAME%", cfg.getIdentity().getName()); body = body.replace("%FROM_NAME%", cfg.getIdentity().getName());
return body; return body;

View File

@@ -30,16 +30,21 @@ import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
@Component @Component
public class EmailNotificationHandler extends GenericNotificationHandler<IEmailConnector, IEmailNotificationGenerator> { public class EmailRawNotificationHandler extends GenericNotificationHandler<IEmailConnector, IEmailNotificationGenerator> {
private EmailConfig cfg; private EmailConfig cfg;
@Autowired @Autowired
public EmailNotificationHandler(EmailConfig cfg, List<IEmailNotificationGenerator> generators, List<IEmailConnector> connectors) { public EmailRawNotificationHandler(EmailConfig cfg, List<IEmailNotificationGenerator> generators, List<IEmailConnector> connectors) {
this.cfg = cfg; this.cfg = cfg;
process(connectors, generators); process(connectors, generators);
} }
@Override
public String getId() {
return "raw";
}
@Override @Override
public String getMedium() { public String getMedium() {
return ThreePidMedium.Email.getId(); return ThreePidMedium.Email.getId();

View File

@@ -40,6 +40,11 @@ public class PhoneNotificationHandler extends GenericNotificationHandler<IPhoneC
process(connectors, generators); process(connectors, generators);
} }
@Override
public String getId() {
return "raw";
}
@Override @Override
public String getMedium() { public String getMedium() {
return ThreePidMedium.PhoneNumber.getId(); return ThreePidMedium.PhoneNumber.getId();

View File

@@ -97,6 +97,7 @@ threepid:
validation: validation:
local: 'classpath:threepids/email/validate-local-template.eml' local: 'classpath:threepids/email/validate-local-template.eml'
remote: 'classpath:threepids/email/validate-remote-template.eml' remote: 'classpath:threepids/email/validate-remote-template.eml'
msisdn: msisdn:
connector: 'twilio' connector: 'twilio'
generator: 'template' generator: 'template'
@@ -130,6 +131,35 @@ session:
enabled: true enabled: true
server: 'root' server: 'root'
notification:
# handler:
# 3PID-medium: 'handlerId'
handlers:
sendgrid:
api:
key: ''
identity:
from: ''
name: ''
templates:
invite:
subject: ''
body:
text: ''
html: ''
session:
validation:
local:
subject: ''
body:
text: ''
html: ''
remote:
subject: ''
body:
text: ''
html: ''
view: view:
session: session:
local: local: