Skip to content

Commit 32771a5

Browse files
authored
Merge pull request #3420 from AtlasOfLivingAustralia/feature/issue3419
Replaced grails markdown plugin with commonmark #3419
2 parents 0a85acf + 08c7f69 commit 32771a5

File tree

6 files changed

+99
-31
lines changed

6 files changed

+99
-31
lines changed

build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ dependencies {
136136
exclude module: "xercesImpl"
137137
}
138138
implementation 'org.grails.plugins:grails-cookie:2.0.3'
139-
implementation 'org.grails.plugins:grails-markdown:3.0.0'
140139

141140
implementation 'org.apache.poi:ooxml-schemas:1.4'
142141
implementation 'org.apache.poi:poi:4.1.2'
@@ -150,7 +149,8 @@ dependencies {
150149
implementation "commons-io:commons-io:2.6"
151150
implementation "org.seleniumhq.selenium:selenium-chrome-driver:3.14.0"
152151
implementation "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion"
153-
implementation group: 'com.googlecode.owasp-java-html-sanitizer', name: 'owasp-java-html-sanitizer', version: '20220608.1'
152+
implementation "org.commonmark:commonmark:0.24.0"
153+
implementation "com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1"
154154

155155
compileOnly "io.micronaut:micronaut-inject-groovy"
156156
console "org.grails:grails-console"

grails-app/services/au/org/ala/merit/EmailService.groovy

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package au.org.ala.merit
22

33
import au.org.ala.merit.config.EmailTemplate
4+
import au.org.ala.merit.util.MarkdownUtils
45
import groovy.util.logging.Slf4j
56

67

@@ -17,7 +18,8 @@ class EmailService {
1718
def systemEmailAddress = grailsApplication.config.getProperty('fieldcapture.system.email.address')
1819
try {
1920
def subjectLine = settingService.getSettingText(mailSubjectTemplate, model)
20-
def body = settingService.getSettingText(mailTemplate, model).markdownToHtml()
21+
String bodyMarkdown = settingService.getSettingText(mailTemplate, model)
22+
String body = MarkdownUtils.markdownToHtmlAndSanitise(bodyMarkdown)
2123

2224
log.info("Sending email: ${subjectLine} to: ${recipient}, from: ${sender}, cc:${ccList}, body: ${body}")
2325
// This is to prevent spamming real users while testing.

grails-app/taglib/au/org/ala/merit/FCTagLib.groovy

+3-24
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@ package au.org.ala.merit
22

33
import au.org.ala.cas.util.AuthenticationCookieUtils
44
import au.org.ala.merit.config.ProgramConfig
5+
import au.org.ala.merit.util.MarkdownUtils
56
import au.org.ala.web.AuthService
67
import bootstrap.Attribute
7-
import com.naleid.grails.MarkdownService
88
import grails.converters.JSON
99
import grails.web.servlet.mvc.GrailsParameterMap
1010
import groovy.util.logging.Slf4j
1111
import groovy.xml.MarkupBuilder
1212
import org.apache.commons.lang.WordUtils
1313
import org.grails.web.json.JSONArray
1414
import org.grails.web.json.JSONObject
15-
import org.owasp.html.HtmlChangeListener
16-
import org.owasp.html.HtmlPolicyBuilder
17-
import org.owasp.html.PolicyFactory
18-
import org.owasp.html.Sanitizers
1915

2016
@Slf4j
2117
class FCTagLib {
@@ -25,12 +21,7 @@ class FCTagLib {
2521
def commonService
2622
def userService
2723
def settingService
28-
MarkdownService markdownService
2924
AuthService authService
30-
MetadataService metadataService
31-
32-
/** Allow simple formatting, links and text within p and divs by default */
33-
def policy = (Sanitizers.FORMATTING & Sanitizers.LINKS & Sanitizers.BLOCKS) & new HtmlPolicyBuilder().allowTextIn("p", "div").toFactory()
3425

3526
def textField = { attrs ->
3627
def outerClass = attrs.remove 'outerClass'
@@ -1170,23 +1161,11 @@ class FCTagLib {
11701161
def markdownToHtml = { Map attrs, body ->
11711162
String text = attrs.text ?: body()
11721163

1173-
out << markdownToHtmlAndSanitise(text)
1164+
out << MarkdownUtils.markdownToHtmlAndSanitise(text)
11741165
}
11751166

11761167
private String markdownToHtmlAndSanitise(String text) {
1177-
String html = markdownService.markdown(text)
1178-
internalSanitise(policy, html)
1179-
}
1180-
1181-
private static String internalSanitise(PolicyFactory policyFactory, String input, String imageId = '', String metadataName = '') {
1182-
policyFactory.sanitize(input, new HtmlChangeListener<Object>() {
1183-
void discardedTag(Object context, String elementName) {
1184-
log.warn("Dropping element $elementName in $imageId.$metadataName")
1185-
}
1186-
void discardedAttributes(Object context, String tagName, String... attributeNames) {
1187-
log.warn("Dropping attributes $attributeNames from $tagName in $imageId.$metadataName")
1188-
}
1189-
}, null)
1168+
MarkdownUtils.markdownToHtmlAndSanitise(text)
11901169
}
11911170

11921171
private static String getScoreLabels(def scoreIds, ProgramConfig config, Boolean includeService) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package au.org.ala.merit.util
2+
3+
import groovy.transform.CompileStatic
4+
import groovy.util.logging.Slf4j
5+
import org.commonmark.parser.Parser
6+
import org.commonmark.renderer.html.HtmlRenderer
7+
import org.owasp.html.HtmlChangeListener
8+
import org.owasp.html.HtmlPolicyBuilder
9+
import org.owasp.html.PolicyFactory
10+
import org.owasp.html.Sanitizers
11+
12+
@CompileStatic
13+
@Slf4j
14+
class MarkdownUtils {
15+
16+
/** Allow simple formatting, links and text within p and divs by default */
17+
static PolicyFactory policy = (Sanitizers.FORMATTING & Sanitizers.LINKS & Sanitizers.BLOCKS) & new HtmlPolicyBuilder().allowTextIn("p", "div").toFactory()
18+
19+
static String markdownToHtmlAndSanitise(String text) {
20+
Parser parser = Parser.builder().build()
21+
org.commonmark.node.Node document = parser.parse(text)
22+
HtmlRenderer renderer = HtmlRenderer.builder().build()
23+
String html = renderer.render(document)
24+
25+
internalSanitise(policy, html)
26+
}
27+
28+
private static String internalSanitise(PolicyFactory policyFactory, String input, String imageId = '', String metadataName = '') {
29+
policyFactory.sanitize(input, new HtmlChangeListener<Object>() {
30+
void discardedTag(Object context, String elementName) {
31+
log.warn("Dropping element $elementName in $imageId.$metadataName")
32+
}
33+
void discardedAttributes(Object context, String tagName, String... attributeNames) {
34+
log.warn("Dropping attributes $attributeNames from $tagName in $imageId.$metadataName")
35+
}
36+
}, null)
37+
}
38+
39+
}

src/test/groovy/au/org/ala/merit/EmailServiceSpec.groovy

+2-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ class EmailServiceSpec extends Specification implements AutowiredTest{
5454
List usersAndRoles = [admin1, grantManager1, editor]
5555
EmailTemplate emailTemplate = EmailTemplate.DEFAULT_PLAN_SUBMITTED_EMAIL_TEMPLATE
5656
String body = "body"
57-
body.metaClass.markdownToHtml = { "Body" }
5857
EmailParams email
5958

6059
when:
@@ -69,7 +68,7 @@ class EmailServiceSpec extends Specification implements AutowiredTest{
6968
email.params.from == "merit@ala.org.au"
7069
email.params.replyTo == "merituser1@test.com"
7170
email.params.subject == "Subject"
72-
email.params.html == "Body"
71+
email.params.html == "<p>body</p>\n"
7372
}
7473

7574

@@ -88,7 +87,6 @@ class EmailServiceSpec extends Specification implements AutowiredTest{
8887
List usersAndRoles = [admin1, admin2, editor]
8988
EmailTemplate emailTemplate = EmailTemplate.DEFAULT_PLAN_APPROVED_EMAIL_TEMPLATE
9089
String body = "body"
91-
body.metaClass.markdownToHtml = { "Body" }
9290
EmailParams email
9391

9492
when:
@@ -102,7 +100,7 @@ class EmailServiceSpec extends Specification implements AutowiredTest{
102100
email.params.from == "merit@ala.org.au"
103101
email.params.replyTo == "merituser1@test.com"
104102
email.params.subject == "Subject"
105-
email.params.html == "Body"
103+
email.params.html == "<p>body</p>\n"
106104

107105
}
108106

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package au.org.ala.merit.util
2+
3+
import spock.lang.Specification
4+
5+
class MarkdownUtilsSpec extends Specification {
6+
7+
def "markdownToHtmlAndSanitise should convert markdown to HTML and sanitize it"() {
8+
given:
9+
String markdown = "# Heading\n\nThis is a [link](http://example.com)."
10+
11+
when:
12+
String result = MarkdownUtils.markdownToHtmlAndSanitise(markdown)
13+
14+
then:
15+
result == "<h1>Heading</h1>\n<p>This is a <a href=\"http://example.com\" rel=\"nofollow\">link</a>.</p>\n"
16+
}
17+
18+
def "markdownToHtmlAndSanitise should remove disallowed tags"() {
19+
given:
20+
String markdown = "<script>alert('XSS');</script>"
21+
22+
when:
23+
String result = MarkdownUtils.markdownToHtmlAndSanitise(markdown)
24+
25+
then:
26+
result == "\n"
27+
}
28+
29+
def "markdownToHtmlAndSanitise should allow simple formatting"() {
30+
given:
31+
String markdown = "**bold** *italic*"
32+
33+
when:
34+
String result = MarkdownUtils.markdownToHtmlAndSanitise(markdown)
35+
36+
then:
37+
result == "<p><strong>bold</strong> <em>italic</em></p>\n"
38+
}
39+
40+
def "markdownToHtmlAndSanitise should allow text within p and div tags"() {
41+
given:
42+
String markdown = "<p>Paragraph</p><div>Division</div>"
43+
44+
when:
45+
String result = MarkdownUtils.markdownToHtmlAndSanitise(markdown)
46+
47+
then:
48+
result == "<p>Paragraph</p><div>Division</div>\n"
49+
}
50+
}

0 commit comments

Comments
 (0)