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

1""" 

2Tests for file upload security enhancements 

3""" 

4 

5from unittest.mock import patch 

6 

7from django.test import TestCase, override_settings 

8from django.core.files.uploadedfile import SimpleUploadedFile 

9from django.contrib.auth.models import User 

10 

11from ivatar.file_security import ( 

12 FileValidator, 

13 validate_uploaded_file, 

14 get_file_security_report, 

15) 

16from ivatar.ivataraccount.forms import UploadPhotoForm 

17 

18 

19class FileSecurityTestCase(TestCase): 

20 """Test cases for file upload security""" 

21 

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 ) 

27 

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" 

30 

31 self.malicious_data = b'GIF89a<script>alert("xss")</script>' 

32 self.large_data = b"x" * (10 * 1024 * 1024) # 10MB 

33 

34 def tearDown(self): 

35 """Clean up after tests""" 

36 pass 

37 

38 def test_valid_jpeg_validation(self): 

39 """Test validation of valid JPEG file""" 

40 validator = FileValidator(self.valid_jpeg_data, "test.jpg") 

41 

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 } 

57 

58 results = validator.comprehensive_validation() 

59 

60 self.assertTrue(results["valid"]) 

61 self.assertEqual(results["file_info"]["detected_type"], "image/jpeg") 

62 self.assertGreaterEqual(results["security_score"], 80) 

63 

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() 

68 

69 self.assertTrue(results["valid"]) 

70 self.assertEqual(results["detected_type"], "image/jpeg") 

71 

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() 

76 

77 self.assertTrue(results["suspicious"]) 

78 self.assertGreater(len(results["threats"]), 0) 

79 

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() 

84 

85 self.assertFalse(results["valid"]) 

86 self.assertIn("File too large", results["errors"][0]) 

87 

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() 

92 

93 self.assertFalse(results["valid"]) 

94 self.assertIn("File extension not allowed", results["errors"][0]) 

95 

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() 

100 

101 # Should return data (may be same or sanitized) 

102 self.assertIsInstance(sanitized_data, bytes) 

103 self.assertGreater(len(sanitized_data), 0) 

104 

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 } 

115 

116 is_valid, results, sanitized_data = validate_uploaded_file( 

117 self.valid_jpeg_data, "test.jpg" 

118 ) 

119 

120 self.assertTrue(is_valid) 

121 self.assertIsInstance(results, dict) 

122 self.assertIsInstance(sanitized_data, bytes) 

123 

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 } 

134 

135 report = get_file_security_report(self.valid_jpeg_data, "test.jpg") 

136 

137 self.assertIn("valid", report) 

138 self.assertIn("security_score", report) 

139 self.assertIn("file_info", report) 

140 

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" 

145 

146 validator = FileValidator(self.valid_jpeg_data, "test.jpg") 

147 results = validator.validate_mime_type() 

148 

149 self.assertTrue(results["valid"]) 

150 self.assertEqual(results["detected_mime"], "image/jpeg") 

151 

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() 

157 

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 ) 

165 

166 

167class UploadPhotoFormSecurityTestCase(TestCase): 

168 """Test cases for UploadPhotoForm security enhancements""" 

169 

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 ) 

175 

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" 

179 

180 uploaded_file = SimpleUploadedFile( 

181 "test.jpg", valid_jpeg_data, content_type="image/jpeg" 

182 ) 

183 

184 form_data = {"photo": uploaded_file, "not_porn": True, "can_distribute": True} 

185 

186 form = UploadPhotoForm(data=form_data, files={"photo": uploaded_file}) 

187 

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 ) 

197 

198 self.assertTrue(form.is_valid()) 

199 

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>' 

203 

204 uploaded_file = SimpleUploadedFile( 

205 "malicious.gif", malicious_data, content_type="image/gif" 

206 ) 

207 

208 form_data = {"photo": uploaded_file, "not_porn": True, "can_distribute": True} 

209 

210 form = UploadPhotoForm(data=form_data, files={"photo": uploaded_file}) 

211 

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 ) 

225 

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 ) 

233 

234 

235class UploadPhotoViewSecurityTestCase(TestCase): 

236 """Test cases for UploadPhotoView security enhancements""" 

237 

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 ) 

243 

244 def tearDown(self): 

245 """Clean up after tests""" 

246 pass 

247 

248 

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""" 

257 

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 ) 

263 

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 

269 

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