From 90703c45534b6cba170ecdcfae963c969464efbd Mon Sep 17 00:00:00 2001 From: Wes Appler <145372368+wes-otf@users.noreply.github.com> Date: Mon, 11 Mar 2024 08:11:12 -0400 Subject: [PATCH] Replace bleach with nh3 (#3696) Fixes #3693 Aims to fully replace bleach with nh3 due to bleach deprecation. Currently, [django-nh3](https://github.com/marksweb/django-nh3) is in it's infancy, but seems like it could be an almost drop in replacement for [django-bleach](https://github.com/marksweb/django-bleach), for I [forked it](https://github.com/wes-otf/django-nh3) and made some small additions that would allow it to work for our purposes and be smoothly migrated. Initial smoke testing in Hypha seems to work exactly as bleach did but needs more extensive testing. Ideally I would smooth out some edges of my fork and put in a PR to django-nh3. Let me know any thoughts/questions! --- .../activity/include/listing_base.html | 6 +- .../include/notifications_dropdown.html | 2 +- .../templates/activity/notifications.html | 3 +- .../messages/email/determination.html | 4 +- hypha/apply/api/v1/serializers.py | 4 +- .../dashboard/applicant_dashboard.html | 4 +- .../dashboard/contracting_dashboard.html | 4 +- .../templates/dashboard/dashboard.html | 4 +- .../dashboard/finance_dashboard.html | 4 +- hypha/apply/determinations/models.py | 4 +- .../base_determination_form.html | 2 +- .../determinations/determination_detail.html | 8 +-- hypha/apply/funds/differ.py | 26 +++++++-- hypha/apply/funds/forms.py | 4 +- .../application_projects/report_detail.html | 6 +- .../application_projects/vendor_detail.html | 6 +- .../review/render_scored_answer_field.html | 4 +- .../templates/review/review_detail.html | 2 +- .../review/templates/review/review_list.html | 4 +- hypha/apply/stream_forms/blocks.py | 10 ++-- .../stream_forms/render_markdown_field.html | 4 +- .../stream_forms/render_unsafe_field.html | 8 +-- .../apply/templates/forms/includes/field.html | 4 +- hypha/apply/utils/blocks.py | 6 +- .../apply_home/includes/apply_listing.html | 4 +- .../news/templates/news/news_index.html | 56 +++++++++++++++++++ hypha/settings/base.py | 30 +++++----- hypha/settings/django.py | 2 +- requirements.txt | 2 +- 29 files changed, 149 insertions(+), 78 deletions(-) create mode 100644 hypha/public/news/templates/news/news_index.html diff --git a/hypha/apply/activity/templates/activity/include/listing_base.html b/hypha/apply/activity/templates/activity/include/listing_base.html index 2336b81eb..af43a912d 100644 --- a/hypha/apply/activity/templates/activity/include/listing_base.html +++ b/hypha/apply/activity/templates/activity/include/listing_base.html @@ -1,4 +1,4 @@ -{% load i18n activity_tags bleach_tags markdown_tags submission_tags apply_tags heroicons %} +{% load i18n activity_tags nh3_tags markdown_tags submission_tags apply_tags heroicons %} <div class="feed__item feed__item--{{ activity.type }} border shadow-sm rounded-sm pb-2 " id="communications#{{ activity.id }}"> <div class="feed__pre-content hidden lg:block"> @@ -48,7 +48,7 @@ data-visibility="{{activity.visibility}}" data-edit-url="{% url 'api:v1:comments-edit' pk=activity.pk %}" > - {{ activity|display_for:request.user|submission_links|markdown|bleach }} + {{ activity|display_for:request.user|submission_links|markdown|nh3 }} </div> <style> @media only screen and (min-width: 1024px){ @@ -60,7 +60,7 @@ <div class="js-edit-block pe-3" aria-live="polite"></div> {% else %} <div class="px-3 prose"> - {{ activity|display_for:request.user|submission_links|markdown|bleach }} + {{ activity|display_for:request.user|submission_links|markdown|nh3 }} </div> {% endif %} diff --git a/hypha/apply/activity/templates/activity/include/notifications_dropdown.html b/hypha/apply/activity/templates/activity/include/notifications_dropdown.html index 65d7830fb..0866fcae5 100644 --- a/hypha/apply/activity/templates/activity/include/notifications_dropdown.html +++ b/hypha/apply/activity/templates/activity/include/notifications_dropdown.html @@ -1,4 +1,4 @@ -{% load i18n activity_tags bleach_tags markdown_tags submission_tags apply_tags %} +{% load i18n activity_tags nh3_tags markdown_tags submission_tags apply_tags %} <div class="notifications notifications--dropdown"> <div class="notifications__content zeta" role="activity"> diff --git a/hypha/apply/activity/templates/activity/notifications.html b/hypha/apply/activity/templates/activity/notifications.html index 647b09ca3..130b90d03 100644 --- a/hypha/apply/activity/templates/activity/notifications.html +++ b/hypha/apply/activity/templates/activity/notifications.html @@ -1,6 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n static activity_tags apply_tags bleach_tags markdown_tags submission_tags heroicons %} - +{% load i18n static activity_tags apply_tags nh3_tags markdown_tags submission_tags heroicons %} {% block content %} <div class="admin-bar"> <div class="admin-bar__inner"> diff --git a/hypha/apply/activity/templates/messages/email/determination.html b/hypha/apply/activity/templates/messages/email/determination.html index 99f53547d..325d559b1 100644 --- a/hypha/apply/activity/templates/messages/email/determination.html +++ b/hypha/apply/activity/templates/messages/email/determination.html @@ -1,8 +1,8 @@ {% extends "messages/email/applicant_base.html" %} -{% load bleach_tags i18n %} +{% load nh3_tags i18n %} {% block content %}{% trans "Your application has been reviewed and the outcome is" %}: {{ determination.clean_outcome }} - {{ determination.message|bleach|striptags }} + {{ determination.message|nh3|striptags }} {% trans "Read the full determination here" %}: {{ request.scheme }}://{{ request.get_host }}{{ determination.get_absolute_url }}{% endblock %} diff --git a/hypha/apply/api/v1/serializers.py b/hypha/apply/api/v1/serializers.py index 01288cc79..d80715f24 100644 --- a/hypha/apply/api/v1/serializers.py +++ b/hypha/apply/api/v1/serializers.py @@ -1,5 +1,5 @@ from django.contrib.auth import get_user_model -from django_bleach.templatetags.bleach_tags import bleach_value +from django_nh3.templatetags.nh3_tags import nh3_value from rest_framework import serializers from hypha.apply.activity.models import Activity @@ -416,7 +416,7 @@ class CommentSerializer(serializers.ModelSerializer): ) def get_message(self, obj): - return bleach_value(markdown_to_html(obj.message)) + return nh3_value(markdown_to_html(obj.message)) def get_editable(self, obj): return self.context["request"].user == obj.user diff --git a/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html b/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html index 6d53d17f8..992c709d4 100644 --- a/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html +++ b/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html @@ -1,6 +1,6 @@ {% extends "base-apply.html" %} {% load render_table from django_tables2 %} -{% load i18n static wagtailcore_tags workflow_tags statusbar_tags heroicons dashboard_statusbar_tags apply_tags invoice_tools markdown_tags bleach_tags %} +{% load i18n static wagtailcore_tags workflow_tags statusbar_tags heroicons dashboard_statusbar_tags apply_tags invoice_tools markdown_tags nh3_tags %} {% block body_class %}bg-light-grey{% endblock %} {% block title %}{% trans "Dashboard" %}{% endblock %} @@ -31,7 +31,7 @@ {% for task in my_tasks.data %} <div class="bg-white p-1 flex mb-1 items-center"> <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg> - <div class="flex-1">{{ task.text|markdown|bleach }}</div> + <div class="flex-1">{{ task.text|markdown|nh3 }}</div> <a class="button button-primary m-2" href="{{ task.url }}">View</a> </div> {% endfor %} diff --git a/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html b/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html index 3048ee170..2054d55a8 100644 --- a/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html +++ b/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html @@ -1,6 +1,6 @@ {% extends "base-apply.html" %} {% load render_table from django_tables2 %} -{% load i18n static markdown_tags bleach_tags %} +{% load i18n static markdown_tags nh3_tags %} {% block title %}{% trans "Dashboard" %}{% endblock %} @@ -21,7 +21,7 @@ {% for task in my_tasks.data %} <div class="bg-white p-1 flex mb-1 items-center"> <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg> - <div class="flex-1">{{ task.text|markdown|bleach }}</div> + <div class="flex-1">{{ task.text|markdown|nh3 }}</div> <a class="button button-primary m-2" href="{{ task.url }}">View</a> </div> {% endfor %} diff --git a/hypha/apply/dashboard/templates/dashboard/dashboard.html b/hypha/apply/dashboard/templates/dashboard/dashboard.html index 6252c72f6..416674592 100644 --- a/hypha/apply/dashboard/templates/dashboard/dashboard.html +++ b/hypha/apply/dashboard/templates/dashboard/dashboard.html @@ -1,6 +1,6 @@ {% extends "base-apply.html" %} {% load render_table from django_tables2 %} -{% load i18n static bleach_tags markdown_tags %} +{% load i18n static nh3_tags markdown_tags %} {% block extra_css %} {{ my_reviewed.filterset.form.media.css }} @@ -30,7 +30,7 @@ {% for task in my_tasks.data %} <div class="bg-white p-1 flex mb-1 items-center"> <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg> - <div class="flex-1">{{ task.text|markdown|bleach }}</div> + <div class="flex-1">{{ task.text|markdown|nh3 }}</div> <a class="button button-primary m-2" href="{{ task.url }}">View</a> </div> {% endfor %} diff --git a/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html b/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html index 34f6b6962..6320843ea 100644 --- a/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html +++ b/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html @@ -1,6 +1,6 @@ {% extends "base-apply.html" %} {% load render_table from django_tables2 %} -{% load i18n static markdown_tags bleach_tags %} +{% load i18n static markdown_tags nh3_tags %} {% block title %}{% trans "Dashboard" %}{% endblock %} @@ -21,7 +21,7 @@ {% for task in my_tasks.data %} <div class="bg-white p-1 flex mb-1 items-center"> <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg> - <div class="flex-1">{{ task.text|markdown|bleach }}</div> + <div class="flex-1">{{ task.text|markdown|nh3 }}</div> <a class="button button-primary m-2" href="{{ task.url }}">View</a> </div> {% endfor %} diff --git a/hypha/apply/determinations/models.py b/hypha/apply/determinations/models.py index 0707ba0cf..09bbfb22c 100644 --- a/hypha/apply/determinations/models.py +++ b/hypha/apply/determinations/models.py @@ -1,4 +1,4 @@ -import bleach +import nh3 from django.conf import settings from django.core.serializers.json import DjangoJSONEncoder from django.db import models @@ -130,7 +130,7 @@ class Determination(DeterminationFormFieldsMixin, AccessFormData, models.Model): @property def stripped_message(self): - return bleach.clean(self.message, tags=[], strip=True) + return nh3.clean(self.message, tags=set()) @property def clean_outcome(self): diff --git a/hypha/apply/determinations/templates/determinations/base_determination_form.html b/hypha/apply/determinations/templates/determinations/base_determination_form.html index ec982f879..153c84bc6 100644 --- a/hypha/apply/determinations/templates/determinations/base_determination_form.html +++ b/hypha/apply/determinations/templates/determinations/base_determination_form.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n static bleach_tags %} +{% load i18n static nh3_tags %} {% block title %}{% if object %}{% trans "Edit a Determination" %} {% if object.is_draft %}{% trans "draft" %}{% endif %}{% else %}{% trans "Create a Determination" %}{% endif %}{% endblock %} {% block content %} diff --git a/hypha/apply/determinations/templates/determinations/determination_detail.html b/hypha/apply/determinations/templates/determinations/determination_detail.html index c160ddfe2..0ca28fead 100644 --- a/hypha/apply/determinations/templates/determinations/determination_detail.html +++ b/hypha/apply/determinations/templates/determinations/determination_detail.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n bleach_tags heroicons %} +{% load i18n nh3_tags heroicons %} {% block title %}{% trans "Determination for" %} {{ determination.submission.title }}{% endblock %} @@ -32,14 +32,14 @@ <div class="rich-text rich-text--answers prose"> <h4>{% trans "Determination message" %}</h4> - {{ determination.message|bleach }} + {{ determination.message|nh3 }} {% for group in determination.detailed_data.values %} {% if group.title %} - <h4>{{ group.title|bleach }}</h4> + <h4>{{ group.title|nh3 }}</h4> {% endif %} {% for question, answer in group.questions %} <h5>{{ question }}</h5> - {% if answer %}{% if answer == True %}{{ answer|yesno:"Agree,Disagree" }}{% else %}{{ answer|bleach }}{% endif %}{% else %}-{% endif %} + {% if answer %}{% if answer == True %}{{ answer|yesno:"Agree,Disagree" }}{% else %}{{ answer|nh3 }}{% endif %}{% else %}-{% endif %} {% endfor %} {% endfor %} </div> diff --git a/hypha/apply/funds/differ.py b/hypha/apply/funds/differ.py index 66ccf9ffb..0c289eb98 100644 --- a/hypha/apply/funds/differ.py +++ b/hypha/apply/funds/differ.py @@ -1,7 +1,8 @@ import re from difflib import SequenceMatcher +from typing import Tuple -from bleach.sanitizer import Cleaner +import nh3 from django.utils.html import format_html from django.utils.safestring import mark_safe @@ -16,13 +17,26 @@ def wrap_added(text): return format_html('<span class="bg-green-200">{}</span>', mark_safe(text)) -def compare(answer_a, answer_b, should_bleach=True): - if should_bleach: - cleaner = Cleaner(tags=["h4"], attributes={}, strip=True) +def compare(answer_a: str, answer_b: str, should_clean: bool = True) -> Tuple[str, str]: + """Compare two strings, populate diff HTML and insert it, and return a tuple of the given strings. + + Args: + answer_a: + The original string + answer_b: + The string to compare to the original + should_clean: + Optional boolean to determine if the string should be sanitized with NH3 (default=True) + + Returns: + A tuple of the original strings with diff HTML inserted. + """ + + if should_clean: answer_a = re.sub("(<li[^>]*>)", r"\1â—¦ ", answer_a) answer_b = re.sub("(<li[^>]*>)", r"\1â—¦ ", answer_b) - answer_a = cleaner.clean(answer_a) - answer_b = cleaner.clean(answer_b) + answer_a = nh3.clean(answer_a, tags={"h4"}, attributes={}) + answer_b = nh3.clean(answer_b, tags={"h4"}, attributes={}) diff = SequenceMatcher(None, answer_a, answer_b) from_diff = [] diff --git a/hypha/apply/funds/forms.py b/hypha/apply/funds/forms.py index d1face9a8..3921248b0 100644 --- a/hypha/apply/funds/forms.py +++ b/hypha/apply/funds/forms.py @@ -3,7 +3,7 @@ from functools import partial from itertools import groupby from operator import methodcaller -import bleach +import nh3 from django import forms from django.db.models import Q from django.utils.safestring import mark_safe @@ -448,7 +448,7 @@ def make_role_reviewer_fields(): staff_reviewers = User.objects.staff().only("full_name", "pk") for role in ReviewerRole.objects.all().order_by("order"): - role_name = bleach.clean(role.name, strip=True) + role_name = nh3.clean(role.name, tags=set()) field_name = f"role_reviewer_{role.id}" field = forms.ModelChoiceField( queryset=staff_reviewers, diff --git a/hypha/apply/projects/templates/application_projects/report_detail.html b/hypha/apply/projects/templates/application_projects/report_detail.html index a2cf266ef..b0209e7be 100644 --- a/hypha/apply/projects/templates/application_projects/report_detail.html +++ b/hypha/apply/projects/templates/application_projects/report_detail.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n static bleach_tags heroicons %} +{% load i18n static nh3_tags heroicons %} {% block title %}{% trans "Report" %} | {{ object.project.title }}{% endblock %} {% block body_class %}{% endblock %} @@ -28,12 +28,12 @@ {% else %} <h4>{% trans "Public Report" %}</h4> <div class="rich-text"> - {{ object.current.public_content|bleach|safe }} + {{ object.current.public_content|nh3|safe }} </div> <h4>{% trans "Private Report" %}</h4> <div class="rich-text"> - {{ object.current.private_content|bleach|safe }} + {{ object.current.private_content|nh3|safe }} </div> {% for file in object.current.files.all %} {% if forloop.first %} diff --git a/hypha/apply/projects/templates/application_projects/vendor_detail.html b/hypha/apply/projects/templates/application_projects/vendor_detail.html index c45bf7c16..9fb4d2f0b 100644 --- a/hypha/apply/projects/templates/application_projects/vendor_detail.html +++ b/hypha/apply/projects/templates/application_projects/vendor_detail.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load bleach_tags i18n approval_tools heroicons %} +{% load nh3_tags i18n approval_tools heroicons %} {% user_can_edit_project object request.user as editable %} {% block title %}{% trans "Contracting Information for" %} {{ project.title }} {% endblock %} @@ -31,7 +31,7 @@ <div class="rich-text rich-text--answers"> {% for group in vendor_detailed_response.values %} {% if group.title %} - <h1>{{ group.title|bleach }}</h4> + <h1>{{ group.title|nh3 }}</h4> {% endif %} {% for question, answer in group.questions %} <h5>{{ question }}</h5> @@ -44,7 +44,7 @@ </div> </div> {% else %} - <p>{% if answer == True or answer == False %}{{ answer|yesno:"Yes,No" }}{% else %}{% if answer %}{{ answer|bleach }}{% else %}-{% endif %}{% endif %}</p> + <p>{% if answer == True or answer == False %}{{ answer|yesno:"Yes,No" }}{% else %}{% if answer %}{{ answer|nh3 }}{% else %}-{% endif %}{% endif %}</p> {% endif %} {% endfor %} {% endfor %} diff --git a/hypha/apply/review/templates/review/render_scored_answer_field.html b/hypha/apply/review/templates/review/render_scored_answer_field.html index a3ac5718d..6b94842c4 100644 --- a/hypha/apply/review/templates/review/render_scored_answer_field.html +++ b/hypha/apply/review/templates/review/render_scored_answer_field.html @@ -1,8 +1,8 @@ -{% load bleach_tags %} +{% load nh3_tags %} {% block data_display %} <div> <h5>{{ value.field_label }}</h5> <div>{{ score }}</div> - <div>{{ comment|bleach }}</div> + <div>{{ comment|nh3 }}</div> </div> {% endblock %} diff --git a/hypha/apply/review/templates/review/review_detail.html b/hypha/apply/review/templates/review/review_detail.html index 706828fd4..535f6ff36 100644 --- a/hypha/apply/review/templates/review/review_detail.html +++ b/hypha/apply/review/templates/review/review_detail.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n bleach_tags submission_tags heroicons %} +{% load i18n nh3_tags submission_tags heroicons %} {% block title %}{% trans "Review for" %} {{ review.submission.title }}{% endblock %} {% block content %} diff --git a/hypha/apply/review/templates/review/review_list.html b/hypha/apply/review/templates/review/review_list.html index 3cfdd3298..272f0b560 100644 --- a/hypha/apply/review/templates/review/review_list.html +++ b/hypha/apply/review/templates/review/review_list.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n bleach_tags review_tags workflow_tags %} +{% load i18n nh3_tags review_tags workflow_tags %} {% block title %}{% trans "Reviews" %}{% endblock %} @@ -28,7 +28,7 @@ {% elif answers.question == "Opinions"%} <td class="reviews-list__td">{{ answer }}</td> {% else %} - <td class="reviews-list__td">{{ answer|bleach }}</td> + <td class="reviews-list__td">{{ answer|nh3 }}</td> {% endif %} {% endfor %} </tr> diff --git a/hypha/apply/stream_forms/blocks.py b/hypha/apply/stream_forms/blocks.py index 70d264dd5..13dc30015 100644 --- a/hypha/apply/stream_forms/blocks.py +++ b/hypha/apply/stream_forms/blocks.py @@ -1,5 +1,5 @@ # Credit to https://github.com/BertrandBordage for initial implementation -import bleach +import nh3 from anyascii import anyascii from dateutil.parser import isoparse, parse from django import forms @@ -11,7 +11,7 @@ from django.utils.encoding import force_str from django.utils.html import conditional_escape from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ -from django_bleach.templatetags.bleach_tags import bleach_value +from django_nh3.templatetags.nh3_tags import nh3_value from wagtail.blocks import ( BooleanBlock, CharBlock, @@ -89,7 +89,7 @@ class FormFieldBlock(StructBlock): } def prepare_data(self, value, data, serialize=False): - return bleach_value(str(data)) + return nh3_value(str(data)) def render(self, value, context): data = context.get("data") @@ -141,7 +141,7 @@ class CharFieldBlock(OptionalFormFieldBlock): def get_searchable_content(self, value, data): # CharField acts as a fallback. Force data to string data = str(data) - return bleach.clean(data or "", tags=[], strip=True) + return nh3.clean(data or "", tags=set()) class MultiInputCharFieldBlock(CharFieldBlock): @@ -165,7 +165,7 @@ class TextFieldBlock(OptionalFormFieldBlock): template = "stream_forms/render_unsafe_field.html" def get_searchable_content(self, value, data): - return bleach.clean(data or "", tags=[], strip=True) + return nh3.clean(data or "", tags=set()) class NumberFieldBlock(OptionalFormFieldBlock): diff --git a/hypha/apply/stream_forms/templates/stream_forms/render_markdown_field.html b/hypha/apply/stream_forms/templates/stream_forms/render_markdown_field.html index f9b586177..4ef393bd4 100644 --- a/hypha/apply/stream_forms/templates/stream_forms/render_markdown_field.html +++ b/hypha/apply/stream_forms/templates/stream_forms/render_markdown_field.html @@ -1,8 +1,8 @@ {% extends "stream_forms/render_field.html" %} -{% load bleach_tags markdown_tags %} +{% load nh3_tags markdown_tags %} {% block data_display %} {% if data %} - {{ data|markdown|bleach }} + {{ data|markdown|nh3 }} {% else %} {{ block.super }} {% endif %} diff --git a/hypha/apply/stream_forms/templates/stream_forms/render_unsafe_field.html b/hypha/apply/stream_forms/templates/stream_forms/render_unsafe_field.html index 9725f800f..3138a4d25 100644 --- a/hypha/apply/stream_forms/templates/stream_forms/render_unsafe_field.html +++ b/hypha/apply/stream_forms/templates/stream_forms/render_unsafe_field.html @@ -1,13 +1,13 @@ {% extends "stream_forms/render_field.html" %} -{% load bleach_tags %} +{% load nh3_tags %} {% block data_display %} {% if data %} {% if value.format == 'url' %} - <a class="link" href="{{ data|bleach }}" target="_blank" rel="noopener noreferrer">{{ data|bleach }}</a> + <a class="link" href="{{ data|nh3 }}" target="_blank" rel="noopener noreferrer">{{ data|nh3 }}</a> {% elif value.format == 'email' %} - <a class="u-email" href="mailto:{{ data|bleach }}">{{ data|bleach }}</a> + <a class="u-email" href="mailto:{{ data|nh3 }}">{{ data|nh3 }}</a> {% else %} - <p>{{ data|bleach }}</p> + <p>{{ data|nh3 }}</p> {% endif %} {% else %} {{ block.super }} diff --git a/hypha/apply/templates/forms/includes/field.html b/hypha/apply/templates/forms/includes/field.html index b9dec1d83..f4c2802c1 100644 --- a/hypha/apply/templates/forms/includes/field.html +++ b/hypha/apply/templates/forms/includes/field.html @@ -1,5 +1,5 @@ {% load i18n util_tags %} -{% load bleach_tags markdown_tags heroicons %} +{% load nh3_tags markdown_tags heroicons %} {% with widget_type=field|widget_type field_type=field|field_type %} <div class="form__group {{ field.id_for_label }} form__group--{{ widget_type }} {% if widget_type == 'checkbox_input' %} form__group--checkbox{% endif %}{% if widget_type == 'clearable_file_input' or widget_type == 'multi_file_input' or widget_type == 'single_file_field_widget' or widget_type == 'multi_file_field_widget' %} form__group--file{% endif %}{% if field.help_text %} form__group--wrap{% endif %}{% if field.errors %} form__error{% endif %}{% if is_application and field.field.group_number > 1 %} field-group field-group-{{ field.field.group_number }}{% endif %}{% if is_application and field.field.grouper_for %} form-fields-grouper{% endif %}"{% if is_application and field.field.grouper_for %}data-grouper-for="{{ field.field.grouper_for }}" data-toggle-on="{{ field.field.choices.0.0 }}" data-toggle-off="{{ field.field.choices.1.0 }}"{% endif %}{% if is_application and field.field.group_number > 1 %} data-hidden="{% if not show_all_group_fields and not field.field.visible %}true{% else %}false{% endif %}" data-required="{{ field.field.required_when_visible }}"{% endif %}{% if field.field.word_limit %} data-word-limit="{{ field.field.word_limit }}"{% endif %}> @@ -34,7 +34,7 @@ {% endif %} {% if field.help_text %} - <div class="form__help prose prose-sm">{{ field.help_text|markdown|bleach }}</div> + <div class="form__help prose prose-sm">{{ field.help_text|markdown|nh3 }}</div> {% endif %} {% if field.field.help_link %} diff --git a/hypha/apply/utils/blocks.py b/hypha/apply/utils/blocks.py index 7f74b0c50..baee0b40e 100644 --- a/hypha/apply/utils/blocks.py +++ b/hypha/apply/utils/blocks.py @@ -1,6 +1,6 @@ from collections import Counter -import bleach +import nh3 from django.core.exceptions import ValidationError from django.forms.utils import ErrorList from django.utils.safestring import mark_safe @@ -48,7 +48,7 @@ class RichTextFieldBlock(TextFieldBlock): icon = "form" def get_searchable_content(self, value, data): - return bleach.clean(data or "", tags=[], strip=True) + return nh3.clean(data or "", tags=set()) def no_response(self): return "<p>-</p>" @@ -64,7 +64,7 @@ class MarkdownTextFieldBlock(TextFieldBlock): template = "stream_forms/render_markdown_field.html" def get_searchable_content(self, value, data): - return bleach.clean(data or "", tags=[], strip=True) + return nh3.clean(data or "", tags=set()) def no_response(self): return "<p>-</p>" diff --git a/hypha/home/templates/apply_home/includes/apply_listing.html b/hypha/home/templates/apply_home/includes/apply_listing.html index 83d8cbf39..d4b4ac9e4 100644 --- a/hypha/home/templates/apply_home/includes/apply_listing.html +++ b/hypha/home/templates/apply_home/includes/apply_listing.html @@ -1,4 +1,4 @@ -{% load i18n bleach_tags wagtailcore_tags markdown_tags heroicons %} +{% load i18n nh3_tags wagtailcore_tags markdown_tags heroicons %} {% if page.open_round and page.list_on_front_page %} <div class="flex justify-between items-center px-4 py-8 gap-4 hover:bg-slate-50 transition-colors"> @@ -17,7 +17,7 @@ {% if page.description %} <p> - {{ page.description|markdown|bleach }} + {{ page.description|markdown|nh3 }} </p> {% endif %} </div> diff --git a/hypha/public/news/templates/news/news_index.html b/hypha/public/news/templates/news/news_index.html new file mode 100644 index 000000000..1cdeb6220 --- /dev/null +++ b/hypha/public/news/templates/news/news_index.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% load wagtailcore_tags wagtailimages_tags static markdown_tags nh3_tags %} +{% block feedlinks %}<link rel="alternate" type="application/rss+xml" title="{{ page.title }}" href="{% url "news_feed" %}">{% endblock %} +{% block body_class %}light-grey-bg{% endblock %} + +{% block content %} + <div class="wrapper wrapper--small wrapper--inner-space-medium"> + + {% if page.introduction %} + <h4 class="heading heading--listings-introduction">{{ page.introduction|markdown|nh3 }}</h4> + {% endif %} + + <form class="form" method="GET"> + <div class="form__select form__select--narrow form__select--inline"> + <select name="news_type"> + <option value="">All</option> + {% for news_type in news_types %} + <option value="{{ news_type.0 }}" {% if request.GET.news_type == news_type.0|slugify %}selected="selected"{% endif %}>{{ news_type.1 }}</option> + {% endfor %} + </select> + </div> + <button class="link link--button link--button__stretch" type="submit">Filter</button> + </form> + + {% if news %} + <div class="wrapper wrapper--listings wrapper--top-space"> + {% for n in news %} + <a class="listing" href="{% pageurl n %}"> + {% if n.listing_image %} + {% image n.listing_image fill-450x300 %} + {% endif %} + <h4 class="listing__title" role="listitem"> + {{ n.listing_title|default:n.title }} + </h4> + {% if n.listing_summary or n.introduction %} + <h6 class="listing__teaser">{{ n.listing_summary|default:n.introduction }}</h6> + {% endif %} + <span class="listing__meta"> + {{ n.display_date|date:"SHORT_DATE_FORMAT" }} + {% if n.authors.all %} + | By: + {% for author in n.authors.all %} + {{ author.author }} + {% endfor %} + {% endif %} + </span> + </a> + {% endfor %} + </div> + {% include "includes/pagination.html" with paginator_page=news %} + {% else %} + {# no items #} + {% endif %} + + </div> +{% endblock %} diff --git a/hypha/settings/base.py b/hypha/settings/base.py index 6c4eb4854..e5fb60735 100644 --- a/hypha/settings/base.py +++ b/hypha/settings/base.py @@ -383,9 +383,9 @@ SOCIAL_AUTH_PIPELINE = ( "hypha.apply.users.pipeline.make_otf_staff", ) -# Bleach Settings +# NH3 Settings -BLEACH_ALLOWED_TAGS = [ +NH3_ALLOWED_TAGS = [ "a", "b", "big", @@ -426,18 +426,20 @@ BLEACH_ALLOWED_TAGS = [ "tr", "ul", ] -BLEACH_ALLOWED_ATTRIBUTES = [ - "class", - "colspan", - "href", - "rowspan", - "target", - "title", - "width", -] -BLEACH_ALLOWED_STYLES = [] -BLEACH_STRIP_TAGS = True -BLEACH_STRIP_COMMENTS = True + +NH3_ALLOWED_ATTRIBUTES = { + "*": [ + "class", + "colspan", + "href", + "rowspan", + "target", + "title", + "width", + ] +} + +NH3_STRIP_COMMENTS = True # Hijack Settings diff --git a/hypha/settings/django.py b/hypha/settings/django.py index 40e092d9f..3f3cac9bf 100644 --- a/hypha/settings/django.py +++ b/hypha/settings/django.py @@ -57,7 +57,7 @@ INSTALLED_APPS = [ "django_filters", "django_select2", "addressfield", - "django_bleach", + "django_nh3", "django_fsm", "django_pwned_passwords", "django_slack", diff --git a/requirements.txt b/requirements.txt index 7ce442991..1ab5b86ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ click==8.1.7 dj-database-url==2.1.0 django-anymail==10.2 django-basic-auth-ip-whitelist==0.5 -django-bleach==3.1.0 +django-nh3==0.1.1 django-countries==7.5.1 django-elevate==2.0.3 django-extensions==3.2.3 -- GitLab