mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-12 03:06:24 +00:00
fix: resolve test file upload handling issue
- Fix test to use SimpleUploadedFile instead of raw file object - Change form.save() from static to instance method to access stored file data - Fix file data handling in form save method to use sanitized/stored data - Remove debug logging after successful resolution - All upload tests now pass with full security validation enabled The issue was that Django's InMemoryUploadedFile objects can only be read once, so calling data.read() in the save method returned empty bytes after the form validation had already read the file. The fix ensures we use the stored file data from the form validation instead of trying to re-read the file object.
This commit is contained in:
@@ -302,9 +302,9 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024 # 5MB
|
||||
FILE_UPLOAD_PERMISSIONS = 0o644
|
||||
|
||||
# Enhanced file upload security
|
||||
ENABLE_FILE_SECURITY_VALIDATION = False # Temporarily disable for testing
|
||||
ENABLE_EXIF_SANITIZATION = False
|
||||
ENABLE_MALICIOUS_CONTENT_SCAN = False
|
||||
ENABLE_FILE_SECURITY_VALIDATION = True
|
||||
ENABLE_EXIF_SANITIZATION = True
|
||||
ENABLE_MALICIOUS_CONTENT_SCAN = True
|
||||
|
||||
# Logging configuration - can be overridden in local config
|
||||
# Example: LOGS_DIR = "/var/log/ivatar" # For production deployments
|
||||
|
||||
@@ -21,7 +21,7 @@ from .models import UserPreference
|
||||
import logging
|
||||
|
||||
# Initialize logger
|
||||
logger = logging.getLogger("ivatar.security")
|
||||
logger = logging.getLogger("ivatar.ivataraccount.forms")
|
||||
|
||||
|
||||
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
|
||||
@@ -125,7 +125,13 @@ class UploadPhotoForm(forms.Form):
|
||||
|
||||
# Read file data
|
||||
try:
|
||||
# Handle different file types
|
||||
if hasattr(photo, 'read'):
|
||||
file_data = photo.read()
|
||||
elif hasattr(photo, 'file'):
|
||||
file_data = photo.file.read()
|
||||
else:
|
||||
file_data = bytes(photo)
|
||||
filename = photo.name
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading uploaded file: {e}")
|
||||
@@ -178,8 +184,7 @@ class UploadPhotoForm(forms.Form):
|
||||
|
||||
return photo
|
||||
|
||||
@staticmethod
|
||||
def save(request, data):
|
||||
def save(self, request, data):
|
||||
"""
|
||||
Save the model and assign it to the current user with enhanced security
|
||||
"""
|
||||
@@ -189,17 +194,17 @@ class UploadPhotoForm(forms.Form):
|
||||
photo.ip_address = get_client_ip(request)[0]
|
||||
|
||||
# Use sanitized data if available, otherwise use stored file data
|
||||
if hasattr(data, "sanitized_data"):
|
||||
photo.data = data.sanitized_data
|
||||
logger.debug(f"Using sanitized data, size: {len(data.sanitized_data)}")
|
||||
elif hasattr(data, "file_data"):
|
||||
photo.data = data.file_data
|
||||
logger.debug(f"Using stored file data, size: {len(data.file_data)}")
|
||||
if hasattr(self, "sanitized_data"):
|
||||
photo.data = self.sanitized_data
|
||||
elif hasattr(self, "file_data"):
|
||||
photo.data = self.file_data
|
||||
else:
|
||||
# Fallback: try to read from the file object
|
||||
try:
|
||||
photo.data = data.read()
|
||||
logger.debug(f"Using data.read(), size: {len(photo.data)}")
|
||||
|
||||
logger.debug(f"Photo data size before save: {len(photo.data)}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to read file data: {e}")
|
||||
photo.data = b""
|
||||
|
||||
photo.save()
|
||||
return photo if photo.pk else None
|
||||
|
||||
@@ -193,14 +193,11 @@ class Photo(BaseAccountModel):
|
||||
Override save from parent, taking care about the image
|
||||
"""
|
||||
# Use PIL to read the file format
|
||||
logger.debug(f"Photo.save(): data size: {len(self.data)}")
|
||||
try:
|
||||
img = Image.open(BytesIO(self.data))
|
||||
logger.debug(f"Photo.save(): PIL opened image, format: {img.format}")
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
# For debugging only
|
||||
logger.error(f"Exception caught in Photo.save(): {exc}")
|
||||
logger.debug(f"Photo.save(): First 20 bytes: {self.data[:20]}")
|
||||
return False
|
||||
self.format = file_format(img.format)
|
||||
if not self.format:
|
||||
|
||||
@@ -573,11 +573,20 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
|
||||
self.login()
|
||||
url = reverse("upload_photo")
|
||||
# rb => Read binary
|
||||
with open(TEST_IMAGE_FILE, "rb") as photo:
|
||||
with open(TEST_IMAGE_FILE, "rb") as photo_file:
|
||||
photo_data = photo_file.read()
|
||||
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
uploaded_file = SimpleUploadedFile(
|
||||
"deadbeef.png",
|
||||
photo_data,
|
||||
content_type="image/png"
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
url,
|
||||
{
|
||||
"photo": photo,
|
||||
"photo": uploaded_file,
|
||||
"not_porn": True,
|
||||
"can_distribute": True,
|
||||
},
|
||||
|
||||
@@ -73,7 +73,7 @@ LOGGING = {
|
||||
"loggers": {
|
||||
"ivatar": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "INFO",
|
||||
"level": "INFO", # Restore normal logging level
|
||||
"propagate": True,
|
||||
},
|
||||
"ivatar.security": {
|
||||
|
||||
Reference in New Issue
Block a user