Add various Notification template generator improvements
- Add ability to set arbitrary value for some placeholders (Fix #133) - More Unit tests - Improve doc
This commit is contained in:
@@ -1,19 +1,99 @@
|
|||||||
# Notifications: Generate from templates
|
# Notifications: Template generator
|
||||||
To create notification content, you can use the `template` generator if supported for the 3PID medium which will read
|
Most of the Identity actions will trigger a notification of some kind, informing the user of some confirmation, next step
|
||||||
content from configured files.
|
or just informing them about the current state of things.
|
||||||
|
|
||||||
Placeholders can be integrated into the templates to dynamically populate such content with relevant information like
|
Those notifications are by default generated from templates and by replacing placeholder tokens in them with the relevant
|
||||||
the 3PID that was requested, the domain of your Identity server, etc.
|
values of the notification. It is possible to customize the value of some placeholders, making easy to set values in the builtin templates, and/or
|
||||||
|
provide your own custom templates.
|
||||||
|
|
||||||
Templates can be configured for each event that would send a notification to the end user. Events share a set of common
|
Templates for the following events/actions are available:
|
||||||
placeholders and also have their own individual set of placeholders.
|
- [3PID invite](../../features/identity.md)
|
||||||
|
- [3PID session: validation](../session/session.md)
|
||||||
|
- [3PID session: fraudulent unbind](https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#improving-your-privacy-one-commit-at-the-time)
|
||||||
|
- [Matrix ID invite](../../features/experimental/application-service.md#email-notification-about-room-invites-by-matrix-ids)
|
||||||
|
|
||||||
|
## Placeholders
|
||||||
|
All placeholders **MUST** be surrounded with `%` in the template. Per example, the `DOMAIN` placeholder would become
|
||||||
|
`%DOMAIN%` within the template. This ensures replacement doesn't happen on non-placeholder strings.
|
||||||
|
|
||||||
|
### Global
|
||||||
|
The following placeholders are available in every template:
|
||||||
|
|
||||||
|
| Placeholder | Purpose |
|
||||||
|
|---------------------|------------------------------------------------------------------------------|
|
||||||
|
| `DOMAIN` | Identity server authoritative domain, as configured in `matrix.domain` |
|
||||||
|
| `DOMAIN_PRETTY` | Same as `DOMAIN` with the first letter upper case and all other lower case |
|
||||||
|
| `FROM_EMAIL` | Email address configured in `threepid.medium.<3PID medium>.identity.from` |
|
||||||
|
| `FROM_NAME` | Name configured in `threepid.medium.<3PID medium>.identity.name` |
|
||||||
|
| `RECIPIENT_MEDIUM` | The 3PID medium, like `email` or `msisdn` |
|
||||||
|
| `RECIPIENT_ADDRESS` | The address to which the notification is sent |
|
||||||
|
|
||||||
|
### Room invitation
|
||||||
|
Specific placeholders:
|
||||||
|
|
||||||
|
| Placeholder | Purpose |
|
||||||
|
|---------------------|------------------------------------------------------------------------------------------|
|
||||||
|
| `SENDER_ID` | Matrix ID of the user who made the invite |
|
||||||
|
| `SENDER_NAME` | Display name of the user who made the invite, if not available/set, empty |
|
||||||
|
| `SENDER_NAME_OR_ID` | Display name of the user who made the invite. If not available/set, its Matrix ID |
|
||||||
|
| `INVITE_MEDIUM` | The 3PID medium for the invite. |
|
||||||
|
| `INVITE_ADDRESS` | The 3PID address for the invite. |
|
||||||
|
| `ROOM_ID` | The Matrix ID of the Room in which the invite took place |
|
||||||
|
| `ROOM_NAME` | The Name of the room in which the invite took place. If not available/set, empty |
|
||||||
|
| `ROOM_NAME_OR_ID` | The Name of the room in which the invite took place. If not available/set, its Matrix ID |
|
||||||
|
| `REGISTER_URL` | The URL to provide to the user allowing them to register their account, if needed |
|
||||||
|
|
||||||
|
### Validation of 3PID Session
|
||||||
|
Specific placeholders:
|
||||||
|
|
||||||
|
| Placeholder | Purpose |
|
||||||
|
|--------------------|--------------------------------------------------------------------------------------|
|
||||||
|
| `VALIDATION_LINK` | URL, including token, to validate the 3PID session. |
|
||||||
|
| `VALIDATION_TOKEN` | The token needed to validate the session, in case the user cannot use the link. |
|
||||||
|
| `NEXT_URL` | URL to redirect to after the sessions has been validated. |
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
mxisd comes with a set of builtin templates to easily get started. Those templates can be found
|
||||||
|
[in the repository](https://github.com/kamax-matrix/mxisd/tree/master/src/main/resources/threepids). If you want to use
|
||||||
|
customized templates, we recommend using the builtin templates as a starting point.
|
||||||
|
|
||||||
|
> **NOTE**: The link above point to the latest version of the built-in templates. Those might be different from your
|
||||||
|
version. Be sure to view the repo at the current tag.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
All configuration is specific to [3PID mediums](https://matrix.org/docs/spec/appendices.html#pid-types) and happen
|
||||||
|
under the namespace `threepid.medium.<medium>.generators.template`.
|
||||||
|
|
||||||
|
Under such namespace, the following keys are available:
|
||||||
|
- `invite`: Path to the 3PID invite notification template
|
||||||
|
- `session.validation`: Path to the 3PID session validation notification template
|
||||||
|
- `session.unbind.fraudulent`: Path to the 3PID session fraudulent unbind notification template
|
||||||
|
- `generic.matrixId`: Path to the Matrix ID invite notification template
|
||||||
|
- `placeholder`: Map of key/values to set static values for some placeholders.
|
||||||
|
|
||||||
|
The `placeholder` map supports the following keys, mapped to their respective template placeholders:
|
||||||
|
- `REGISTER_URL`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
#### Simple
|
||||||
|
```yaml
|
||||||
|
threepid:
|
||||||
|
medium:
|
||||||
|
email:
|
||||||
|
generators:
|
||||||
|
template:
|
||||||
|
placeholder:
|
||||||
|
REGISTER_URL: 'https://matrix-client.example.org'
|
||||||
|
```
|
||||||
|
In this configuration, the builtin templates are used and a static value for the `REGISTER_URL` is set, allowing to point
|
||||||
|
a newly invited user to a webapp allowing the creation of its account on the server.
|
||||||
|
|
||||||
|
#### Advanced
|
||||||
To configure paths to the various templates:
|
To configure paths to the various templates:
|
||||||
```yaml
|
```yaml
|
||||||
threepid:
|
threepid:
|
||||||
medium:
|
medium:
|
||||||
<YOUR 3PID MEDIUM HERE>:
|
email:
|
||||||
generators:
|
generators:
|
||||||
template:
|
template:
|
||||||
invite: '/path/to/invite-template.eml'
|
invite: '/path/to/invite-template.eml'
|
||||||
@@ -23,41 +103,7 @@ threepid:
|
|||||||
fraudulent: '/path/to/unbind-fraudulent-template.eml'
|
fraudulent: '/path/to/unbind-fraudulent-template.eml'
|
||||||
generic:
|
generic:
|
||||||
matrixId: '/path/to/mxid-invite-template.eml'
|
matrixId: '/path/to/mxid-invite-template.eml'
|
||||||
|
placeholder:
|
||||||
|
REGISTER_URL: 'https://matrix-client.example.org'
|
||||||
```
|
```
|
||||||
The `template` generator is usually the default, so no further configuration is needed.
|
In this configuration, a custom template is used for each event and a static value for the `REGISTER_URL` is set.
|
||||||
|
|
||||||
## Global placeholders
|
|
||||||
| Placeholder | Purpose |
|
|
||||||
|-----------------------|------------------------------------------------------------------------------|
|
|
||||||
| `%DOMAIN%` | Identity server authoritative domain, as configured in `matrix.domain` |
|
|
||||||
| `%DOMAIN_PRETTY%` | Same as `%DOMAIN%` with the first letter upper case and all other lower case |
|
|
||||||
| `%FROM_EMAIL%` | Email address configured in `threepid.medium.<3PID medium>.identity.from` |
|
|
||||||
| `%FROM_NAME%` | Name configured in `threepid.medium.<3PID medium>.identity.name` |
|
|
||||||
| `%RECIPIENT_MEDIUM%` | The 3PID medium, like `email` or `msisdn` |
|
|
||||||
| `%RECIPIENT_ADDRESS%` | The address to which the notification is sent |
|
|
||||||
|
|
||||||
## Events
|
|
||||||
### Room invitation
|
|
||||||
This template is used when someone is invited into a room using an email address which has no known bind to a Matrix ID.
|
|
||||||
#### Placeholders
|
|
||||||
| Placeholder | Purpose |
|
|
||||||
|-----------------------|------------------------------------------------------------------------------------------|
|
|
||||||
| `%SENDER_ID%` | Matrix ID of the user who made the invite |
|
|
||||||
| `%SENDER_NAME%` | Display name of the user who made the invite, if not available/set, empty |
|
|
||||||
| `%SENDER_NAME_OR_ID%` | Display name of the user who made the invite. If not available/set, its Matrix ID |
|
|
||||||
| `%INVITE_MEDIUM%` | The 3PID medium for the invite. |
|
|
||||||
| `%INVITE_ADDRESS%` | The 3PID address for the invite. |
|
|
||||||
| `%ROOM_ID%` | The Matrix ID of the Room in which the invite took place |
|
|
||||||
| `%ROOM_NAME%` | The Name of the room in which the invite took place. If not available/set, empty |
|
|
||||||
| `%ROOM_NAME_OR_ID%` | The Name of the room in which the invite took place. If not available/set, its Matrix ID |
|
|
||||||
|
|
||||||
### Validation of 3PID Session
|
|
||||||
This template is used when to user which added their 3PID address to their profile/settings and the session policy
|
|
||||||
allows at least local sessions.
|
|
||||||
|
|
||||||
#### Placeholders
|
|
||||||
| Placeholder | Purpose |
|
|
||||||
|----------------------|--------------------------------------------------------------------------------------|
|
|
||||||
| `%VALIDATION_LINK%` | URL, including token, to validate the 3PID session. |
|
|
||||||
| `%VALIDATION_TOKEN%` | The token needed to validate the session, in case the user cannot use the link. |
|
|
||||||
| `%NEXT_URL%` | URL to redirect to after the sessions has been validated. |
|
|
||||||
|
@@ -77,6 +77,7 @@ public class GenericTemplateConfig {
|
|||||||
private String invite;
|
private String invite;
|
||||||
private Session session = new Session();
|
private Session session = new Session();
|
||||||
private Map<String, String> generic = new HashMap<>();
|
private Map<String, String> generic = new HashMap<>();
|
||||||
|
private Map<String, String> placeholder = new HashMap<>();
|
||||||
|
|
||||||
public String getInvite() {
|
public String getInvite() {
|
||||||
return invite;
|
return invite;
|
||||||
@@ -98,4 +99,12 @@ public class GenericTemplateConfig {
|
|||||||
this.generic = generic;
|
this.generic = generic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPlaceholder() {
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaceholder(Map<String, String> placeholder) {
|
||||||
|
this.placeholder = placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -68,6 +68,7 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
|
|||||||
@Override
|
@Override
|
||||||
public String getForReply(IThreePidInviteReply invite) {
|
public String getForReply(IThreePidInviteReply invite) {
|
||||||
log.info("Generating notification content for 3PID invite");
|
log.info("Generating notification content for 3PID invite");
|
||||||
|
invite.getInvite().getProperties().putAll(cfg.getPlaceholder());
|
||||||
return populateForReply(invite, getTemplateContent(cfg.getInvite()));
|
return populateForReply(invite, getTemplateContent(cfg.getInvite()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,6 +35,8 @@ import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.SenderDisp
|
|||||||
|
|
||||||
public abstract class PlaceholderNotificationGenerator {
|
public abstract class PlaceholderNotificationGenerator {
|
||||||
|
|
||||||
|
public static final String RegisterUrl = "REGISTER_URL";
|
||||||
|
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
private ServerConfig srvCfg;
|
private ServerConfig srvCfg;
|
||||||
|
|
||||||
@@ -76,8 +78,10 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
|
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
|
||||||
String roomName = invite.getInvite().getProperties().getOrDefault(RoomName, "");
|
String roomName = invite.getInvite().getProperties().getOrDefault(RoomName, "");
|
||||||
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
||||||
|
String registerUrl = StringUtils.defaultIfBlank(invite.getInvite().getProperties().get(RegisterUrl), "https://" + mxCfg.getDomain());
|
||||||
|
|
||||||
return populateForCommon(tpid, input)
|
return populateForCommon(tpid, input)
|
||||||
|
.replace("%" + RegisterUrl + "%", registerUrl)
|
||||||
.replace("%SENDER_ID%", invite.getInvite().getSender().getId())
|
.replace("%SENDER_ID%", invite.getInvite().getSender().getId())
|
||||||
.replace("%SENDER_NAME%", senderName)
|
.replace("%SENDER_NAME%", senderName)
|
||||||
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
|
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
|
||||||
@@ -102,10 +106,6 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
.replace("%NEXT_URL%", validationLink);
|
.replace("%NEXT_URL%", validationLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String populateForRemoteValidation(IThreePidSession session, String input) {
|
|
||||||
return populateForValidation(session, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
|
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
|
||||||
return populateForCommon(tpid, input);
|
return populateForCommon(tpid, input);
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ Content-Disposition: inline
|
|||||||
Hi,
|
Hi,
|
||||||
|
|
||||||
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
||||||
Matrix. To join the conversation, register an account on https://%DOMAIN%
|
Matrix. To join the conversation, register an account on %REGISTER_URL%
|
||||||
|
|
||||||
You can also register an account on a public server and get in touch with them.
|
You can also register an account on a public server and get in touch with them.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ pre, code {
|
|||||||
<p>Hi,</p>
|
<p>Hi,</p>
|
||||||
|
|
||||||
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
||||||
Matrix. To join the conversation, register an account on <a href="https://%DOMAIN%">%DOMAIN%</a>.</p>
|
Matrix. To join the conversation, register an account on <a href="%REGISTER_URL%">%DOMAIN%</a>.</p>
|
||||||
|
|
||||||
<pYou can also register an account on a public server and get in touch with them.</p>
|
<pYou can also register an account on a public server and get in touch with them.</p>
|
||||||
|
|
||||||
|
@@ -32,7 +32,10 @@ import io.kamax.mxisd.config.MxisdConfig;
|
|||||||
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
|
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
|
||||||
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||||
import io.kamax.mxisd.invitation.MatrixIdInvite;
|
import io.kamax.mxisd.invitation.MatrixIdInvite;
|
||||||
|
import io.kamax.mxisd.invitation.ThreePidInvite;
|
||||||
|
import io.kamax.mxisd.invitation.ThreePidInviteReply;
|
||||||
import io.kamax.mxisd.threepid.connector.email.EmailSmtpConnector;
|
import io.kamax.mxisd.threepid.connector.email.EmailSmtpConnector;
|
||||||
|
import io.kamax.mxisd.threepid.generator.PlaceholderNotificationGenerator;
|
||||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||||
import org.apache.commons.lang.RandomStringUtils;
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@@ -45,6 +48,7 @@ import javax.mail.internet.MimeBodyPart;
|
|||||||
import javax.mail.internet.MimeMessage;
|
import javax.mail.internet.MimeMessage;
|
||||||
import javax.mail.internet.MimeMultipart;
|
import javax.mail.internet.MimeMultipart;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
@@ -118,6 +122,29 @@ public class EmailNotificationTest {
|
|||||||
assertEquals(1, msg.getRecipients(Message.RecipientType.TO).length);
|
assertEquals(1, msg.getRecipients(Message.RecipientType.TO).length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forThreepidInvite() throws MessagingException, IOException {
|
||||||
|
String registerUrl = "https://" + RandomStringUtils.randomAlphanumeric(20) + ".example.org/register";
|
||||||
|
gm.setUser(user, user);
|
||||||
|
|
||||||
|
_MatrixID sender = MatrixID.asAcceptable(user, domain);
|
||||||
|
ThreePidInvite inv = new ThreePidInvite(sender, ThreePidMedium.Email.getId(), target, "!rid:" + domain);
|
||||||
|
inv.getProperties().put(PlaceholderNotificationGenerator.RegisterUrl, registerUrl);
|
||||||
|
m.getNotif().sendForReply(new ThreePidInviteReply("a", inv, "b", "c", new ArrayList<>()));
|
||||||
|
|
||||||
|
assertEquals(1, gm.getReceivedMessages().length);
|
||||||
|
MimeMessage msg = gm.getReceivedMessages()[0];
|
||||||
|
assertEquals(1, msg.getFrom().length);
|
||||||
|
assertEquals(senderEmail, msg.getFrom()[0].toString());
|
||||||
|
assertEquals(1, msg.getRecipients(Message.RecipientType.TO).length);
|
||||||
|
|
||||||
|
// We just check on the text/plain one. HTML is multipart and it's difficult so we skip
|
||||||
|
MimeMultipart content = (MimeMultipart) msg.getContent();
|
||||||
|
MimeBodyPart mbp = (MimeBodyPart) content.getBodyPart(0);
|
||||||
|
String mbpContent = mbp.getContent().toString();
|
||||||
|
assertTrue(mbpContent.contains(registerUrl));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void forValidation() throws MessagingException, IOException {
|
public void forValidation() throws MessagingException, IOException {
|
||||||
gm.setUser(user, user);
|
gm.setUser(user, user);
|
||||||
|
Reference in New Issue
Block a user