diff --git a/CHANGELOG.md b/CHANGELOG.md
index 012a8c69..4fe7287c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 6.1.0-RELEASE
+* Add a `sanitiseContentFor` parameter to `sendEmail`, a list of placeholders, the content of which you want Notify to sanitise.
+
## 6.0.0-RELEASE
* Remove Java 8, 9, and 10 from supported versions. This library only supports Java 11+ from now on.
diff --git a/pom.xml b/pom.xml
index 668f888a..88c1c28a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
uk.gov.service.notify
notifications-java-client
- 6.0.1-RELEASE
+ 6.1.0-RELEASE
jar
GOV.UK Notify Java client
diff --git a/src/main/java/uk/gov/service/notify/NotificationClient.java b/src/main/java/uk/gov/service/notify/NotificationClient.java
index ba15ea8d..fc3ec298 100644
--- a/src/main/java/uk/gov/service/notify/NotificationClient.java
+++ b/src/main/java/uk/gov/service/notify/NotificationClient.java
@@ -22,6 +22,7 @@
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Objects;
+import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -138,7 +139,7 @@ public SendEmailResponse sendEmail(String templateId,
String emailAddress,
Map personalisation,
String reference) throws NotificationClientException {
- return sendEmail(templateId, emailAddress, personalisation, reference, "", null);
+ return sendEmail(templateId, emailAddress, personalisation, reference, "", null, null);
}
@Override
@@ -147,7 +148,7 @@ public SendEmailResponse sendEmail(String templateId,
Map personalisation,
String reference,
String emailReplyToId) throws NotificationClientException {
- return sendEmail(templateId, emailAddress, personalisation, reference, emailReplyToId, null);
+ return sendEmail(templateId, emailAddress, personalisation, reference, emailReplyToId, null, null);
}
@Override
@@ -157,6 +158,18 @@ public SendEmailResponse sendEmail(String templateId,
String reference,
String emailReplyToId,
URI oneClickUnsubscribeURL) throws NotificationClientException {
+ return sendEmail(templateId, emailAddress, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL, null);
+ }
+
+ @Override
+ public SendEmailResponse sendEmail(String templateId,
+ String emailAddress,
+ Map personalisation,
+ String reference,
+ String emailReplyToId,
+ URI oneClickUnsubscribeURL,
+ List sanitiseContentFor) throws NotificationClientException {
+
JSONObject body = createBodyForPostRequest(templateId,
null,
@@ -176,6 +189,11 @@ public SendEmailResponse sendEmail(String templateId,
body.put("one_click_unsubscribe_url", oneClickUnsubscribeURL);
}
+ if(sanitiseContentFor != null)
+ {
+ body.put("sanitise_content_for", sanitiseContentFor);
+ }
+
HttpURLConnection conn = createConnectionAndSetHeaders(baseUrl + "/v2/notifications/email", "POST");
String response = performPostRequest(conn, body, HttpsURLConnection.HTTP_CREATED);
return new SendEmailResponse(response);
diff --git a/src/main/java/uk/gov/service/notify/NotificationClientApi.java b/src/main/java/uk/gov/service/notify/NotificationClientApi.java
index fea5e4ca..9424ba19 100644
--- a/src/main/java/uk/gov/service/notify/NotificationClientApi.java
+++ b/src/main/java/uk/gov/service/notify/NotificationClientApi.java
@@ -4,6 +4,7 @@
import java.io.File;
import java.io.InputStream;
import java.net.URI;
+import java.util.List;
import java.util.Map;
public interface NotificationClientApi {
@@ -61,6 +62,27 @@ public interface NotificationClientApi {
*/
SendEmailResponse sendEmail(String templateId, String emailAddress, Map personalisation, String reference, String emailReplyToId, URI oneClickUnsubscribeURL) throws NotificationClientException;
+ /**
+ * The sendEmail method will create an HTTPS POST request. A JWT token will be created and added as an Authorization header to the request.
+ *
+ * @param templateId The template id is visible on the template page in the application.
+ * @param emailAddress The email address
+ * @param personalisation Map representing the placeholders for the template if any. For example, key=name value=Bob
+ * Can be an empty map or null when the template does not require placeholders.
+ * @param reference A reference specified by the service for the notification. Get all notifications can be filtered by this reference.
+ * This reference can be unique or used used to refer to a batch of notifications.
+ * Can be an empty string or null, when you do not require a reference for the notifications.
+ * @param emailReplyToId An optional identifier for a reply to email address for the notification, rather than use the service default.
+ * Service emailReplyToIds can be accessed via the service settings / manage email reply to addresses page.
+ * Omit this argument to use the default service email reply to address.
+ * @param oneClickUnsubscribeURL A link so users can unsubscribe, see https://www.notifications.service.gov.uk/using-notify/unsubscribe-links
+ * @param sanitiseContentFor A list of placeholders, the content of which should be sanitised
+ * by Notify. Notify will remove links and escape Markdown characters for the selected content.
+ * @return SendEmailResponse
+ * @throws NotificationClientException see https://docs.notifications.service.gov.uk/java.html#send-an-email-error-codes
+ */
+ SendEmailResponse sendEmail(String templateId, String emailAddress, Map personalisation, String reference, String emailReplyToId, URI oneClickUnsubscribeURL, List sanitiseContentFor) throws NotificationClientException;
+
/**
* The sendSms method will create an HTTPS POST request. A JWT token will be created and added as an Authorization header to the request.
*
diff --git a/src/main/java/uk/gov/service/notify/SendEmailResponse.java b/src/main/java/uk/gov/service/notify/SendEmailResponse.java
index 8132e8fd..609dc2a6 100644
--- a/src/main/java/uk/gov/service/notify/SendEmailResponse.java
+++ b/src/main/java/uk/gov/service/notify/SendEmailResponse.java
@@ -16,6 +16,7 @@ public class SendEmailResponse {
private final String subject;
private final String fromEmail;
private final URI oneClickUnsubscribeURL;
+ private final JSONObject sanitisedContent;
public SendEmailResponse(String response) {
JSONObject data = new JSONObject(response);
@@ -30,6 +31,7 @@ public SendEmailResponse(String response) {
templateVersion = template.getInt("version");
templateUri = template.getString("uri");
oneClickUnsubscribeURL = data.isNull("one_click_unsubscribe_url") ? null : URI.create(data.getString("one_click_unsubscribe_url"));
+ sanitisedContent = data.isNull("sanitised_content") ? null : data.getJSONObject("sanitised_content");
}
public UUID getNotificationId() {
@@ -68,6 +70,10 @@ public Optional getOneClickUnsubscribeURL() {
return Optional.ofNullable(oneClickUnsubscribeURL);
}
+ public Optional getSanitisedContent() {
+ return Optional.ofNullable(sanitisedContent);
+ }
+
@Override
public String toString() {
return "SendEmailResponse{" +
@@ -80,6 +86,7 @@ public String toString() {
", subject='" + subject + '\'' +
", fromEmail='" + fromEmail + '\'' +
", oneClickUnsubscribeURL=" + oneClickUnsubscribeURL +
+ ", sanitisedContent=" + sanitisedContent +
'}';
}
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index ae24944f..4202c92f 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -6,4 +6,4 @@
# - PATCH version when you make backwards-compatible bug fixes.
#
# -- http://semver.org/
-project.version=6.0.1-RELEASE
+project.version=6.1.0-RELEASE
diff --git a/src/test/java/uk/gov/service/notify/NotificationClientTest.java b/src/test/java/uk/gov/service/notify/NotificationClientTest.java
index 5cc4848c..9d4306d8 100644
--- a/src/test/java/uk/gov/service/notify/NotificationClientTest.java
+++ b/src/test/java/uk/gov/service/notify/NotificationClientTest.java
@@ -45,6 +45,7 @@
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -392,6 +393,44 @@ public void testSendEmailWithUnsubscribeURL() throws NotificationClientException
assertEquals(oneClickUnsubscribeURL, requestReceivedByNotifyApi.getOneClickUnsubscribeURL());
}
+ @Test
+ public void testSendEmailWithsanitiseContentFor() throws NotificationClientException, IOException {
+ NotifyEmailResponse expected = objectMapper.readValue(this.getClass().getClassLoader().getResourceAsStream("v2_notifications_email_sanitise_personalisation_response.json"), NotifyEmailResponse.class);
+ wireMockRule.stubFor(post("/v2/notifications/email")
+ .willReturn(created()
+ .withResponseBody(new Body(objectMapper.writeValueAsString(expected)))));
+ NotificationClient client = new NotificationClient(COMBINED_API_KEY, BASE_URL);
+ // setting up this map can be replaced with Map.of() in later Java versions
+ Map personalisation = new HashMap<>();
+ personalisation.put("name", "Anne Example, now [click this evil link](https://evil.link)");
+ URI oneClickUnsubscribeURL = URI.create("http://localhost/unsubscribe");
+ String[] placeholdersToSanitise = {"name"};
+ List sanitiseContentFor = Arrays.asList(placeholdersToSanitise);
+
+ SendEmailResponse actual = client.sendEmail("aTemplateId", "anEmailAddress", personalisation, "aReference", "an emailReplyToId", oneClickUnsubscribeURL, sanitiseContentFor);
+
+ assertEquals(expected.getNotificationId(), actual.getNotificationId());
+ assertEquals(expected.getReference(), actual.getReference().get());
+ assertEquals(expected.getContent().getBody(), actual.getBody());
+ assertEquals(expected.getContent().getSubject(), actual.getSubject());
+ assertEquals(expected.getContent().getFromEmail(), actual.getFromEmail().get());
+ // No notification uri in SendEmailResponse?
+// assertEquals(expected.getUri(), actual.getUri());
+ assertEquals(expected.getTemplate().getId(), actual.getTemplateId());
+ assertEquals(expected.getTemplate().getVersion(), actual.getTemplateVersion());
+ assertEquals(expected.getTemplate().getUri().toString(), actual.getTemplateUri());
+ assertEquals(objectMapper.writeValueAsString(expected.getSanitisedContent()), actual.getSanitisedContent().orElseGet(JSONObject::new).toString());
+
+ LoggedRequest request = validateRequest();
+ NotifyEmailRequest requestReceivedByNotifyApi = objectMapper.readValue(request.getBodyAsString(), NotifyEmailRequest.class);
+ assertEquals("anEmailAddress", requestReceivedByNotifyApi.getEmailAddress());
+ assertEquals("aTemplateId", requestReceivedByNotifyApi.getTemplateId());
+ assertEquals(personalisation, requestReceivedByNotifyApi.getPersonalisation());
+ assertEquals("aReference", requestReceivedByNotifyApi.getReference());
+ assertEquals("an emailReplyToId", requestReceivedByNotifyApi.getEmailReplyToId());
+ assertEquals(sanitiseContentFor, requestReceivedByNotifyApi.getSanitiseContentFor());
+ }
+
@Test
public void testSendSmsHandlesErrors() {
wireMockRule.stubFor(post("/v2/notifications/sms")
diff --git a/src/test/java/uk/gov/service/notify/domain/NotifyEmailRequest.java b/src/test/java/uk/gov/service/notify/domain/NotifyEmailRequest.java
index 0ef28280..68cfc30d 100644
--- a/src/test/java/uk/gov/service/notify/domain/NotifyEmailRequest.java
+++ b/src/test/java/uk/gov/service/notify/domain/NotifyEmailRequest.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URI;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -14,19 +15,24 @@ public class NotifyEmailRequest {
private final String reference;
private final String emailReplyToId;
private final URI oneClickUnsubscribeURL;
+ private final List sanitiseContentFor;
- public NotifyEmailRequest(@JsonProperty("email_address") String emailAddress,
- @JsonProperty("template_id") String templateId,
- @JsonProperty("personalisation") Map personalisation,
- @JsonProperty("reference") String reference,
- @JsonProperty("email_reply_to_id") String emailReplyToId,
- @JsonProperty("one_click_unsubscribe_url") URI oneClickUnsubscribeURL) {
+ public NotifyEmailRequest(
+ @JsonProperty("email_address") String emailAddress,
+ @JsonProperty("template_id") String templateId,
+ @JsonProperty("personalisation") Map personalisation,
+ @JsonProperty("reference") String reference,
+ @JsonProperty("email_reply_to_id") String emailReplyToId,
+ @JsonProperty("one_click_unsubscribe_url") URI oneClickUnsubscribeURL,
+ @JsonProperty("sanitise_content_for") List sanitiseContentFor
+ ) {
this.emailAddress = emailAddress;
this.templateId = templateId;
this.personalisation = personalisation;
this.reference = reference;
this.emailReplyToId = emailReplyToId;
this.oneClickUnsubscribeURL = oneClickUnsubscribeURL;
+ this.sanitiseContentFor = sanitiseContentFor;
}
@JsonProperty("email_address")
@@ -59,17 +65,24 @@ public URI getOneClickUnsubscribeURL() {
return oneClickUnsubscribeURL;
}
+ @JsonProperty("sanitise_content_for")
+ public List getSanitiseContentFor() {
+ return sanitiseContentFor;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NotifyEmailRequest request = (NotifyEmailRequest) o;
- return Objects.equals(emailAddress, request.emailAddress) && Objects.equals(templateId, request.templateId) && Objects.equals(personalisation, request.personalisation) && Objects.equals(reference, request.reference) && Objects.equals(emailReplyToId, request.emailReplyToId) && Objects.equals(oneClickUnsubscribeURL, request.oneClickUnsubscribeURL);
+ return Objects.equals(emailAddress, request.emailAddress) && Objects.equals(templateId, request.templateId) && Objects.equals(personalisation, request.personalisation) && Objects.equals(reference, request.reference) && Objects.equals(emailReplyToId, request.emailReplyToId) && Objects.equals(oneClickUnsubscribeURL, request.oneClickUnsubscribeURL) && Objects.equals(sanitiseContentFor, request.sanitiseContentFor);
}
@Override
public int hashCode() {
- return Objects.hash(emailAddress, templateId, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL);
+ return Objects.hash(
+ emailAddress, templateId, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL, sanitiseContentFor
+ );
}
@Override
@@ -81,6 +94,7 @@ public String toString() {
", reference='" + reference + '\'' +
", emailReplyToId='" + emailReplyToId + '\'' +
", oneClickUnsubscribeURL=" + oneClickUnsubscribeURL +
+ ", sanitiseContentFor=" + sanitiseContentFor +
'}';
}
}
diff --git a/src/test/java/uk/gov/service/notify/domain/NotifyEmailResponse.java b/src/test/java/uk/gov/service/notify/domain/NotifyEmailResponse.java
index 261392eb..2e10cc9f 100644
--- a/src/test/java/uk/gov/service/notify/domain/NotifyEmailResponse.java
+++ b/src/test/java/uk/gov/service/notify/domain/NotifyEmailResponse.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import java.net.URI;
+import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -116,19 +117,24 @@ public String toString() {
private final URI uri;
private final Template template;
private final URI oneClickUnsubscribeURL;
-
- public NotifyEmailResponse(@JsonProperty("id") UUID notificationId,
- @JsonProperty("reference") String reference,
- @JsonProperty("content") Content content,
- @JsonProperty("uri") URI uri,
- @JsonProperty("template") Template template,
- @JsonProperty("one_click_unsubscribe_url") URI oneClickUnsubscribeURL) {
+ private final Map> sanitisedContent;
+
+ public NotifyEmailResponse(
+ @JsonProperty("id") UUID notificationId,
+ @JsonProperty("reference") String reference,
+ @JsonProperty("content") Content content,
+ @JsonProperty("uri") URI uri,
+ @JsonProperty("template") Template template,
+ @JsonProperty("one_click_unsubscribe_url") URI oneClickUnsubscribeURL,
+ @JsonProperty("sanitised_content") Map> sanitisedContent
+ ) {
this.notificationId = notificationId;
this.reference = reference;
this.content = content;
this.uri = uri;
this.template = template;
this.oneClickUnsubscribeURL = oneClickUnsubscribeURL;
+ this.sanitisedContent = sanitisedContent;
}
@JsonProperty("id")
@@ -161,17 +167,22 @@ public URI getOneClickUnsubscribeURL() {
return oneClickUnsubscribeURL;
}
+ @JsonProperty("sanitised_content")
+ public Map> getSanitisedContent() {
+ return sanitisedContent;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NotifyEmailResponse that = (NotifyEmailResponse) o;
- return Objects.equals(notificationId, that.notificationId) && Objects.equals(reference, that.reference) && Objects.equals(content, that.content) && Objects.equals(uri, that.uri) && Objects.equals(template, that.template) && Objects.equals(oneClickUnsubscribeURL, that.oneClickUnsubscribeURL);
+ return Objects.equals(notificationId, that.notificationId) && Objects.equals(reference, that.reference) && Objects.equals(content, that.content) && Objects.equals(uri, that.uri) && Objects.equals(template, that.template) && Objects.equals(oneClickUnsubscribeURL, that.oneClickUnsubscribeURL) && Objects.equals(sanitisedContent, that.sanitisedContent);
}
@Override
public int hashCode() {
- return Objects.hash(notificationId, reference, content, uri, template, oneClickUnsubscribeURL);
+ return Objects.hash(notificationId, reference, content, uri, template, oneClickUnsubscribeURL, sanitisedContent);
}
@Override
@@ -183,6 +194,7 @@ public String toString() {
", uri=" + uri +
", template=" + template +
", oneClickUnsubscribeURL=" + oneClickUnsubscribeURL +
+ ", sanitisedContent=" + sanitisedContent +
'}';
}
}
diff --git a/src/test/resources/v2_notifications_email_sanitise_personalisation_response.json b/src/test/resources/v2_notifications_email_sanitise_personalisation_response.json
new file mode 100644
index 00000000..48cae2fe
--- /dev/null
+++ b/src/test/resources/v2_notifications_email_sanitise_personalisation_response.json
@@ -0,0 +1,21 @@
+{
+ "id": "7dd0b55b-fca1-496f-b52a-787692045f71",
+ "reference": "a reference",
+ "content": {
+ "body": "a content`body",
+ "subject": "a subject",
+ "from_email": "a from email"
+ },
+ "uri": null,
+ "template": {
+ "id": "cc378915-2e6c-4e04-86a7-5f71949582b7",
+ "version": -1,
+ "uri": "http://localhost"
+ },
+ "sanitised_content": {
+ "name": {
+ "unsanitised": "Anne Example, now [click this evil link](https://evil.link)",
+ "sanitised": "Anne Example, now \\[click this evil link\\]\\(\\)"
+ }
+ }
+}