Coverage for ivatar/test_file_security.py: 98%
109 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 00:08 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 00:08 +0000
1"""
2Tests for file upload security enhancements
3"""
5from unittest.mock import patch
7from django.test import TestCase, override_settings
8from django.core.files.uploadedfile import SimpleUploadedFile
9from django.contrib.auth.models import User
11from ivatar.file_security import (
12 FileValidator,
13 validate_uploaded_file,
14 get_file_security_report,
15)
16from ivatar.ivataraccount.forms import UploadPhotoForm
19class FileSecurityTestCase(TestCase):
20 """Test cases for file upload security"""
22 def setUp(self):
23 """Set up test data"""
24 self.user = User.objects.create_user(
25 username="testuser", email="test@example.com", password="testpass123"
26 )
28 # Create test image data
29 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"
31 self.malicious_data = b'GIF89a<script>alert("xss")</script>'
32 self.large_data = b"x" * (10 * 1024 * 1024) # 10MB
34 def tearDown(self):
35 """Clean up after tests"""
36 pass
38 def test_valid_jpeg_validation(self):
39 """Test validation of valid JPEG file"""
40 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
42 # Mock PIL validation to avoid issues with test data
43 with patch.object(validator, "validate_pil_image") as mock_pil:
44 mock_pil.return_value = {
45 "valid": True,
46 "image_info": {
47 "format": "JPEG",
48 "mode": "RGB",
49 "size": (100, 100),
50 "width": 100,
51 "height": 100,
52 "has_transparency": False,
53 },
54 "errors": [],
55 "warnings": [],
56 }
58 results = validator.comprehensive_validation()
60 self.assertTrue(results["valid"])
61 self.assertEqual(results["file_info"]["detected_type"], "image/jpeg")
62 self.assertGreaterEqual(results["security_score"], 80)
64 def test_magic_bytes_validation(self):
65 """Test magic bytes validation"""
66 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
67 results = validator.validate_magic_bytes()
69 self.assertTrue(results["valid"])
70 self.assertEqual(results["detected_type"], "image/jpeg")
72 def test_malicious_content_detection(self):
73 """Test detection of malicious content"""
74 validator = FileValidator(self.malicious_data, "malicious.gif")
75 results = validator.scan_for_malicious_content()
77 self.assertTrue(results["suspicious"])
78 self.assertGreater(len(results["threats"]), 0)
80 def test_file_size_validation(self):
81 """Test file size validation"""
82 validator = FileValidator(self.large_data, "large.jpg")
83 results = validator.validate_basic()
85 self.assertFalse(results["valid"])
86 self.assertIn("File too large", results["errors"][0])
88 def test_invalid_extension_validation(self):
89 """Test invalid file extension validation"""
90 validator = FileValidator(self.valid_jpeg_data, "test.exe")
91 results = validator.validate_basic()
93 self.assertFalse(results["valid"])
94 self.assertIn("File extension not allowed", results["errors"][0])
96 def test_exif_sanitization(self):
97 """Test EXIF data sanitization"""
98 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
99 sanitized_data = validator.sanitize_exif_data()
101 # Should return data (may be same or sanitized)
102 self.assertIsInstance(sanitized_data, bytes)
103 self.assertGreater(len(sanitized_data), 0)
105 def test_comprehensive_validation_function(self):
106 """Test the main validation function"""
107 # Mock PIL validation to avoid issues with test data
108 with patch("ivatar.file_security.FileValidator.validate_pil_image") as mock_pil:
109 mock_pil.return_value = {
110 "valid": True,
111 "image_info": {"format": "JPEG", "size": (100, 100)},
112 "errors": [],
113 "warnings": [],
114 }
116 is_valid, results, sanitized_data = validate_uploaded_file(
117 self.valid_jpeg_data, "test.jpg"
118 )
120 self.assertTrue(is_valid)
121 self.assertIsInstance(results, dict)
122 self.assertIsInstance(sanitized_data, bytes)
124 def test_security_report_generation(self):
125 """Test security report generation"""
126 # Mock PIL validation to avoid issues with test data
127 with patch("ivatar.file_security.FileValidator.validate_pil_image") as mock_pil:
128 mock_pil.return_value = {
129 "valid": True,
130 "image_info": {"format": "JPEG", "size": (100, 100)},
131 "errors": [],
132 "warnings": [],
133 }
135 report = get_file_security_report(self.valid_jpeg_data, "test.jpg")
137 self.assertIn("valid", report)
138 self.assertIn("security_score", report)
139 self.assertIn("file_info", report)
141 @patch("ivatar.file_security.magic.from_buffer")
142 def test_mime_type_validation(self, mock_magic):
143 """Test MIME type validation with mocked magic"""
144 mock_magic.return_value = "image/jpeg"
146 validator = FileValidator(self.valid_jpeg_data, "test.jpg")
147 results = validator.validate_mime_type()
149 self.assertTrue(results["valid"])
150 self.assertEqual(results["detected_mime"], "image/jpeg")
152 def test_polyglot_attack_detection(self):
153 """Test detection of polyglot attacks"""
154 polyglot_data = b'GIF89a<script>alert("xss")</script>'
155 validator = FileValidator(polyglot_data, "polyglot.gif")
156 results = validator.scan_for_malicious_content()
158 self.assertTrue(results["suspicious"])
159 # Check for either polyglot attack or suspicious script pattern
160 threats_text = " ".join(results["threats"]).lower()
161 self.assertTrue(
162 "polyglot attack" in threats_text or "suspicious pattern" in threats_text,
163 f"Expected polyglot attack or suspicious pattern, got: {results['threats']}",
164 )
167class UploadPhotoFormSecurityTestCase(TestCase):
168 """Test cases for UploadPhotoForm security enhancements"""
170 def setUp(self):
171 """Set up test data"""
172 self.user = User.objects.create_user(
173 username="testuser", email="test@example.com", password="testpass123"
174 )
176 def test_form_validation_with_valid_file(self):
177 """Test form validation with valid file"""
178 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"
180 uploaded_file = SimpleUploadedFile(
181 "test.jpg", valid_jpeg_data, content_type="image/jpeg"
182 )
184 form_data = {"photo": uploaded_file, "not_porn": True, "can_distribute": True}
186 form = UploadPhotoForm(data=form_data, files={"photo": uploaded_file})
188 # Mock the validation to avoid PIL issues in tests
189 with patch(
190 "ivatar.ivataraccount.forms.validate_uploaded_file"
191 ) as mock_validate:
192 mock_validate.return_value = (
193 True,
194 {"security_score": 95, "errors": [], "warnings": []},
195 valid_jpeg_data,
196 )
198 self.assertTrue(form.is_valid())
200 def test_form_validation_with_malicious_file(self):
201 """Test form validation with malicious file"""
202 malicious_data = b'GIF89a<script>alert("xss")</script>'
204 uploaded_file = SimpleUploadedFile(
205 "malicious.gif", malicious_data, content_type="image/gif"
206 )
208 form_data = {"photo": uploaded_file, "not_porn": True, "can_distribute": True}
210 form = UploadPhotoForm(data=form_data, files={"photo": uploaded_file})
212 # Mock the validation to return malicious file detection
213 with patch(
214 "ivatar.ivataraccount.forms.validate_uploaded_file"
215 ) as mock_validate:
216 mock_validate.return_value = (
217 False,
218 {
219 "security_score": 20,
220 "errors": ["Malicious content detected"],
221 "warnings": [],
222 },
223 malicious_data,
224 )
226 self.assertFalse(form.is_valid())
227 # Check for any error message indicating validation failure
228 error_text = str(form.errors["photo"]).lower()
229 self.assertTrue(
230 "malicious" in error_text or "validation failed" in error_text,
231 f"Expected malicious or validation failed message, got: {form.errors['photo']}",
232 )
235class UploadPhotoViewSecurityTestCase(TestCase):
236 """Test cases for UploadPhotoView security enhancements"""
238 def setUp(self):
239 """Set up test data"""
240 self.user = User.objects.create_user(
241 username="testuser", email="test@example.com", password="testpass123"
242 )
244 def tearDown(self):
245 """Clean up after tests"""
246 pass
249@override_settings(
250 ENABLE_FILE_SECURITY_VALIDATION=True,
251 ENABLE_EXIF_SANITIZATION=True,
252 ENABLE_MALICIOUS_CONTENT_SCAN=True,
253 ENABLE_RATE_LIMITING=True,
254)
255class FileSecurityIntegrationTestCase(TestCase):
256 """Integration tests for file upload security"""
258 def setUp(self):
259 """Set up test data"""
260 self.user = User.objects.create_user(
261 username="testuser", email="test@example.com", password="testpass123"
262 )
264 def test_end_to_end_security_validation(self):
265 """Test end-to-end security validation"""
266 # This would test the complete flow from upload to storage
267 # with all security checks enabled
268 pass
270 def test_security_logging(self):
271 """Test that security events are properly logged"""
272 # This would test that security events are logged
273 # when malicious files are uploaded
274 pass