Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46d1d9f823 |
@@ -1,8 +1,10 @@
|
||||
# Third party imports
|
||||
from rest_framework import serializers
|
||||
import base64
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from plane.utils.binary_validator import validate_binary_data
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
PageLog,
|
||||
@@ -186,3 +188,52 @@ class PageVersionDetailSerializer(BaseSerializer):
|
||||
"updated_by",
|
||||
]
|
||||
read_only_fields = ["workspace", "page"]
|
||||
|
||||
|
||||
class PageBinaryUpdateSerializer(serializers.Serializer):
|
||||
"""Serializer for updating page binary description with validation"""
|
||||
|
||||
description_binary = serializers.CharField(required=False, allow_blank=True)
|
||||
description_html = serializers.CharField(required=False, allow_blank=True)
|
||||
description = serializers.JSONField(required=False, allow_null=True)
|
||||
|
||||
def validate_description_binary(self, value):
|
||||
"""Validate the base64-encoded binary data"""
|
||||
if not value:
|
||||
return value
|
||||
|
||||
try:
|
||||
# Decode the base64 data
|
||||
binary_data = base64.b64decode(value)
|
||||
|
||||
# Validate the binary data
|
||||
is_valid, error_message = validate_binary_data(binary_data)
|
||||
if not is_valid:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid binary data: {error_message}"
|
||||
)
|
||||
|
||||
return value
|
||||
except Exception as e:
|
||||
if isinstance(e, serializers.ValidationError):
|
||||
raise
|
||||
raise serializers.ValidationError("Failed to decode base64 data")
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Update the page instance with validated data"""
|
||||
if "description_binary" in validated_data:
|
||||
if validated_data["description_binary"]:
|
||||
instance.description_binary = base64.b64decode(
|
||||
validated_data["description_binary"]
|
||||
)
|
||||
else:
|
||||
instance.description_binary = None
|
||||
|
||||
if "description_html" in validated_data:
|
||||
instance.description_html = validated_data["description_html"]
|
||||
|
||||
if "description" in validated_data:
|
||||
instance.description = validated_data["description"]
|
||||
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
@@ -25,6 +25,7 @@ from plane.app.serializers import (
|
||||
PageSerializer,
|
||||
SubPageSerializer,
|
||||
PageDetailSerializer,
|
||||
PageBinaryUpdateSerializer,
|
||||
)
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
@@ -538,32 +539,27 @@ class PagesDescriptionViewSet(BaseViewSet):
|
||||
{"description_html": page.description_html}, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
||||
# Get the base64 data from the request
|
||||
base64_data = request.data.get("description_binary")
|
||||
|
||||
# If base64 data is provided
|
||||
if base64_data:
|
||||
# Decode the base64 data to bytes
|
||||
new_binary_data = base64.b64decode(base64_data)
|
||||
# capture the page transaction
|
||||
# Use serializer for validation and update
|
||||
serializer = PageBinaryUpdateSerializer(page, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
# Capture the page transaction
|
||||
if request.data.get("description_html"):
|
||||
page_transaction.delay(
|
||||
new_value=request.data, old_value=existing_instance, page_id=pk
|
||||
)
|
||||
# Store the updated binary data
|
||||
page.description_binary = new_binary_data
|
||||
page.description_html = request.data.get("description_html")
|
||||
page.description = request.data.get("description")
|
||||
page.save()
|
||||
# Return a success response
|
||||
|
||||
# Update the page using serializer
|
||||
updated_page = serializer.save()
|
||||
|
||||
# Run background tasks
|
||||
page_version.delay(
|
||||
page_id=page.id,
|
||||
page_id=updated_page.id,
|
||||
existing_instance=existing_instance,
|
||||
user_id=request.user.id,
|
||||
)
|
||||
return Response({"message": "Updated successfully"})
|
||||
else:
|
||||
return Response({"error": "No binary data provided"})
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class PageDuplicateEndpoint(BaseAPIView):
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Python imports
|
||||
|
||||
|
||||
def validate_binary_data(binary_data):
|
||||
"""
|
||||
Validate that binary data appears to be valid document format and doesn't contain malicious content.
|
||||
|
||||
Args:
|
||||
binary_data (bytes): The binary data to validate
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid: bool, error_message: str or None)
|
||||
"""
|
||||
if not binary_data:
|
||||
return True, None # Empty is OK
|
||||
|
||||
# Size check - 10MB limit
|
||||
MAX_SIZE = 10 * 1024 * 1024
|
||||
if len(binary_data) > MAX_SIZE:
|
||||
return False, "Binary data exceeds maximum size limit (10MB)"
|
||||
|
||||
# Basic format validation
|
||||
if len(binary_data) < 4:
|
||||
return False, "Binary data too short to be valid document format"
|
||||
|
||||
# Check for suspicious text patterns (HTML/JS)
|
||||
try:
|
||||
decoded_text = binary_data.decode("utf-8", errors="ignore")[:200]
|
||||
suspicious_patterns = [
|
||||
"<html",
|
||||
"<!doctype",
|
||||
"<script",
|
||||
"javascript:",
|
||||
"data:",
|
||||
"<iframe",
|
||||
]
|
||||
if any(pattern in decoded_text.lower() for pattern in suspicious_patterns):
|
||||
return False, "Binary data contains suspicious content patterns"
|
||||
except:
|
||||
pass # Binary data might not be decodable as text, which is fine
|
||||
|
||||
return True, None
|
||||
Reference in New Issue
Block a user