Skip to content

Commit

Permalink
Add various Notification template generator improvements
Browse files Browse the repository at this point in the history
- Add ability to set arbitrary value for some placeholders (Fix #133)
- More Unit tests
- Improve doc
  • Loading branch information
maxidorius committed Apr 26, 2019
1 parent e2c8a56 commit f331af0
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 51 deletions.
136 changes: 91 additions & 45 deletions docs/threepids/notification/template-generator.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,99 @@
# Notifications: Generate from templates
To create notification content, you can use the `template` generator if supported for the 3PID medium which will read
content from configured files.
# Notifications: Template generator
Most of the Identity actions will trigger a notification of some kind, informing the user of some confirmation, next step
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
the 3PID that was requested, the domain of your Identity server, etc.
Those notifications are by default generated from templates and by replacing placeholder tokens in them with the relevant
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
placeholders and also have their own individual set of placeholders.
Templates for the following events/actions are available:
- [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
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:
```yaml
threepid:
medium:
<YOUR 3PID MEDIUM HERE>:
email:
generators:
template:
invite: '/path/to/invite-template.eml'
Expand All @@ -23,41 +103,7 @@ threepid:
fraudulent: '/path/to/unbind-fraudulent-template.eml'
generic:
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.

## 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. |
In this configuration, a custom template is used for each event and a static value for the `REGISTER_URL` is set.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void setUnbind(SessionUnbind unbind) {
private String invite;
private Session session = new Session();
private Map<String, String> generic = new HashMap<>();
private Map<String, String> placeholder = new HashMap<>();

public String getInvite() {
return invite;
Expand All @@ -98,4 +99,12 @@ public void setGeneric(Map<String, String> generic) {
this.generic = generic;
}

public Map<String, String> getPlaceholder() {
return placeholder;
}

public void setPlaceholder(Map<String, String> placeholder) {
this.placeholder = placeholder;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public String getForInvite(IMatrixIdInvite invite) {
@Override
public String getForReply(IThreePidInviteReply invite) {
log.info("Generating notification content for 3PID invite");
invite.getInvite().getProperties().putAll(cfg.getPlaceholder());
return populateForReply(invite, getTemplateContent(cfg.getInvite()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

public abstract class PlaceholderNotificationGenerator {

public static final String RegisterUrl = "REGISTER_URL";

private MatrixConfig mxCfg;
private ServerConfig srvCfg;

Expand Down Expand Up @@ -76,8 +78,10 @@ protected String populateForReply(IThreePidInviteReply invite, String input) {
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
String roomName = invite.getInvite().getProperties().getOrDefault(RoomName, "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
String registerUrl = StringUtils.defaultIfBlank(invite.getInvite().getProperties().get(RegisterUrl), "https://" + mxCfg.getDomain());

return populateForCommon(tpid, input)
.replace("%" + RegisterUrl + "%", registerUrl)
.replace("%SENDER_ID%", invite.getInvite().getSender().getId())
.replace("%SENDER_NAME%", senderName)
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
Expand All @@ -102,10 +106,6 @@ protected String populateForValidation(IThreePidSession session, String input) {
.replace("%NEXT_URL%", validationLink);
}

protected String populateForRemoteValidation(IThreePidSession session, String input) {
return populateForValidation(session, input);
}

protected String populateForFraudulentUndind(ThreePid tpid, String input) {
return populateForCommon(tpid, input);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/threepids/email/invite-template.eml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Content-Disposition: inline
Hi,
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
Matrix. To join the conversation, register an account on 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.
Expand Down Expand Up @@ -69,7 +69,7 @@ pre, code {
<p>Hi,</p>

<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>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
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.generator.PlaceholderNotificationGenerator;
import io.kamax.mxisd.threepid.session.ThreePidSession;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.After;
Expand All @@ -45,6 +48,7 @@
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

import static junit.framework.TestCase.assertEquals;
Expand Down Expand Up @@ -118,6 +122,29 @@ public void forMatrixIdInvite() throws MessagingException {
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
public void forValidation() throws MessagingException, IOException {
gm.setUser(user, user);
Expand Down

0 comments on commit f331af0

Please # to comment.