Support for Sanitized HTML in emails.
This change adds a shaded dependency on owasp (https://code.google.com/p/owasp-java-html-sanitizer/) to add support for HTML Sanitization. Only images that reference an attachment are supported. This Sanitization may be customized for each email profile. Other dangerous behavior is suppressed. See elastic/elasticsearch#163 Fixes elastic/elasticsearch#163 Original commit: elastic/x-pack-elasticsearch@bc237d1beb
This commit is contained in:
parent
771b8824a5
commit
3c7b42eb7b
7
pom.xml
7
pom.xml
|
@ -149,6 +149,12 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
|
||||
<artifactId>owasp-java-html-sanitizer</artifactId>
|
||||
<version>r239</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
|
@ -328,6 +334,7 @@
|
|||
<promoteTransitiveDependencies>true</promoteTransitiveDependencies>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer</include>
|
||||
<include>org.quartz-scheduler:quartz</include>
|
||||
<include>org.slf4j:slf4j-api</include>
|
||||
<include>org.slf4j:slf4j-nop</include>
|
||||
|
|
|
@ -6,9 +6,13 @@
|
|||
package org.elasticsearch.watcher.actions.email.service;
|
||||
|
||||
import org.elasticsearch.common.base.Charsets;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.owasp.html.*;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
|
@ -16,6 +20,7 @@ import javax.mail.internet.MimeBodyPart;
|
|||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
|
@ -87,7 +92,8 @@ public enum Profile implements ToXContent {
|
|||
|
||||
if (email.htmlBody != null) {
|
||||
MimeBodyPart html = new MimeBodyPart();
|
||||
html.setText(email.htmlBody, Charsets.UTF_8.name(), "html");
|
||||
String sanitizedHtml = sanitizeHtml(email.attachments, email.htmlBody);
|
||||
html.setText(sanitizedHtml, Charsets.UTF_8.name(), "html");
|
||||
alternative.addBodyPart(html);
|
||||
}
|
||||
|
||||
|
@ -217,4 +223,54 @@ public enum Profile implements ToXContent {
|
|||
return part;
|
||||
}
|
||||
|
||||
static String sanitizeHtml(final ImmutableMap<String, Attachment> attachments, String html){
|
||||
ElementPolicy onlyCIDImgPolicy = new AttachementVerifyElementPolicy(attachments);
|
||||
PolicyFactory policy = Sanitizers.FORMATTING
|
||||
.and(new HtmlPolicyBuilder()
|
||||
.allowElements("img", "table", "tr", "td", "style", "body", "head")
|
||||
.allowAttributes("src").onElements("img")
|
||||
.allowAttributes("class").onElements("style")
|
||||
.allowUrlProtocols("cid")
|
||||
.allowCommonInlineFormattingElements()
|
||||
.allowElements(onlyCIDImgPolicy, "img")
|
||||
.allowStyling(CssSchema.DEFAULT)
|
||||
.toFactory())
|
||||
.and(Sanitizers.LINKS)
|
||||
.and(Sanitizers.BLOCKS);
|
||||
return policy.sanitize(html);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class AttachementVerifyElementPolicy implements ElementPolicy {
|
||||
|
||||
private final ImmutableMap<String, Attachment> attachments;
|
||||
|
||||
AttachementVerifyElementPolicy(ImmutableMap<String, Attachment> attchments) {
|
||||
this.attachments = attchments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@ParametersAreNonnullByDefault String elementName, @ParametersAreNonnullByDefault List<String> attrs) {
|
||||
if (attrs.size() == 0) {
|
||||
return elementName;
|
||||
}
|
||||
for (int i = 0; i < attrs.size(); ++i) {
|
||||
if(attrs.get(i).equals("src") && i < attrs.size() - 1) {
|
||||
String srcValue = attrs.get(i+1);
|
||||
if (!srcValue.startsWith("cid:")) {
|
||||
return null; //Disallow anything other than content ids
|
||||
}
|
||||
String contentId = srcValue.substring(4);
|
||||
if (attachments.containsKey(contentId)) {
|
||||
return elementName;
|
||||
} else {
|
||||
return null; //This cid wasn't found
|
||||
}
|
||||
}
|
||||
}
|
||||
return elementName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.actions.email.service;
|
||||
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class HtmlSanitizeTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void test_HtmlSanitizer_onclick() {
|
||||
String badHtml = "<button type=\"button\"" +
|
||||
"onclick=\"document.getElementById('demo').innerHTML = Date()\">" +
|
||||
"Click me to display Date and Time.</button>";
|
||||
byte[] bytes = new byte[0];
|
||||
String sanitizedHtml = Profile.sanitizeHtml(ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")), badHtml);
|
||||
assertThat(sanitizedHtml, equalTo("Click me to display Date and Time."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_HtmlSanitizer_Nonattachment_img() {
|
||||
String badHtml = "<img src=\"http://test.com/nastyimage.jpg\"/>This is a bad image";
|
||||
byte[] bytes = new byte[0];
|
||||
String sanitizedHtml = Profile.sanitizeHtml(ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")), badHtml);
|
||||
assertThat(sanitizedHtml, equalTo("This is a bad image"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_HtmlSanitizer_Goodattachment_img() {
|
||||
String goodHtml = "<img src=\"cid:foo\" />This is a good image";
|
||||
byte[] bytes = new byte[0];
|
||||
String sanitizedHtml = Profile.sanitizeHtml(ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")), goodHtml);
|
||||
assertThat(sanitizedHtml, equalTo(goodHtml));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_HtmlSanitizer_table() {
|
||||
String goodHtml = "<table><tr><td>cell1</td><td>cell2</td></tr></table>";
|
||||
byte[] bytes = new byte[0];
|
||||
String sanitizedHtml = Profile.sanitizeHtml(ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")), goodHtml);
|
||||
assertThat(sanitizedHtml, equalTo(goodHtml));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_HtmlSanitizer_Badattachment_img() {
|
||||
String goodHtml = "<img src=\"cid:bad\" />This is a bad image";
|
||||
byte[] bytes = new byte[0];
|
||||
String sanitizedHtml = Profile.sanitizeHtml(ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")), goodHtml);
|
||||
assertThat(sanitizedHtml, equalTo("This is a bad image"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_HtmlSanitizer_Script() {
|
||||
String badHtml = "<script>doSomethingNefarious()</script>This was a dangerous script";
|
||||
byte[] bytes = new byte[0];
|
||||
String sanitizedHtml = Profile.sanitizeHtml(ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")), badHtml);
|
||||
assertThat(sanitizedHtml, equalTo("This was a dangerous script"));
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue