5
5
AdvancedSearch , ExpressionSearch , RHA1FunctionalSimilarity , RHA1Analytics , URIStatistics , URIIndex , FileDownload , \
6
6
URLThreatIntelligence , AnalyzeURL , DomainThreatIntelligence , IPThreatIntelligence , FileUpload , DeleteFile , \
7
7
ReanalyzeFile , DataChangeSubscription , DynamicAnalysis , CertificateIndex , RansomwareIndicators , NewMalwareFilesFeed , \
8
- NewMalwareURIFeed , ImpHashSimilarity , YARAHunting , YARARetroHunting , TAXIIRansomwareFeed , CustomerUsage , NetworkReputation , \
8
+ NewMalwareURIFeed , ImpHashSimilarity , YARAHunting , YARARetroHunting , TAXIIRansomwareFeed , CustomerUsage , \
9
+ NetworkReputation , FileReputationUserOverride , NetworkReputationUserOverride , \
9
10
CLASSIFICATIONS , AVAILABLE_PLATFORMS , RHA1_TYPE_MAP , \
10
11
resolve_hash_type , calculate_hash , NotFoundError
11
12
from ReversingLabs .SDK .helper import WrongInputError , BadGatewayError , DEFAULT_USER_AGENT
@@ -173,6 +174,121 @@ def test_error_status_code(self, requests_mock):
173
174
self .file_reputation .get_file_reputation (SHA1 )
174
175
175
176
177
+ class TestFileReputationUserOverride :
178
+ @classmethod
179
+ def setup_class (cls ):
180
+ cls .override = FileReputationUserOverride (HOST , USERNAME , PASSWORD )
181
+
182
+ def test_list_active (self , requests_mock ):
183
+ hash_type = "sha1"
184
+ self .override .list_active_overrides (
185
+ hash_type = hash_type
186
+ )
187
+
188
+ expected_url = f"{ HOST } /api/databrowser/malware_presence/user_override/list_hashes/{ hash_type } ?format=json"
189
+
190
+ requests_mock .get .assert_called_with (
191
+ url = expected_url ,
192
+ auth = (USERNAME , PASSWORD ),
193
+ verify = True ,
194
+ proxies = None ,
195
+ headers = {"User-Agent" : DEFAULT_USER_AGENT },
196
+ params = None
197
+ )
198
+
199
+ def test_override (self , requests_mock ):
200
+ override_samples = [
201
+ {
202
+ "sha1" : SHA1 ,
203
+ "sha256" : SHA256 ,
204
+ "md5" : MD5 ,
205
+ "classification" : "SUSPICIOUS"
206
+ }
207
+ ]
208
+
209
+ remove_override = [
210
+ {
211
+ "sha1" : SHA1 ,
212
+ "sha256" : SHA256 ,
213
+ "md5" : MD5
214
+ }
215
+ ]
216
+
217
+ self .override .override_classification (
218
+ override_samples = override_samples ,
219
+ remove_override = remove_override
220
+ )
221
+
222
+ expected_url = f"{ HOST } /api/databrowser/malware_presence/user_override/json"
223
+ post_json = {"rl" : {"query" : {"override_samples" : override_samples , "remove_override" : remove_override }}}
224
+
225
+ requests_mock .post .assert_called_with (
226
+ url = expected_url ,
227
+ auth = (USERNAME , PASSWORD ),
228
+ json = post_json ,
229
+ data = None ,
230
+ verify = True ,
231
+ proxies = None ,
232
+ headers = {"User-Agent" : DEFAULT_USER_AGENT },
233
+ params = None
234
+ )
235
+
236
+
237
+ class TestNetworkReputationUserOverride :
238
+ @classmethod
239
+ def setup_class (cls ):
240
+ cls .override = NetworkReputationUserOverride (HOST , USERNAME , PASSWORD )
241
+
242
+ def test_list_overrides (self , requests_mock ):
243
+ self .override .list_overrides ()
244
+
245
+ expected_url = f"{ HOST } /api/networking/user_override/v1/query/list_overrides"
246
+
247
+ requests_mock .get .assert_called_with (
248
+ url = expected_url ,
249
+ auth = (USERNAME , PASSWORD ),
250
+ verify = True ,
251
+ proxies = None ,
252
+ headers = {"User-Agent" : DEFAULT_USER_AGENT },
253
+ params = {"format" : "json" , "next_network_location" : None }
254
+ )
255
+
256
+ def test_override (self , requests_mock ):
257
+ override_list = [{
258
+ 'network_location' : 'example_network_location' ,
259
+ 'type' : 'url' ,
260
+ 'classification' : 'SUSPICIOUS' ,
261
+ 'categories' : ['list' , 'of' , 'arbitrary' , 'categories' ]
262
+ }]
263
+
264
+ remove_list = [{
265
+ 'network_location' : 'example_network_location' ,
266
+ 'type' : 'url'
267
+ }]
268
+
269
+ self .override .reputation_override (
270
+ override_list = override_list ,
271
+ remove_overrides_list = remove_list
272
+ )
273
+
274
+ expected_url = f"{ HOST } /api/networking/user_override/v1/query/json"
275
+
276
+ post_json = {"rl" : {"query" : {"user_override" :
277
+ {"override_network_locations" : override_list ,
278
+ "remove_overrides" : remove_list }, "response_format" : "json" }}}
279
+
280
+ requests_mock .post .assert_called_with (
281
+ url = expected_url ,
282
+ auth = (USERNAME , PASSWORD ),
283
+ json = post_json ,
284
+ data = None ,
285
+ verify = True ,
286
+ proxies = None ,
287
+ headers = {"User-Agent" : DEFAULT_USER_AGENT },
288
+ params = None
289
+ )
290
+
291
+
176
292
class TestAVScanners :
177
293
@classmethod
178
294
def setup_class (cls ):
@@ -370,8 +486,9 @@ def test_single_query(self, requests_mock):
370
486
self .uri_index .get_uri_index (self .test_url , classification = "MALICIOUS" ,
371
487
page_sha1 = "21841b32c6165b27dddbd4d6eb3a672defe54271" )
372
488
373
- expected_url = (f"{ HOST } /api/uri_index/v1/query/0164af1f2e83a7411a3c8cfd02b1424156a21b6b/21841b32c6165b27dddbd4d6eb3a672defe54271?"
374
- f"format=json&classification=MALICIOUS" )
489
+ expected_url = (
490
+ f"{ HOST } /api/uri_index/v1/query/0164af1f2e83a7411a3c8cfd02b1424156a21b6b/21841b32c6165b27dddbd4d6eb3a672defe54271?"
491
+ f"format=json&classification=MALICIOUS" )
375
492
376
493
requests_mock .get .assert_called_with (
377
494
url = expected_url ,
@@ -389,7 +506,8 @@ def setup_class(cls):
389
506
cls .adv_search = AdvancedSearch (HOST , USERNAME , PASSWORD )
390
507
391
508
def test_wrong_input (self , requests_mock ):
392
- with pytest .raises (WrongInputError , match = r"records_per_page parameter must be integer with value between 1 and 10000" ):
509
+ with pytest .raises (WrongInputError ,
510
+ match = r"records_per_page parameter must be integer with value between 1 and 10000" ):
393
511
self .adv_search .search ("search_query" , records_per_page = 12000 )
394
512
395
513
with pytest .raises (WrongInputError , match = r"Sorting criteria must be one of the following options" ):
@@ -400,11 +518,13 @@ def test_wrong_input(self, requests_mock):
400
518
def test_single_query (self , requests_mock ):
401
519
requests_mock .post .return_value .status_code = 200
402
520
403
- self .adv_search .search (query_string = "av-count:5 available:TRUE" , sorting_criteria = "sha1" , sorting_order = "desc" , page_number = 2 , records_per_page = 5 )
521
+ self .adv_search .search (query_string = "av-count:5 available:TRUE" , sorting_criteria = "sha1" , sorting_order = "desc" ,
522
+ page_number = 2 , records_per_page = 5 )
404
523
405
524
expected_url = f"{ HOST } /api/search/v1/query"
406
525
407
- post_json = {"query" : "av-count:5 available:TRUE" , "page" : 2 , "records_per_page" : 5 , "format" : "json" , "sort" : "sha1 desc" }
526
+ post_json = {"query" : "av-count:5 available:TRUE" , "page" : 2 , "records_per_page" : 5 , "format" : "json" ,
527
+ "sort" : "sha1 desc" }
408
528
409
529
requests_mock .post .assert_called_with (
410
530
url = expected_url ,
@@ -506,7 +626,9 @@ def test_query(self, requests_mock):
506
626
507
627
expected_url = f"{ HOST } /api/networking/url/v1/report/query/json"
508
628
509
- post_json = {"rl" : {"query" : {"url" : "https://www.softpedia.com/get/Office-tools/Text-editors/Sublime-Text.shtml" , "response_format" : "json" }}}
629
+ post_json = {"rl" : {
630
+ "query" : {"url" : "https://www.softpedia.com/get/Office-tools/Text-editors/Sublime-Text.shtml" ,
631
+ "response_format" : "json" }}}
510
632
511
633
requests_mock .post .assert_called_with (
512
634
url = expected_url ,
@@ -532,7 +654,9 @@ def test_query(self, requests_mock):
532
654
533
655
expected_url = f"{ HOST } /api/networking/url/v1/analyze/query/json"
534
656
535
- post_json = {"rl" : {"query" : {"url" : "https://www.softpedia.com/get/Office-tools/Text-editors/Sublime-Text.shtml" , "response_format" : "json" }}}
657
+ post_json = {"rl" : {
658
+ "query" : {"url" : "https://www.softpedia.com/get/Office-tools/Text-editors/Sublime-Text.shtml" ,
659
+ "response_format" : "json" }}}
536
660
537
661
requests_mock .post .assert_called_with (
538
662
url = expected_url ,
@@ -635,6 +759,10 @@ def test_upload_meta(self, requests_mock):
635
759
data = meta_xml
636
760
)
637
761
762
+ def test_upload_file (self , requests_mock ):
763
+ with pytest .raises (WrongInputError , match = r"file_handle parameter must be a file open in 'rb' mode" ):
764
+ self .upload .upload_sample_from_file (file_handle = "aaa" )
765
+
638
766
639
767
class TestDeleteFile :
640
768
@classmethod
@@ -681,7 +809,7 @@ class TestDataChangeSubscription:
681
809
def setup_class (cls ):
682
810
cls .data_change = DataChangeSubscription (HOST , USERNAME , PASSWORD )
683
811
684
- def test_query (self , requests_mock ):
812
+ def test_subscribe (self , requests_mock ):
685
813
self .data_change .subscribe ([SHA1 , SHA1 ])
686
814
687
815
expected_url = f"{ HOST } /api/subscription/data_change/v1/bulk_query/subscribe/json"
@@ -699,6 +827,21 @@ def test_query(self, requests_mock):
699
827
data = None
700
828
)
701
829
830
+ def test_unsubscribe (self ):
831
+ with pytest .raises (WrongInputError , match = r"All hashes in the list must be of the same type." ):
832
+ self .data_change .unsubscribe (hashes = [SHA1 , SHA256 ])
833
+
834
+ def test_pull_from_feed (self ):
835
+ with pytest .raises (WrongInputError , match = r"events parameter must be a list of strings" ):
836
+ self .data_change .pull_from_feed (events = "event1,event2" )
837
+
838
+ def test_continuous_data_change_feed (self ):
839
+ with pytest .raises (WrongInputError , match = r"If the timestamp time_format is used, time_value parameter must be a Unix" ):
840
+ self .data_change .continuous_data_change_feed (time_format = "timestamp" , time_value = "2024-05-15T22:12:32" )
841
+
842
+ with pytest .raises (WrongInputError , match = r"If the utc time_format is used, time_value parameter must be written in the" ):
843
+ self .data_change .continuous_data_change_feed (time_format = "utc" , time_value = "12345678" )
844
+
702
845
703
846
class TestDynamicAnalysis :
704
847
@classmethod
@@ -715,7 +858,8 @@ def test_detonate_file(self, requests_mock):
715
858
716
859
expected_url = f"{ HOST } /api/dynamic/analysis/analyze/v1/query/json"
717
860
718
- post_json = {"rl" : {"platform" : "windows10" , "response_format" : "json" , "sha1" : SHA1 , "optional_parameters" : "sample_name=sample_name, internet_simulation=true" }}
861
+ post_json = {"rl" : {"platform" : "windows10" , "response_format" : "json" , "sha1" : SHA1 ,
862
+ "optional_parameters" : "sample_name=sample_name, internet_simulation=true" }}
719
863
720
864
requests_mock .post .assert_called_with (
721
865
url = expected_url ,
@@ -728,6 +872,24 @@ def test_detonate_file(self, requests_mock):
728
872
data = None
729
873
)
730
874
875
+ def test_file_analysis_results (self , requests_mock ):
876
+ self .da .get_dynamic_analysis_results (sample_hash = SHA1 , latest = True )
877
+
878
+ expected_url = f"{ HOST } /api/dynamic/analysis/report/v1/query/sha1/{ SHA1 } /latest?format=json"
879
+
880
+ requests_mock .get .assert_called_with (
881
+ url = expected_url ,
882
+ auth = (USERNAME , PASSWORD ),
883
+ verify = True ,
884
+ proxies = None ,
885
+ headers = {"User-Agent" : DEFAULT_USER_AGENT },
886
+ params = None
887
+ )
888
+
889
+ def test_url_analysis_results (self ):
890
+ with pytest .raises (WrongInputError , match = r"analysis_id parameter bust be string." ):
891
+ self .da .get_dynamic_analysis_results (url_sha1 = SHA1 , analysis_id = 123 )
892
+
731
893
732
894
class TestCertificateIndex :
733
895
@classmethod
@@ -762,7 +924,7 @@ def test_query(self, requests_mock):
762
924
)
763
925
764
926
expected_url = f"{ HOST } /api/public/v1/ransomware/indicators?withHealth=0&tagFormat=dict&" \
765
- "hours=3&indicatorTypes=ipv4,hash,domain,uri&onlyFreemium=0"
927
+ "hours=3&indicatorTypes=ipv4,hash,domain,uri&onlyFreemium=0"
766
928
767
929
requests_mock .get .assert_called_with (
768
930
url = expected_url ,
@@ -906,7 +1068,7 @@ class TestTAXIIRansomwareFeed:
906
1068
def setup_class (cls ):
907
1069
cls .taxii = TAXIIRansomwareFeed (HOST , USERNAME , PASSWORD )
908
1070
909
- def test_get_objects (self , requests_mock ):
1071
+ def test_get_objects_lite (self , requests_mock ):
910
1072
self .taxii .get_objects (
911
1073
api_root = "lite-root" ,
912
1074
collection_id = "123456"
@@ -930,6 +1092,30 @@ def test_get_objects(self, requests_mock):
930
1092
params = query_params
931
1093
)
932
1094
1095
+ def test_get_objects_regular (self , requests_mock ):
1096
+ self .taxii .get_objects (
1097
+ api_root = "regular-root" ,
1098
+ collection_id = "654321"
1099
+ )
1100
+
1101
+ query_params = {
1102
+ "limit" : 500 ,
1103
+ "added_after" : None ,
1104
+ "match[id]" : None ,
1105
+ "next" : None
1106
+ }
1107
+
1108
+ expected_url = f"{ HOST } /api/taxii/regular-root/collections/654321/objects/"
1109
+
1110
+ requests_mock .get .assert_called_with (
1111
+ url = expected_url ,
1112
+ auth = (USERNAME , PASSWORD ),
1113
+ verify = True ,
1114
+ proxies = None ,
1115
+ headers = {"User-Agent" : DEFAULT_USER_AGENT , 'Accept' : 'application/taxii+json;version=2.1' },
1116
+ params = query_params
1117
+ )
1118
+
933
1119
934
1120
class TestCustomerUsage :
935
1121
@classmethod
@@ -965,7 +1151,9 @@ def test_query(self, requests_mock):
965
1151
966
1152
expected_url = f"{ HOST } /api/networking/reputation/v1/query/json"
967
1153
968
- post_json = {"rl" : {"query" : {"network_locations" : [{"network_location" : "some.domain" }, {"network_location" : "another.domain" }], "response_format" : "json" }}}
1154
+ post_json = {"rl" : {"query" : {
1155
+ "network_locations" : [{"network_location" : "some.domain" }, {"network_location" : "another.domain" }],
1156
+ "response_format" : "json" }}}
969
1157
970
1158
requests_mock .post .assert_called_with (
971
1159
url = expected_url ,
@@ -977,3 +1165,63 @@ def test_query(self, requests_mock):
977
1165
json = post_json ,
978
1166
data = None
979
1167
)
1168
+
1169
+
1170
+ class TestMalwareFamilyDetection :
1171
+ pass
1172
+
1173
+
1174
+ class TestVerticalFeedsStatistics :
1175
+ pass
1176
+
1177
+
1178
+ class TestVerticalFeedsSearch :
1179
+ pass
1180
+
1181
+
1182
+ class TestCertificateAnalytics :
1183
+ pass
1184
+
1185
+
1186
+ class TestCertificateThumbprintSearch :
1187
+ pass
1188
+
1189
+
1190
+ class TestNewMalwarePlatformFiltered :
1191
+ pass
1192
+
1193
+
1194
+ class TestNewFilesFirstScan :
1195
+ pass
1196
+
1197
+
1198
+ class TestNewFilesFirstAndRescan :
1199
+ pass
1200
+
1201
+
1202
+ class TestFilesWithDetectionChanges :
1203
+ pass
1204
+
1205
+
1206
+ class TestMWPChangeEventsFeed :
1207
+ pass
1208
+
1209
+
1210
+ class TestCvesExploitedInTheWild :
1211
+ pass
1212
+
1213
+
1214
+ class TestNewExploitOrCveSamplesFoundInWildHourly :
1215
+ pass
1216
+
1217
+
1218
+ class TestNewExploitAndCveSamplesFoundInWildDaily :
1219
+ pass
1220
+
1221
+
1222
+ class TestNewWhitelistedFiles :
1223
+ pass
1224
+
1225
+
1226
+ class TestChangesWhitelistedFiles :
1227
+ pass
0 commit comments