From e739ff507671cfd1892aad2e22fae83425c31851 Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Fri, 29 Jul 2022 08:27:39 -0700 Subject: [PATCH] feat: allow more HTML tags in report description (#20908) --- superset/reports/notifications/email.py | 38 +++++++++++++-- tests/unit_tests/notifications/email_tests.py | 47 +++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 tests/unit_tests/notifications/email_tests.py diff --git a/superset/reports/notifications/email.py b/superset/reports/notifications/email.py index 0fd571c386..42ee9b4663 100644 --- a/superset/reports/notifications/email.py +++ b/superset/reports/notifications/email.py @@ -38,6 +38,31 @@ logger = logging.getLogger(__name__) TABLE_TAGS = ["table", "th", "tr", "td", "thead", "tbody", "tfoot"] TABLE_ATTRIBUTES = ["colspan", "rowspan", "halign", "border", "class"] +ALLOWED_TAGS = [ + "a", + "abbr", + "acronym", + "b", + "blockquote", + "br", + "code", + "div", + "em", + "i", + "li", + "ol", + "p", + "strong", + "ul", +] + TABLE_TAGS + +ALLOWED_ATTRIBUTES = { + "a": ["href", "title"], + "abbr": ["title"], + "acronym": ["title"], + **{tag: TABLE_ATTRIBUTES for tag in TABLE_TAGS}, +} + @dataclass class EmailContent: @@ -82,13 +107,19 @@ class EmailNotification(BaseNotification): # pylint: disable=too-few-public-met } # Strip any malicious HTML from the description - description = bleach.clean(self._content.description or "") + description = bleach.clean( + self._content.description or "", + tags=ALLOWED_TAGS, + attributes=ALLOWED_ATTRIBUTES, + ) # Strip malicious HTML from embedded data, allowing only table elements if self._content.embedded_data is not None: df = self._content.embedded_data html_table = bleach.clean( - df.to_html(na_rep="", index=True), + df.to_html(na_rep="", index=True, escape=True), + # pandas will escape the HTML in cells already, so passing + # more allowed tags here will not work tags=TABLE_TAGS, attributes=TABLE_ATTRIBUTES, ) @@ -127,7 +158,8 @@ class EmailNotification(BaseNotification): # pylint: disable=too-few-public-met -

{description}

+
{description}
+
{call_to_action}

{html_table} {img_tag} diff --git a/tests/unit_tests/notifications/email_tests.py b/tests/unit_tests/notifications/email_tests.py new file mode 100644 index 0000000000..9bc8b8090f --- /dev/null +++ b/tests/unit_tests/notifications/email_tests.py @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import pandas as pd +from flask.ctx import AppContext + + +def test_render_description_with_html(app_context: AppContext) -> None: + # `superset.models.helpers`, a dependency of following imports, + # requires app context + from superset.reports.models import ReportRecipients, ReportRecipientType + from superset.reports.notifications.base import NotificationContent + from superset.reports.notifications.email import EmailNotification + + content = NotificationContent( + name="test alert", + embedded_data=pd.DataFrame( + { + "A": [1, 2, 3], + "B": [4, 5, 6], + "C": ["111", "222", '333'], + } + ), + description='

This is a test alert


', + ) + email_body = ( + EmailNotification( + recipient=ReportRecipients(type=ReportRecipientType.EMAIL), content=content + ) + ._get_content() + .body + ) + assert '

This is a test alert


' in email_body + assert '<a href="http://www.example.com">333</a>' in email_body