Skip to content

Commit bc1eb29

Browse files
zubeydecivelekzzacharo
authored andcommitted
sanitizer: allow style for description
1 parent dfef91b commit bc1eb29

6 files changed

Lines changed: 95 additions & 19 deletions

File tree

cds/modules/deposit/static/json/cds_deposit/forms/project.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"enterMode": 2,
2020
"entities": false,
2121
"height": 200,
22+
"allowedContent": true,
23+
"forcePasteAsPlainText": false,
24+
"extraAllowedContent": "*{color}; *[style]",
25+
"disableAutoInline": true,
26+
"extraPlugins": "colorbutton",
2227
"toolbar": [
2328
[
2429
"PasteText",
@@ -32,6 +37,9 @@
3237
"Subscript",
3338
"Superscript"
3439
],
40+
[
41+
"TextColor"
42+
],
3543
[
3644
"NumberedList",
3745
"BulletedList",

cds/modules/deposit/static/json/cds_deposit/forms/video.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
"enterMode": 2,
1919
"entities": false,
2020
"height": 200,
21+
"allowedContent": true,
22+
"forcePasteAsPlainText": false,
23+
"extraAllowedContent": "*{color}; *[style]",
24+
"disableAutoInline": true,
25+
"extraPlugins": "colorbutton",
2126
"toolbar": [
2227
[
2328
"PasteText",
@@ -31,6 +36,9 @@
3136
"Subscript",
3237
"Superscript"
3338
],
39+
[
40+
"TextColor"
41+
],
3442
[
3543
"NumberedList",
3644
"BulletedList",

cds/modules/records/serializers/json.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@
3232
has_read_record_permission,
3333
)
3434
from ..utils import HTMLTagRemover, parse_video_chapters, remove_html_tags
35-
from marshmallow_utils.html import sanitize_html
35+
from marshmallow_utils.html import sanitize_html, ALLOWED_HTML_ATTRS, ALLOWED_CSS_STYLES
36+
37+
CUSTOM_ALLOWED_ATTRS = {
38+
**ALLOWED_HTML_ATTRS,
39+
"span": ALLOWED_HTML_ATTRS.get("span", []) + ["style"],
40+
"p": ALLOWED_HTML_ATTRS.get("p", []) + ["style"],
41+
}
42+
43+
CUSTOM_ALLOWED_CSS = ALLOWED_CSS_STYLES + ["color"]
3644

3745

3846
class CDSJSONSerializer(JSONSerializer):
@@ -60,7 +68,11 @@ def _sanitize_metadata(self, metadata):
6068
if "description" in metadata:
6169
description = metadata["description"]
6270
description = self.html_tag_remover.unescape(description)
63-
metadata["description"] = sanitize_html(description)
71+
metadata["description"] = sanitize_html(
72+
description,
73+
attrs=CUSTOM_ALLOWED_ATTRS,
74+
css_styles=CUSTOM_ALLOWED_CSS,
75+
)
6476

6577
if "translations" in metadata:
6678
for t in metadata["translations"]:
@@ -108,12 +120,12 @@ def preprocess_record(self, pid, record, links_factory=None):
108120
except KeyError:
109121
# ignore error if keys are missing in the metadata
110122
pass
111-
112-
description = metadata.get('description', '')
123+
124+
description = metadata.get("description", "")
113125
if description:
114-
metadata['chapters'] = parse_video_chapters(description)
126+
metadata["chapters"] = parse_video_chapters(description)
115127
else:
116-
metadata['chapters'] = []
128+
metadata["chapters"] = []
117129

118130
return result
119131

cds/modules/records/serializers/schemas/common.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from marshmallow import RAISE, Schema, ValidationError, fields, validates_schema
2323
from marshmallow.validate import Length
2424
from marshmallow_utils.fields import SanitizedHTML
25+
from marshmallow_utils.html import sanitize_html
2526

2627
from ...api import Keyword
2728
from ...resolver import keyword_resolver
@@ -219,4 +220,42 @@ class RelatedIdentifiersSchema(Schema):
219220
identifier = fields.Str(required=True)
220221
scheme = fields.Str(required=True)
221222
relation_type = fields.Str(required=True)
222-
resource_type = fields.Str()
223+
resource_type = fields.Str()
224+
225+
226+
class SanitizedHTMLWithCSS(fields.String):
227+
"""Enhanced SanitizedHTML supporting inline CSS sanitization.
228+
229+
Fully compatible with marshmallow_utils.fields.SanitizedHTML,
230+
but adds CSS.
231+
"""
232+
233+
def __init__(
234+
self,
235+
tags=None,
236+
attrs=None,
237+
css_styles=None,
238+
*args,
239+
**kwargs,
240+
):
241+
"""
242+
:param tags: Allowed HTML tags.
243+
:param attrs: Allowed HTML attributes per tag.
244+
:param css_styles: List of allowed CSS properties (e.g., ["color"]).
245+
"""
246+
super().__init__(*args, **kwargs)
247+
248+
self.tags = tags
249+
self.attrs = attrs
250+
self.css_styles = css_styles
251+
252+
def _deserialize(self, value, attr, data, **kwargs):
253+
"""Run bleach sanitize with CSS support."""
254+
value = super()._deserialize(value, attr, data, **kwargs)
255+
256+
return sanitize_html(
257+
value,
258+
tags=self.tags,
259+
attrs=self.attrs,
260+
css_styles=self.css_styles,
261+
)

cds/modules/records/serializers/schemas/project.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
# 59 Temple Place, Suite 330, Boston, MA 02D111-1307, USA.
1919
"""Project JSON schema."""
2020

21+
from cds.modules.records.serializers.json import (
22+
CUSTOM_ALLOWED_ATTRS,
23+
CUSTOM_ALLOWED_CSS,
24+
)
2125
from invenio_jsonschemas import current_jsonschemas
2226
from marshmallow import Schema, fields, pre_load, post_load
23-
from marshmallow_utils.fields import SanitizedHTML
2427

2528
from ....deposit.api import Project, deposit_video_resolver
2629
from .common import (
@@ -32,6 +35,7 @@
3235
KeywordsSchema,
3336
LicenseSchema,
3437
OaiSchema,
38+
SanitizedHTMLWithCSS,
3539
StrictKeysSchema,
3640
TitleSchema,
3741
TranslationsSchema,
@@ -77,7 +81,10 @@ class ProjectSchema(StrictKeysSchema):
7781
_deposit = fields.Nested(ProjectDepositSchema, required=True)
7882
_cds = fields.Nested(_CDSSSchema, required=True)
7983
title = fields.Nested(TitleSchema, required=True)
80-
description = SanitizedHTML()
84+
description = SanitizedHTMLWithCSS(
85+
attrs=CUSTOM_ALLOWED_ATTRS,
86+
css_styles=CUSTOM_ALLOWED_CSS,
87+
)
8188
category = fields.Str(required=True)
8289
type = fields.Str(required=True)
8390
note = fields.Str()

cds/modules/records/serializers/schemas/video.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
# 59 Temple Place, Suite 330, Boston, MA 02D111-1307, USA.
1919
"""Video JSON schema."""
2020

21+
from cds.modules.records.serializers.json import (
22+
CUSTOM_ALLOWED_ATTRS,
23+
CUSTOM_ALLOWED_CSS,
24+
)
2125
from invenio_jsonschemas import current_jsonschemas
2226
from marshmallow import Schema, fields, pre_load, post_load
23-
from marshmallow_utils.fields import SanitizedHTML
2427
from ....deposit.api import Video
2528
from ..fields.datetime import DateString
2629
from .common import (
@@ -38,6 +41,7 @@
3841
OaiSchema,
3942
RelatedIdentifiersSchema,
4043
RelatedLinksSchema,
44+
SanitizedHTMLWithCSS,
4145
StrictKeysSchema,
4246
TitleSchema,
4347
TranslationsSchema,
@@ -131,7 +135,9 @@ class VideoSchema(StrictKeysSchema):
131135
contributors = fields.Nested(ContributorSchema, many=True, required=True)
132136
copyright = fields.Nested(CopyrightSchema)
133137
date = DateString(required=True)
134-
description = SanitizedHTML(required=True)
138+
description = SanitizedHTMLWithCSS(
139+
attrs=CUSTOM_ALLOWED_ATTRS, css_styles=CUSTOM_ALLOWED_CSS, required=True
140+
)
135141
doi = DOI()
136142
duration = fields.Str()
137143
external_system_identifiers = fields.Nested(
@@ -147,7 +153,7 @@ class VideoSchema(StrictKeysSchema):
147153
note = fields.Str()
148154
publication_date = fields.Str()
149155
recid = fields.Number()
150-
legacy_recid =fields.Number()
156+
legacy_recid = fields.Number()
151157
related_links = fields.Nested(RelatedLinksSchema, many=True)
152158
report_number = fields.List(fields.Str, many=True)
153159
schema = fields.Str(attribute="$schema", data_key="$schema")
@@ -158,15 +164,11 @@ class VideoSchema(StrictKeysSchema):
158164
_curation = fields.Nested(CurationSchema)
159165
additional_titles = fields.List(fields.Nested(AdditionalTitlesSchema))
160166
additional_descriptions = fields.List(fields.Nested(AdditionalDescriptionsSchema))
161-
alternate_identifiers = fields.Nested(
162-
AlternateIdentifiersSchema, many=True
163-
)
164-
related_identifiers = fields.Nested(
165-
RelatedIdentifiersSchema, many=True
166-
)
167+
alternate_identifiers = fields.Nested(AlternateIdentifiersSchema, many=True)
168+
related_identifiers = fields.Nested(RelatedIdentifiersSchema, many=True)
167169
collections = fields.List(fields.Str, many=True)
168170
additional_languages = fields.List(fields.Str, many=True)
169-
171+
170172
# Preservation fields
171173
location = fields.Str()
172174
original_source = fields.Str()

0 commit comments

Comments
 (0)