# Secure: Safe FileResponse with validation
from django.http import FileResponse, JsonResponse, Http404
from django.views import View
from django.conf import settings
from django.core.exceptions import ValidationError
from pathlib import Path
import os
import re
# Safe: Validated file download
class SafeFileDownloadView(View):
def get(self, request, file_id):
try:
# Validate file ID
validated_file_id = self.validate_file_id(file_id)
# Get file info from database or allowlist
file_info = self.get_file_info(validated_file_id)
# Construct safe file path
safe_path = self.get_safe_file_path(file_info)
# Serve file securely
return self.serve_file_safely(safe_path, file_info)
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
except FileNotFoundError:
raise Http404('File not found')
def validate_file_id(self, file_id):
# Validate file ID format
if not file_id or not file_id.isdigit():
raise ValidationError('Invalid file ID')
file_id = int(file_id)
if file_id <= 0:
raise ValidationError('Invalid file ID')
return file_id
def get_file_info(self, file_id):
# This would typically query a database
# Using a mock allowlist for demonstration
allowed_files = {
1: {'filename': 'document1.pdf', 'path': 'documents'},
2: {'filename': 'report.xlsx', 'path': 'reports'},
3: {'filename': 'image.jpg', 'path': 'images'}
}
if file_id not in allowed_files:
raise ValidationError('File not found')
return allowed_files[file_id]
def get_safe_file_path(self, file_info):
# Define safe base directory
downloads_dir = Path(settings.MEDIA_ROOT) / 'downloads'
# Construct path using validated components
file_dir = downloads_dir / file_info['path']
file_path = file_dir / file_info['filename']
# Validate path is within downloads directory
try:
resolved_path = file_path.resolve()
downloads_dir_resolved = downloads_dir.resolve()
resolved_path.relative_to(downloads_dir_resolved)
return resolved_path
except ValueError:
raise ValidationError('File path outside allowed directory')
def serve_file_safely(self, file_path, file_info):
try:
if not file_path.exists():
raise FileNotFoundError('File not found')
# Check file size
max_size = 100 * 1024 * 1024 # 100MB
if file_path.stat().st_size > max_size:
raise ValidationError('File too large')
# Serve file
response = FileResponse(
open(file_path, 'rb'),
as_attachment=True,
filename=file_info['filename']
)
return response
except PermissionError:
raise ValidationError('Access denied')
# Safe: Document serving with validation
def safe_serve_user_document(request):
document_id = request.POST.get('document_id', '')
try:
# Validate document access
validated_doc = validate_document_access(request, document_id)
# Get safe file path
file_path = get_safe_document_path(validated_doc)
# Serve document
return serve_document_safely(file_path, validated_doc)
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
def validate_document_access(request, document_id):
# Validate document ID
if not document_id.isdigit():
raise ValidationError('Invalid document ID')
document_id = int(document_id)
# Check user access (mock implementation)
if not request.user.is_authenticated:
raise ValidationError('Authentication required')
# This would typically check database permissions
user_documents = [1, 2, 3] # Mock user's accessible documents
if document_id not in user_documents:
raise ValidationError('Access denied')
return {
'id': document_id,
'filename': f'document_{document_id}.pdf',
'owner': request.user.id
}
def get_safe_document_path(document_info):
# Define safe base directory
documents_dir = Path(settings.MEDIA_ROOT) / 'user_documents'
# Construct safe path
file_path = documents_dir / str(document_info['owner']) / document_info['filename']
# Validate path
try:
resolved_path = file_path.resolve()
documents_dir_resolved = documents_dir.resolve()
resolved_path.relative_to(documents_dir_resolved)
return resolved_path
except ValueError:
raise ValidationError('Document path outside allowed directory')
def serve_document_safely(file_path, document_info):
try:
if not file_path.exists():
raise ValidationError('Document not found')
response = FileResponse(
open(file_path, 'rb'),
as_attachment=True,
filename=document_info['filename']
)
# Set security headers
response['X-Content-Type-Options'] = 'nosniff'
response['Content-Security-Policy'] = "default-src 'none'"
return response
except PermissionError:
raise ValidationError('Access denied')
# Safe: Report download with allowlists
def safe_download_report(request):
report_type = request.GET.get('type', '')
date = request.GET.get('date', '')
try:
# Validate inputs
validated_data = validate_report_request(report_type, date)
# Get report file
report_path = get_safe_report_path(validated_data)
# Serve report
return serve_report_safely(report_path, validated_data)
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
def validate_report_request(report_type, date):
# Validate report type
allowed_reports = ['sales', 'inventory', 'users', 'analytics']
if report_type not in allowed_reports:
raise ValidationError('Report type not allowed')
# Validate date format
if not re.match(r'^\d{4}-\d{2}-\d{2}$', date):
raise ValidationError('Invalid date format')
return {
'type': report_type,
'date': date,
'filename': f'{report_type}_report_{date}.pdf'
}
def get_safe_report_path(report_data):
# Define safe reports directory
reports_dir = Path(settings.MEDIA_ROOT) / 'reports' / report_data['type']
# Construct file path
file_path = reports_dir / report_data['filename']
# Validate path
try:
resolved_path = file_path.resolve()
reports_base = Path(settings.MEDIA_ROOT).resolve() / 'reports'
resolved_path.relative_to(reports_base)
return resolved_path
except ValueError:
raise ValidationError('Report path outside allowed directory')
def serve_report_safely(file_path, report_data):
try:
if not file_path.exists():
raise ValidationError('Report not found')
response = FileResponse(
open(file_path, 'rb'),
as_attachment=True,
filename=report_data['filename']
)
response['Content-Type'] = 'application/pdf'
return response
except Exception:
raise ValidationError('Report serving failed')
# Safe: Backup access with strict controls
def safe_download_backup(request):
backup_id = request.GET.get('backup_id', '')
try:
# Only allow staff access
if not request.user.is_staff:
raise ValidationError('Access denied')
# Validate backup access
validated_backup = validate_backup_access(backup_id)
# Get backup file
backup_path = get_safe_backup_path(validated_backup)
# Serve backup
return serve_backup_safely(backup_path, validated_backup)
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
def validate_backup_access(backup_id):
# Validate backup ID format
if not backup_id.isdigit():
raise ValidationError('Invalid backup ID')
# This would query a database for valid backups
valid_backups = {
1: 'backup_20231201.tar.gz',
2: 'backup_20231202.tar.gz',
3: 'backup_20231203.tar.gz'
}
backup_id = int(backup_id)
if backup_id not in valid_backups:
raise ValidationError('Backup not found')
return {
'id': backup_id,
'filename': valid_backups[backup_id]
}
def get_safe_backup_path(backup_info):
# Define safe backup directory
backup_dir = Path(settings.BASE_DIR) / 'backups'
# Construct file path
file_path = backup_dir / backup_info['filename']
# Validate path
try:
resolved_path = file_path.resolve()
backup_dir_resolved = backup_dir.resolve()
resolved_path.relative_to(backup_dir_resolved)
return resolved_path
except ValueError:
raise ValidationError('Backup path outside allowed directory')
def serve_backup_safely(file_path, backup_info):
try:
if not file_path.exists():
raise ValidationError('Backup file not found')
response = FileResponse(
open(file_path, 'rb'),
as_attachment=True,
filename=backup_info['filename']
)
response['Content-Type'] = 'application/gzip'
return response
except Exception:
raise ValidationError('Backup serving failed')