def post(self, request): # pylint: disable=no-self-use """ receive artifact information, and locate all threats that match it """ response = None def check_required_value( arg_name, dictionary=request.data, description_format='Required parameter "{arg_name}" was not provided' ): """ Check that a required value has been provided by the client Arguments: arg_name - name of the value that should be present description_format - format specification of descriptive message """ if arg_name not in dictionary: raise ValueError("{} - {}".format( description_format.format(arg_name=arg_name), json.dumps(dictionary), )) try: context_data = request.data if request.content_type.startswith('multipart/form-data'): check_required_value('artifact') context_data = json.loads(request.data['artifact']) check_required_value('type', context_data) if context_data['type'] == "file.content": check_required_value('file', request.FILES) context_data['value'] = request.FILES['file'] check_required_value('value', context_data) else: check_required_value('type') check_required_value('value') # Generate a search context context = SearchContext(context_data) matching_threats = search_artifacts(context_data['type'], context_data['value'], True, context=context) response = process_threat_results(matching_threats, context) context.save() except ValueError as ve: response = Response({'error': '{}'.format(str(ve))}, status.HTTP_400_BAD_REQUEST) # Keep broad exception handler, as this is an entry point to the service interface except Exception as ex: # pylint: disable=broad-except response = Response({'error': '{}'.format(str(ex))}, status.HTTP_500_INTERNAL_SERVER_ERROR) return response
def test_regular_context(self): context = SearchContext({ "type": "net.ip", "value": "192.168.1.1", }) context.save() loaded_context = SearchContext.load(context.id) self.assertEqual(context.type, loaded_context.type) self.assertEqual(context.value, loaded_context.value) self.assertEqual(context.id, loaded_context.id)
def test_file_context(self): response = None with open("threats/test_data/boss.gif", "rb") as boss_reader: file_data = boss_reader.read() boss_reader.seek(0) response = self.client.post('/', { 'artifact': '{"type": "file.content"}', 'file': boss_reader }, format="multipart") upload_file_args = { "name": "boss.gif", "content_type": "application/octet-stream", "size": 29927, "charset": None, } self.assertEqual(response.status_code, 200) temp_file = TemporaryUploadedFile(**upload_file_args) temp_file.write(file_data) temp_file.flush() context = SearchContext({"type": "file.content", "value": temp_file}) context.save() self.assertEqual(context.file_data_len, len(file_data)) loaded_context = SearchContext.load(context.id) self.assertEqual(loaded_context.base64_file_data_len, context.base64_file_data_len) self.assertEqual(loaded_context.file_data_len, context.file_data_len) with open(loaded_context.value.temporary_file_path(), "rb") as temp_file: loaded_file_data = temp_file.read() for counter in range(0, len(loaded_file_data) // 100): begin = counter * 100 end = begin + 100 self.assertEqual(file_data[begin:end], loaded_file_data[begin:end]) self.assertEqual(len(file_data), len(loaded_file_data))
def test_async_search(self): """ test search operation flagged within search context """ artifact_type = "net.ip" artifact_value = "192.168.1.1" context = SearchContext({ "type": artifact_type, "value": artifact_value }) hits = search_artifacts(artifact_type, artifact_value, True, context=context) self.assertTrue(len(hits) == 0) context.save() # try again, still should find nothing hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 0) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestArtifactSearch.add_result("Test result posted", artifact_type, artifact_value) # now should find something, other search engine should've added something hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 1) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestArtifactSearch.search_complete_flag().wait(2) context = SearchContext.load(context.id) self.assertFalse(context.pending_searches)
def test_2_async_searchers(self): """ test adding threat info using dict objects instead of instances """ artifact_type = "net.ip" artifact_value = "192.168.1.1" context = SearchContext({ "type": artifact_type, "value": artifact_value }) hits = search_artifacts(artifact_type, artifact_value, True, context=context) self.assertTrue(len(hits) == 0) context.save() # try again, still should find nothing hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 0) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestDictArtifactSearch.add_result("Test result posted", artifact_type, artifact_value) hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 1) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestArtifactSearch.add_result("Another test result", artifact_type, artifact_value) hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 2) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestDictArtifactSearch.search_complete_flag().wait(2) hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 2) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestArtifactSearch.search_complete_flag().wait(2) hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 2) context = SearchContext.load(context.id) self.assertFalse(context.pending_searches)
def test_results_already_stored(self): """ Test duplicate entries from same searcher """ artifact_type = "net.ip" artifact_value = "192.168.1.1" context = SearchContext({ "type": artifact_type, "value": artifact_value }) hits = search_artifacts(artifact_type, artifact_value, True, context=context) self.assertTrue(len(hits) == 0) context.save() # try again, still should find nothing hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 0) context = SearchContext.load(context.id) self.assertTrue(context.pending_searches) TestArtifactSearch.add_result("Test result posted", artifact_type, artifact_value) # now should find something, other search engine should've added something hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 1) # add the same result again from the same searcher - should still only get 1 result due to duplicate detection TestArtifactSearch.add_result("Test result posted", artifact_type, artifact_value) TestArtifactSearch.search_complete_flag().wait(2) # now should find something, other search engine should've added something hits = search_artifacts(artifact_type, artifact_value) self.assertTrue(len(hits) == 1)
def get(self, request, request_id): """ Fetch results corresponding to given request id """ request_id = request_id.rstrip('/') context = SearchContext.load(request_id) if context is None: # We return "no content" because we have none # 404 will cause the custom threat service caller to stop processing return Response({"error": "'{}' was not found".format(request_id)}, status.HTTP_204_NO_CONTENT) try: matching_threats = search_artifacts(context.type, context.value) response = process_threat_results(matching_threats, context) # Keep broad exception handler, as this is an entry point to the service interface except Exception as ex: # pylint: disable=broad-except response = Response({'error': '{}'.format(str(ex))}, status.HTTP_500_INTERNAL_SERVER_ERROR) return response