Coverage for ivatar/test_file_security.py: 98%
109 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-20 23:06 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-20 23:06 +0000
1# -*- coding: utf-8 -*-
2"""
3Tests for file upload security enhancements
4"""
6from unittest.mock import patch
8from django.test import TestCase, override_settings
9from django.core.files.uploadedfile import SimpleUploadedFile
10from django.contrib.auth.models import User
12from ivatar.file_security import (
13 FileValidator,
14 validate_uploaded_file,
15 get_file_security_report,
16)
17from ivatar.ivataraccount.forms import UploadPhotoForm
20class FileSecurityTestCase(TestCase):
21 """Test cases for file upload security"""
23 def setUp(self):
24 """Set up test data"""
25 self.user = User.objects.create_user(
26 username="testuser", email="test@example.com", password="testpass123"
27 )
29 # Create test image data
30 self.valid_jpeg_data = b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c $.' \",#\x1c\x1c(7),01444\x1f'9=82<.342\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x01\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07\"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xf9\xff\xd9"
32 self.malicious_data = b'GIF89a<script>alert("xss")</script>'
33 self.large_data = b"x" * (10 * 1024 * 1024) # 10MB
35 def tearDown(self):
36 """Clean up after tests"""
37 pass
39 def test_valid_jpeg_validation(self):
40 """Test validation of valid JPEG file"""
41 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
43 # Mock PIL validation to avoid issues with test data
44 with patch.object(validator, "validate_pil_image") as mock_pil:
45 mock_pil.return_value = {
46 "valid": True,
47 "image_info": {
48 "format": "JPEG",
49 "mode": "RGB",
50 "size": (100, 100),
51 "width": 100,
52 "height": 100,
53 "has_transparency": False,
54 },
55 "errors": [],
56 "warnings": [],
57 }
59 results = validator.comprehensive_validation()
61 self.assertTrue(results["valid"])
62 self.assertEqual(results["file_info"]["detected_type"], "image/jpeg")
63 self.assertGreaterEqual(results["security_score"], 80)
65 def test_magic_bytes_validation(self):
66 """Test magic bytes validation"""
67 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
68 results = validator.validate_magic_bytes()
70 self.assertTrue(results["valid"])
71 self.assertEqual(results["detected_type"], "image/jpeg")
73 def test_malicious_content_detection(self):
74 """Test detection of malicious content"""
75 validator = FileValidator(self.malicious_data, "malicious.gif")
76 results = validator.scan_for_malicious_content()
78 self.assertTrue(results["suspicious"])
79 self.assertGreater(len(results["threats"]), 0)
81 def test_file_size_validation(self):
82 """Test file size validation"""
83 validator = FileValidator(self.large_data, "large.jpg")
84 results = validator.validate_basic()
86 self.assertFalse(results["valid"])
87 self.assertIn("File too large", results["errors"][0])
89 def test_invalid_extension_validation(self):
90 """Test invalid file extension validation"""
91 validator = FileValidator(self.valid_jpeg_data, "test.exe")
92 results = validator.validate_basic()
94 self.assertFalse(results["valid"])
95 self.assertIn("File extension not allowed", results["errors"][0])
97 def test_exif_sanitization(self):
98 """Test EXIF data sanitization"""
99 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
100 sanitized_data = validator.sanitize_exif_data()
102 # Should return data (may be same or sanitized)
103 self.assertIsInstance(sanitized_data, bytes)
104 self.assertGreater(len(sanitized_data), 0)
106 def test_comprehensive_validation_function(self):
107 """Test the main validation function"""
108 # Mock PIL validation to avoid issues with test data
109 with patch("ivatar.file_security.FileValidator.validate_pil_image") as mock_pil:
110 mock_pil.return_value = {
111 "valid": True,
112 "image_info": {"format": "JPEG", "size": (100, 100)},
113 "errors": [],
114 "warnings": [],
115 }
117 is_valid, results, sanitized_data = validate_uploaded_file(
118 self.valid_jpeg_data, "test.jpg"
119 )
121 self.assertTrue(is_valid)
122 self.assertIsInstance(results, dict)
123 self.assertIsInstance(sanitized_data, bytes)
125 def test_security_report_generation(self):
126 """Test security report generation"""
127 # Mock PIL validation to avoid issues with test data
128 with patch("ivatar.file_security.FileValidator.validate_pil_image") as mock_pil:
129 mock_pil.return_value = {
130 "valid": True,
131 "image_info": {"format": "JPEG", "size": (100, 100)},
132 "errors": [],
133 "warnings": [],
134 }
136 report = get_file_security_report(self.valid_jpeg_data, "test.jpg")
138 self.assertIn("valid", report)
139 self.assertIn("security_score", report)
140 self.assertIn("file_info", report)
142 @patch("ivatar.file_security.magic.from_buffer")
143 def test_mime_type_validation(self, mock_magic):
144 """Test MIME type validation with mocked magic"""
145 mock_magic.return_value = "image/jpeg"
147 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
148 results = validator.validate_mime_type()
150 self.assertTrue(results["valid"])
151 self.assertEqual(results["detected_mime"], "image/jpeg")
153 def test_polyglot_attack_detection(self):
154 """Test detection of polyglot attacks"""
155 polyglot_data = b'GIF89a<script>alert("xss")</script>'
156 validator = FileValidator(polyglot_data, "polyglot.gif")
157 results = validator.scan_for_malicious_content()
159 self.assertTrue(results["suspicious"])
160 # Check for either polyglot attack or suspicious script pattern
161 threats_text = " ".join(results["threats"]).lower()
162 self.assertTrue(
163 "polyglot attack" in threats_text or "suspicious pattern" in threats_text,
164 f"Expected polyglot attack or suspicious pattern, got: {results['threats']}",
165 )
168class UploadPhotoFormSecurityTestCase(TestCase):
169 """Test cases for UploadPhotoForm security enhancements"""
171 def setUp(self):
172 """Set up test data"""
173 self.user = User.objects.create_user(
174 username="testuser", email="test@example.com", password="testpass123"
175 )
177 def test_form_validation_with_valid_file(self):
178 """Test form validation with valid file"""
179 valid_jpeg_data = b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c $.' \",#\x1c\x1c(7),01444\x1f'9=82<.342\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x01\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07\"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xf9\xff\xd9"
181 uploaded_file = SimpleUploadedFile(
182 "test.jpg", valid_jpeg_data, content_type="image/jpeg"
183 )
185 form_data = {"photo": uploaded_file, "not_porn": True, "can_distribute": True}
187 form = UploadPhotoForm(data=form_data, files={"photo": uploaded_file})
189 # Mock the validation to avoid PIL issues in tests
190 with patch(
191 "ivatar.ivataraccount.forms.validate_uploaded_file"
192 ) as mock_validate:
193 mock_validate.return_value = (
194 True,
195 {"security_score": 95, "errors": [], "warnings": []},
196 valid_jpeg_data,
197 )
199 self.assertTrue(form.is_valid())
201 def test_form_validation_with_malicious_file(self):
202 """Test form validation with malicious file"""
203 malicious_data = b'GIF89a<script>alert("xss")</script>'
205 uploaded_file = SimpleUploadedFile(
206 "malicious.gif", malicious_data, content_type="image/gif"
207 )
209 form_data = {"photo": uploaded_file, "not_porn": True, "can_distribute": True}
211 form = UploadPhotoForm(data=form_data, files={"photo": uploaded_file})
213 # Mock the validation to return malicious file detection
214 with patch(
215 "ivatar.ivataraccount.forms.validate_uploaded_file"
216 ) as mock_validate:
217 mock_validate.return_value = (
218 False,
219 {
220 "security_score": 20,
221 "errors": ["Malicious content detected"],
222 "warnings": [],
223 },
224 malicious_data,
225 )
227 self.assertFalse(form.is_valid())
228 # Check for any error message indicating validation failure
229 error_text = str(form.errors["photo"]).lower()
230 self.assertTrue(
231 "malicious" in error_text or "validation failed" in error_text,
232 f"Expected malicious or validation failed message, got: {form.errors['photo']}",
233 )
236class UploadPhotoViewSecurityTestCase(TestCase):
237 """Test cases for UploadPhotoView security enhancements"""
239 def setUp(self):
240 """Set up test data"""
241 self.user = User.objects.create_user(
242 username="testuser", email="test@example.com", password="testpass123"
243 )
245 def tearDown(self):
246 """Clean up after tests"""
247 pass
250@override_settings(
251 ENABLE_FILE_SECURITY_VALIDATION=True,
252 ENABLE_EXIF_SANITIZATION=True,
253 ENABLE_MALICIOUS_CONTENT_SCAN=True,
254 ENABLE_RATE_LIMITING=True,
255)
256class FileSecurityIntegrationTestCase(TestCase):
257 """Integration tests for file upload security"""
259 def setUp(self):
260 """Set up test data"""
261 self.user = User.objects.create_user(
262 username="testuser", email="test@example.com", password="testpass123"
263 )
265 def test_end_to_end_security_validation(self):
266 """Test end-to-end security validation"""
267 # This would test the complete flow from upload to storage
268 # with all security checks enabled
269 pass
271 def test_security_logging(self):
272 """Test that security events are properly logged"""
273 # This would test that security events are logged
274 # when malicious files are uploaded
275 pass