Skip to content

Commit c4d2c5b

Browse files
sync: canary changes to preview
2 parents 8228ecc + d4705e1 commit c4d2c5b

File tree

11 files changed

+609
-34
lines changed

11 files changed

+609
-34
lines changed

apps/api/plane/api/serializers/issue.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
User,
2323
EstimatePoint,
2424
)
25+
from plane.utils.content_validator import (
26+
validate_html_content,
27+
validate_json_content,
28+
validate_binary_data,
29+
)
2530

2631
from .base import BaseSerializer
2732
from .cycle import CycleLiteSerializer, CycleSerializer
@@ -83,6 +88,22 @@ def validate(self, data):
8388
except Exception:
8489
raise serializers.ValidationError("Invalid HTML passed")
8590

91+
# Validate description content for security
92+
if data.get("description"):
93+
is_valid, error_msg = validate_json_content(data["description"])
94+
if not is_valid:
95+
raise serializers.ValidationError({"description": error_msg})
96+
97+
if data.get("description_html"):
98+
is_valid, error_msg = validate_html_content(data["description_html"])
99+
if not is_valid:
100+
raise serializers.ValidationError({"description_html": error_msg})
101+
102+
if data.get("description_binary"):
103+
is_valid, error_msg = validate_binary_data(data["description_binary"])
104+
if not is_valid:
105+
raise serializers.ValidationError({"description_binary": error_msg})
106+
86107
# Validate assignees are from project
87108
if data.get("assignees", []):
88109
data["assignees"] = ProjectMember.objects.filter(
@@ -648,7 +669,6 @@ class IssueExpandSerializer(BaseSerializer):
648669
assignees = serializers.SerializerMethodField()
649670
state = StateLiteSerializer(read_only=True)
650671

651-
652672
def get_labels(self, obj):
653673
expand = self.context.get("expand", [])
654674
if "labels" in expand:
@@ -666,7 +686,6 @@ def get_assignees(self, obj):
666686
).data
667687
return [ia.assignee_id for ia in obj.issue_assignee.all()]
668688

669-
670689
class Meta:
671690
model = Issue
672691
fields = "__all__"

apps/api/plane/api/serializers/project.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
Estimate,
1111
)
1212

13+
from plane.utils.content_validator import (
14+
validate_html_content,
15+
validate_json_content,
16+
)
1317
from .base import BaseSerializer
1418

1519

@@ -191,6 +195,29 @@ def validate(self, data):
191195
"Default assignee should be a user in the workspace"
192196
)
193197

198+
# Validate description content for security
199+
if "description" in data and data["description"]:
200+
# For Project, description might be text field, not JSON
201+
if isinstance(data["description"], dict):
202+
is_valid, error_msg = validate_json_content(data["description"])
203+
if not is_valid:
204+
raise serializers.ValidationError({"description": error_msg})
205+
206+
if "description_text" in data and data["description_text"]:
207+
is_valid, error_msg = validate_json_content(data["description_text"])
208+
if not is_valid:
209+
raise serializers.ValidationError({"description_text": error_msg})
210+
211+
if "description_html" in data and data["description_html"]:
212+
if isinstance(data["description_html"], dict):
213+
is_valid, error_msg = validate_json_content(data["description_html"])
214+
else:
215+
is_valid, error_msg = validate_html_content(
216+
str(data["description_html"])
217+
)
218+
if not is_valid:
219+
raise serializers.ValidationError({"description_html": error_msg})
220+
194221
return data
195222

196223
def create(self, validated_data):

apps/api/plane/app/serializers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
SubPageSerializer,
9797
PageDetailSerializer,
9898
PageVersionSerializer,
99+
PageBinaryUpdateSerializer,
99100
PageVersionDetailSerializer,
100101
)
101102

apps/api/plane/app/serializers/draft.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
ProjectMember,
2222
EstimatePoint,
2323
)
24+
from plane.utils.content_validator import (
25+
validate_html_content,
26+
validate_json_content,
27+
validate_binary_data,
28+
)
2429
from plane.app.permissions import ROLE
2530

2631

@@ -70,14 +75,21 @@ def validate(self, attrs):
7075
):
7176
raise serializers.ValidationError("Start date cannot exceed target date")
7277

73-
try:
74-
if attrs.get("description_html", None) is not None:
75-
parsed = html.fromstring(attrs["description_html"])
76-
parsed_str = html.tostring(parsed, encoding="unicode")
77-
attrs["description_html"] = parsed_str
78-
79-
except Exception:
80-
raise serializers.ValidationError("Invalid HTML passed")
78+
# Validate description content for security
79+
if "description" in attrs and attrs["description"]:
80+
is_valid, error_msg = validate_json_content(attrs["description"])
81+
if not is_valid:
82+
raise serializers.ValidationError({"description": error_msg})
83+
84+
if "description_html" in attrs and attrs["description_html"]:
85+
is_valid, error_msg = validate_html_content(attrs["description_html"])
86+
if not is_valid:
87+
raise serializers.ValidationError({"description_html": error_msg})
88+
89+
if "description_binary" in attrs and attrs["description_binary"]:
90+
is_valid, error_msg = validate_binary_data(attrs["description_binary"])
91+
if not is_valid:
92+
raise serializers.ValidationError({"description_binary": error_msg})
8193

8294
# Validate assignees are from project
8395
if attrs.get("assignee_ids", []):

apps/api/plane/app/serializers/issue.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
ProjectMember,
4242
EstimatePoint,
4343
)
44+
from plane.utils.content_validator import (
45+
validate_html_content,
46+
validate_json_content,
47+
validate_binary_data,
48+
)
4449

4550

4651
class IssueFlatSerializer(BaseSerializer):
@@ -122,14 +127,21 @@ def validate(self, attrs):
122127
):
123128
raise serializers.ValidationError("Start date cannot exceed target date")
124129

125-
try:
126-
if attrs.get("description_html", None) is not None:
127-
parsed = html.fromstring(attrs["description_html"])
128-
parsed_str = html.tostring(parsed, encoding="unicode")
129-
attrs["description_html"] = parsed_str
130-
131-
except Exception:
132-
raise serializers.ValidationError("Invalid HTML passed")
130+
# Validate description content for security
131+
if "description" in attrs and attrs["description"]:
132+
is_valid, error_msg = validate_json_content(attrs["description"])
133+
if not is_valid:
134+
raise serializers.ValidationError({"description": error_msg})
135+
136+
if "description_html" in attrs and attrs["description_html"]:
137+
is_valid, error_msg = validate_html_content(attrs["description_html"])
138+
if not is_valid:
139+
raise serializers.ValidationError({"description_html": error_msg})
140+
141+
if "description_binary" in attrs and attrs["description_binary"]:
142+
is_valid, error_msg = validate_binary_data(attrs["description_binary"])
143+
if not is_valid:
144+
raise serializers.ValidationError({"description_binary": error_msg})
133145

134146
# Validate assignees are from project
135147
if attrs.get("assignee_ids", []):

apps/api/plane/app/serializers/page.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Third party imports
22
from rest_framework import serializers
3+
import base64
34

45
# Module imports
56
from .base import BaseSerializer
7+
from plane.utils.content_validator import (
8+
validate_binary_data,
9+
validate_html_content,
10+
validate_json_content,
11+
)
612
from plane.db.models import (
713
Page,
814
PageLog,
@@ -186,3 +192,71 @@ class Meta:
186192
"updated_by",
187193
]
188194
read_only_fields = ["workspace", "page"]
195+
196+
197+
class PageBinaryUpdateSerializer(serializers.Serializer):
198+
"""Serializer for updating page binary description with validation"""
199+
200+
description_binary = serializers.CharField(required=False, allow_blank=True)
201+
description_html = serializers.CharField(required=False, allow_blank=True)
202+
description = serializers.JSONField(required=False, allow_null=True)
203+
204+
def validate_description_binary(self, value):
205+
"""Validate the base64-encoded binary data"""
206+
if not value:
207+
return value
208+
209+
try:
210+
# Decode the base64 data
211+
binary_data = base64.b64decode(value)
212+
213+
# Validate the binary data
214+
is_valid, error_message = validate_binary_data(binary_data)
215+
if not is_valid:
216+
raise serializers.ValidationError(
217+
f"Invalid binary data: {error_message}"
218+
)
219+
220+
return binary_data
221+
except Exception as e:
222+
if isinstance(e, serializers.ValidationError):
223+
raise
224+
raise serializers.ValidationError("Failed to decode base64 data")
225+
226+
def validate_description_html(self, value):
227+
"""Validate the HTML content"""
228+
if not value:
229+
return value
230+
231+
# Use the validation function from utils
232+
is_valid, error_message = validate_html_content(value)
233+
if not is_valid:
234+
raise serializers.ValidationError(error_message)
235+
236+
return value
237+
238+
def validate_description(self, value):
239+
"""Validate the JSON description"""
240+
if not value:
241+
return value
242+
243+
# Use the validation function from utils
244+
is_valid, error_message = validate_json_content(value)
245+
if not is_valid:
246+
raise serializers.ValidationError(error_message)
247+
248+
return value
249+
250+
def update(self, instance, validated_data):
251+
"""Update the page instance with validated data"""
252+
if "description_binary" in validated_data:
253+
instance.description_binary = validated_data.get("description_binary")
254+
255+
if "description_html" in validated_data:
256+
instance.description_html = validated_data.get("description_html")
257+
258+
if "description" in validated_data:
259+
instance.description = validated_data.get("description")
260+
261+
instance.save()
262+
return instance

apps/api/plane/app/serializers/project.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
DeployBoard,
1414
ProjectPublicMember,
1515
)
16+
from plane.utils.content_validator import (
17+
validate_html_content,
18+
validate_json_content,
19+
validate_binary_data,
20+
)
1621

1722

1823
class ProjectSerializer(BaseSerializer):
@@ -58,6 +63,32 @@ def validate_identifier(self, identifier):
5863

5964
return identifier
6065

66+
def validate(self, data):
67+
# Validate description content for security
68+
if "description" in data and data["description"]:
69+
# For Project, description might be text field, not JSON
70+
if isinstance(data["description"], dict):
71+
is_valid, error_msg = validate_json_content(data["description"])
72+
if not is_valid:
73+
raise serializers.ValidationError({"description": error_msg})
74+
75+
if "description_text" in data and data["description_text"]:
76+
is_valid, error_msg = validate_json_content(data["description_text"])
77+
if not is_valid:
78+
raise serializers.ValidationError({"description_text": error_msg})
79+
80+
if "description_html" in data and data["description_html"]:
81+
if isinstance(data["description_html"], dict):
82+
is_valid, error_msg = validate_json_content(data["description_html"])
83+
else:
84+
is_valid, error_msg = validate_html_content(
85+
str(data["description_html"])
86+
)
87+
if not is_valid:
88+
raise serializers.ValidationError({"description_html": error_msg})
89+
90+
return data
91+
6192
def create(self, validated_data):
6293
workspace_id = self.context["workspace_id"]
6394

apps/api/plane/app/serializers/workspace.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
)
2525
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
2626
from plane.utils.url import contains_url
27+
from plane.utils.content_validator import (
28+
validate_html_content,
29+
validate_json_content,
30+
validate_binary_data,
31+
)
2732

2833
# Django imports
2934
from django.core.validators import URLValidator
@@ -312,6 +317,25 @@ class Meta:
312317
read_only_fields = ["workspace", "owner"]
313318
extra_kwargs = {"name": {"required": False}}
314319

320+
def validate(self, data):
321+
# Validate description content for security
322+
if "description" in data and data["description"]:
323+
is_valid, error_msg = validate_json_content(data["description"])
324+
if not is_valid:
325+
raise serializers.ValidationError({"description": error_msg})
326+
327+
if "description_html" in data and data["description_html"]:
328+
is_valid, error_msg = validate_html_content(data["description_html"])
329+
if not is_valid:
330+
raise serializers.ValidationError({"description_html": error_msg})
331+
332+
if "description_binary" in data and data["description_binary"]:
333+
is_valid, error_msg = validate_binary_data(data["description_binary"])
334+
if not is_valid:
335+
raise serializers.ValidationError({"description_binary": error_msg})
336+
337+
return data
338+
315339

316340
class WorkspaceUserPreferenceSerializer(BaseSerializer):
317341
class Meta:

0 commit comments

Comments
 (0)