Skip to content

Commit 4c1ea0d

Browse files
authored
Feat: Add ability to search by date (#908)
1 parent 533d628 commit 4c1ea0d

File tree

4 files changed

+63
-7
lines changed

4 files changed

+63
-7
lines changed

core/database_arango.py

+8
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,14 @@ def filter(
670670
conditions.append(f"COUNT(FOR c IN o.context[*] FILTER REGEX_TEST(c.@arg{i}_key, @arg{i}_value) RETURN c) > 0")
671671
aql_args[f'arg{i}_key'] = context_field
672672
sorts.append(f"o.context[*].@arg{i}_key")
673+
elif key == 'created':
674+
operator = value[0]
675+
if operator not in ['<', '>']:
676+
operator = '='
677+
else:
678+
aql_args[f'arg{i}_value'] = value[1:]
679+
conditions.append(f"DATE_TIMESTAMP(o.created) {operator}= DATE_TIMESTAMP(@arg{i}_value)")
680+
sorts.append("o.created")
673681
else:
674682
conditions.append(f"REGEX_TEST(o.@arg{i}_key, @arg{i}_value, true)")
675683
aql_args[f'arg{i}_key'] = key

core/schemas/entity.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import datetime
22
import re
3-
import unicodedata
43
from enum import Enum
5-
from typing import ClassVar, Literal, Optional, Type
4+
from typing import ClassVar, Literal, Type
5+
6+
from pydantic import BaseModel, Field
67

78
from core import database_arango
89
from core.helpers import now
910
from core.schemas.graph import TagRelationship
10-
from core.schemas.tag import Tag
11-
from pydantic import BaseModel, Field
1211

1312

1413
class EntityType(str, Enum):

tests/apiv2/entities.py

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import unittest
23

34
from fastapi.testclient import TestClient
@@ -12,7 +13,10 @@
1213
class EntityTest(unittest.TestCase):
1314
def setUp(self) -> None:
1415
database_arango.db.clear()
15-
self.entity1 = entity.ThreatActor(name="ta1", aliases=["badactor"]).save()
16+
self.entity1 = entity.ThreatActor(
17+
name="ta1",
18+
aliases=["badactor"],
19+
created=datetime.datetime(2020, 1, 1)).save()
1620
self.entity1.tag(["ta1"])
1721
self.entity2 = entity.ThreatActor(name="bears").save()
1822

@@ -66,6 +70,35 @@ def test_search_entities_subfields(self):
6670
self.assertEqual(data["entities"][0]["name"], "ta1")
6771
self.assertEqual(data["entities"][0]["type"], "threat-actor")
6872

73+
def test_search_entities_with_creation_date(self):
74+
response = client.post(
75+
"/api/v2/entities/search",
76+
json={
77+
"query": {"created": "<2020-01-02"},
78+
"type": "threat-actor",
79+
},
80+
)
81+
data = response.json()
82+
self.assertEqual(response.status_code, 200, data)
83+
self.assertEqual(len(data["entities"]), 1)
84+
self.assertEqual(data["entities"][0]["name"], "ta1")
85+
self.assertEqual(data["entities"][0]["type"], "threat-actor")
86+
87+
response = client.post(
88+
"/api/v2/entities/search",
89+
json={
90+
"query": {"created": ">2020-01-03"},
91+
"type": "threat-actor",
92+
},
93+
)
94+
data = response.json()
95+
self.assertEqual(response.status_code, 200, data)
96+
# self.entity2 is created with a default timestamp of now(), so should
97+
# be the only threat-actor entity captured with this filter.
98+
self.assertEqual(len(data["entities"]), 1, data)
99+
self.assertEqual(data["entities"][0]["name"], "bears")
100+
self.assertEqual(data["entities"][0]["type"], "threat-actor")
101+
69102
def test_new_entity_with_tag(self):
70103
response = client.post(
71104
"/api/v2/entities/",

tests/schemas/entity.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
from core.schemas.entity import (AttackPattern, Entity, Malware, ThreatActor,
66
Tool, Vulnerability)
77
from core.schemas.observables import hostname
8-
8+
import datetime
99

1010
class EntityTest(unittest.TestCase):
1111

1212
def setUp(self) -> None:
1313
database_arango.db.clear()
1414
self.ta1 = ThreatActor(name="APT123", aliases=['CrazyFrog']).save()
1515
self.vuln1 = Vulnerability(name="CVE-2018-1337", title='elite exploit').save()
16-
self.malware1 = Malware(name="zeus").save()
16+
self.malware1 = Malware(name="zeus", created=datetime.datetime(2020, 1, 1)).save()
1717
self.tool1 = Tool(name="mimikatz").save()
1818

1919
def tearDown(self) -> None:
@@ -70,6 +70,22 @@ def test_filter_entities_regex(self):
7070
self.assertEqual(total, 1)
7171
self.assertEqual(entities[0], self.vuln1)
7272

73+
def test_filter_entities_time(self):
74+
entities, total = Entity.filter({"created": "2020-01-01"})
75+
self.assertEqual(len(entities), 1)
76+
self.assertEqual(total, 1)
77+
self.assertEqual(entities[0], self.malware1)
78+
79+
entities, total = Entity.filter({"created": "<2020-01-02"})
80+
self.assertEqual(len(entities), 1)
81+
self.assertEqual(total, 1)
82+
self.assertEqual(entities[0], self.malware1)
83+
84+
entities, total = Entity.filter({"created": ">2020-01-02"})
85+
self.assertEqual(len(entities), 3)
86+
self.assertEqual(total, 3)
87+
self.assertNotIn(self.malware1, entities)
88+
7389
def test_entity_with_tags(self):
7490
entity = ThreatActor(name="APT0").save()
7591
entity.tag(['tag1', 'tag2'])

0 commit comments

Comments
 (0)