dogesec-commons 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dogesec_commons/__init__.py +0 -0
- dogesec_commons/asgi.py +16 -0
- dogesec_commons/objects/__init__.py +1 -0
- dogesec_commons/objects/apps.py +10 -0
- dogesec_commons/objects/conf.py +8 -0
- dogesec_commons/objects/db_view_creator.py +164 -0
- dogesec_commons/objects/helpers.py +660 -0
- dogesec_commons/objects/views.py +427 -0
- dogesec_commons/settings.py +161 -0
- dogesec_commons/stixifier/__init__.py +0 -0
- dogesec_commons/stixifier/apps.py +5 -0
- dogesec_commons/stixifier/conf.py +1 -0
- dogesec_commons/stixifier/migrations/0001_initial.py +36 -0
- dogesec_commons/stixifier/migrations/0002_profile_ai_content_check_variable.py +18 -0
- dogesec_commons/stixifier/migrations/0003_rename_ai_content_check_variable_profile_ai_content_check_provider_and_more.py +23 -0
- dogesec_commons/stixifier/migrations/0004_profile_identity_id.py +18 -0
- dogesec_commons/stixifier/migrations/0005_profile_generate_pdf.py +18 -0
- dogesec_commons/stixifier/migrations/__init__.py +0 -0
- dogesec_commons/stixifier/models.py +57 -0
- dogesec_commons/stixifier/serializers.py +192 -0
- dogesec_commons/stixifier/stixifier.py +252 -0
- dogesec_commons/stixifier/summarizer.py +62 -0
- dogesec_commons/stixifier/views.py +193 -0
- dogesec_commons/urls.py +45 -0
- dogesec_commons/utils/__init__.py +3 -0
- dogesec_commons/utils/autoschema.py +88 -0
- dogesec_commons/utils/exceptions.py +28 -0
- dogesec_commons/utils/filters.py +66 -0
- dogesec_commons/utils/ordering.py +47 -0
- dogesec_commons/utils/pagination.py +81 -0
- dogesec_commons/utils/schemas.py +27 -0
- dogesec_commons/utils/serializers.py +47 -0
- dogesec_commons/wsgi.py +16 -0
- dogesec_commons-1.0.2.dist-info/METADATA +57 -0
- dogesec_commons-1.0.2.dist-info/RECORD +37 -0
- dogesec_commons-1.0.2.dist-info/WHEEL +4 -0
- dogesec_commons-1.0.2.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,660 @@
|
|
1
|
+
import contextlib
|
2
|
+
import logging
|
3
|
+
import typing, re
|
4
|
+
from arango import ArangoClient
|
5
|
+
from django.conf import settings
|
6
|
+
from rest_framework.response import Response
|
7
|
+
from drf_spectacular.utils import OpenApiParameter
|
8
|
+
from ..utils.pagination import Pagination
|
9
|
+
from rest_framework.exceptions import ValidationError, NotFound
|
10
|
+
from stix2arango.services import ArangoDBService
|
11
|
+
from . import conf
|
12
|
+
|
13
|
+
from django.http import HttpResponse
|
14
|
+
from django.conf import settings
|
15
|
+
from rest_framework import decorators, response, status
|
16
|
+
|
17
|
+
|
18
|
+
SDO_TYPES = set(
|
19
|
+
[
|
20
|
+
"attack-pattern",
|
21
|
+
"campaign",
|
22
|
+
"course-of-action",
|
23
|
+
"grouping",
|
24
|
+
"identity",
|
25
|
+
"incident",
|
26
|
+
"indicator",
|
27
|
+
"infrastructure",
|
28
|
+
"intrusion-set",
|
29
|
+
"location",
|
30
|
+
"malware",
|
31
|
+
"malware-analysis",
|
32
|
+
"note",
|
33
|
+
"observed-data",
|
34
|
+
"opinion",
|
35
|
+
"report",
|
36
|
+
"threat-actor",
|
37
|
+
"sighting",
|
38
|
+
"tool",
|
39
|
+
"vulnerability",
|
40
|
+
"weakness",
|
41
|
+
]
|
42
|
+
)
|
43
|
+
|
44
|
+
SCO_TYPES = set(
|
45
|
+
[
|
46
|
+
"artifact",
|
47
|
+
"autonomous-system",
|
48
|
+
"bank-account",
|
49
|
+
"bank-card",
|
50
|
+
"cryptocurrency-transaction",
|
51
|
+
"cryptocurrency-wallet",
|
52
|
+
"directory",
|
53
|
+
"domain-name",
|
54
|
+
"email-addr",
|
55
|
+
"email-message",
|
56
|
+
"file",
|
57
|
+
"ipv4-addr",
|
58
|
+
"ipv6-addr",
|
59
|
+
"mac-addr",
|
60
|
+
"mutex",
|
61
|
+
"network-traffic",
|
62
|
+
"phone-number",
|
63
|
+
"process",
|
64
|
+
"software",
|
65
|
+
"url",
|
66
|
+
"user-account",
|
67
|
+
"user-agent",
|
68
|
+
"windows-registry-key",
|
69
|
+
"x509-certificate",
|
70
|
+
]
|
71
|
+
)
|
72
|
+
SDO_SORT_FIELDS = [
|
73
|
+
"name_ascending",
|
74
|
+
"name_descending",
|
75
|
+
"created_ascending",
|
76
|
+
"created_descending",
|
77
|
+
"modified_ascending",
|
78
|
+
"modified_descending",
|
79
|
+
"type_ascending",
|
80
|
+
"type_descending",
|
81
|
+
]
|
82
|
+
SRO_SORT_FIELDS = [
|
83
|
+
"created_ascending",
|
84
|
+
"created_descending",
|
85
|
+
"modified_ascending",
|
86
|
+
"modified_descending",
|
87
|
+
]
|
88
|
+
|
89
|
+
|
90
|
+
SCO_SORT_FIELDS = ["type_ascending", "type_descending"]
|
91
|
+
|
92
|
+
|
93
|
+
SMO_SORT_FIELDS = [
|
94
|
+
"created_ascending",
|
95
|
+
"created_descending",
|
96
|
+
"type_ascending",
|
97
|
+
"type_descending",
|
98
|
+
]
|
99
|
+
|
100
|
+
|
101
|
+
SMO_TYPES = set(
|
102
|
+
[
|
103
|
+
"marking-definition",
|
104
|
+
"extension-definition",
|
105
|
+
"language-content",
|
106
|
+
]
|
107
|
+
)
|
108
|
+
|
109
|
+
OBJECT_TYPES = SDO_TYPES.union(SCO_TYPES).union(["relationship"]).union(SMO_TYPES)
|
110
|
+
|
111
|
+
|
112
|
+
def positive_int(integer_string, cutoff=None, default=1):
|
113
|
+
"""
|
114
|
+
Cast a string to a strictly positive integer.
|
115
|
+
"""
|
116
|
+
with contextlib.suppress(ValueError, TypeError):
|
117
|
+
ret = int(integer_string)
|
118
|
+
if ret <= 0:
|
119
|
+
return default
|
120
|
+
if cutoff:
|
121
|
+
return min(ret, cutoff)
|
122
|
+
return ret
|
123
|
+
return default
|
124
|
+
|
125
|
+
|
126
|
+
class ArangoDBHelper:
|
127
|
+
max_page_size = conf.MAXIMUM_PAGE_SIZE
|
128
|
+
page_size = conf.DEFAULT_PAGE_SIZE
|
129
|
+
SRO_OBJECTS_ONLY_LATEST = getattr(settings, "SRO_OBJECTS_ONLY_LATEST", True)
|
130
|
+
STIX_OBJECT_SCHEMA = {
|
131
|
+
"type": "object",
|
132
|
+
"properties": {
|
133
|
+
"type": {
|
134
|
+
"example": "domain-name",
|
135
|
+
},
|
136
|
+
"id": {
|
137
|
+
"example": "domain-name--a86627d4-285b-5358-b332-4e33f3ec1075",
|
138
|
+
},
|
139
|
+
},
|
140
|
+
"additionalProperties": True,
|
141
|
+
}
|
142
|
+
|
143
|
+
@staticmethod
|
144
|
+
def get_like_literal(str: str):
|
145
|
+
return str.replace("_", "\\_").replace("%", "\\%")
|
146
|
+
|
147
|
+
@classmethod
|
148
|
+
def like_string(cls, string: str):
|
149
|
+
return "%" + cls.get_like_literal(string) + "%"
|
150
|
+
|
151
|
+
def get_sort_stmt(self, sort_options: list[str], customs={}, doc_name="doc"):
|
152
|
+
finder = re.compile(r"(.+)_((a|de)sc)ending")
|
153
|
+
sort_field = self.query.get("sort", sort_options[0])
|
154
|
+
if sort_field not in sort_options:
|
155
|
+
return ""
|
156
|
+
if m := finder.match(sort_field):
|
157
|
+
field = m.group(1)
|
158
|
+
direction = m.group(2).upper()
|
159
|
+
if cfield := customs.get(field):
|
160
|
+
return f"SORT {cfield} {direction}"
|
161
|
+
return f"SORT {doc_name}.{field} {direction}"
|
162
|
+
|
163
|
+
def query_as_array(self, key):
|
164
|
+
query = self.query.get(key)
|
165
|
+
if not query:
|
166
|
+
return []
|
167
|
+
return query.split(",")
|
168
|
+
|
169
|
+
def query_as_bool(self, key, default=True):
|
170
|
+
query_str = self.query.get(key)
|
171
|
+
if not query_str:
|
172
|
+
return default
|
173
|
+
return query_str.lower() in ["true", "yes", "1", "y"]
|
174
|
+
|
175
|
+
@classmethod
|
176
|
+
def get_page_params(cls, kwargs):
|
177
|
+
page_number = positive_int(kwargs.get("page"))
|
178
|
+
page_limit = positive_int(
|
179
|
+
kwargs.get("page_size"),
|
180
|
+
cutoff=ArangoDBHelper.max_page_size,
|
181
|
+
default=ArangoDBHelper.page_size,
|
182
|
+
)
|
183
|
+
return page_number, page_limit
|
184
|
+
|
185
|
+
@classmethod
|
186
|
+
def get_paginated_response(
|
187
|
+
cls, data, page_number, page_size=page_size, full_count=0, result_key="objects"
|
188
|
+
):
|
189
|
+
return Response(
|
190
|
+
{
|
191
|
+
"page_size": page_size or cls.page_size,
|
192
|
+
"page_number": page_number,
|
193
|
+
"page_results_count": len(data),
|
194
|
+
"total_results_count": full_count,
|
195
|
+
result_key: list(data),
|
196
|
+
}
|
197
|
+
)
|
198
|
+
|
199
|
+
@classmethod
|
200
|
+
def get_paginated_response_schema(cls, result_key="objects", schema=None):
|
201
|
+
|
202
|
+
return {
|
203
|
+
200: {
|
204
|
+
"type": "object",
|
205
|
+
"required": ["page_results_count", result_key],
|
206
|
+
"properties": {
|
207
|
+
"page_size": {
|
208
|
+
"type": "integer",
|
209
|
+
"example": cls.max_page_size,
|
210
|
+
},
|
211
|
+
"page_number": {
|
212
|
+
"type": "integer",
|
213
|
+
"example": 3,
|
214
|
+
},
|
215
|
+
"page_results_count": {
|
216
|
+
"type": "integer",
|
217
|
+
"example": cls.page_size,
|
218
|
+
},
|
219
|
+
"total_results_count": {
|
220
|
+
"type": "integer",
|
221
|
+
"example": cls.page_size * cls.max_page_size,
|
222
|
+
},
|
223
|
+
result_key: {
|
224
|
+
"type": "array",
|
225
|
+
"items": schema or cls.STIX_OBJECT_SCHEMA,
|
226
|
+
},
|
227
|
+
},
|
228
|
+
},
|
229
|
+
400: {
|
230
|
+
"type": "object",
|
231
|
+
"properties": {
|
232
|
+
"detail": {"type": "string"},
|
233
|
+
"code": {"type": "integer"},
|
234
|
+
},
|
235
|
+
"required": [
|
236
|
+
"code",
|
237
|
+
],
|
238
|
+
},
|
239
|
+
}
|
240
|
+
|
241
|
+
@classmethod
|
242
|
+
def get_schema_operation_parameters(self):
|
243
|
+
parameters = [
|
244
|
+
OpenApiParameter(
|
245
|
+
"page",
|
246
|
+
type=int,
|
247
|
+
description=Pagination.page_query_description,
|
248
|
+
),
|
249
|
+
OpenApiParameter(
|
250
|
+
"page_size",
|
251
|
+
type=int,
|
252
|
+
description=Pagination.page_size_query_description,
|
253
|
+
),
|
254
|
+
]
|
255
|
+
return parameters
|
256
|
+
|
257
|
+
client = ArangoClient(hosts=settings.ARANGODB_HOST_URL)
|
258
|
+
DB_NAME = conf.DB_NAME
|
259
|
+
|
260
|
+
def __init__(self, collection, request, result_key="objects") -> None:
|
261
|
+
self.collection = collection
|
262
|
+
self.db = self.client.db(
|
263
|
+
self.DB_NAME,
|
264
|
+
username=settings.ARANGODB_USERNAME,
|
265
|
+
password=settings.ARANGODB_PASSWORD,
|
266
|
+
)
|
267
|
+
self.result_key = result_key
|
268
|
+
self.request = request
|
269
|
+
self.query = request.query_params.dict() if request else dict()
|
270
|
+
self.page, self.count = self.get_page_params(self.query)
|
271
|
+
|
272
|
+
def execute_query(self, query, bind_vars={}, paginate=True):
|
273
|
+
if paginate:
|
274
|
+
bind_vars["offset"], bind_vars["count"] = self.get_offset_and_count(
|
275
|
+
self.count, self.page
|
276
|
+
)
|
277
|
+
try:
|
278
|
+
cursor = self.db.aql.execute(
|
279
|
+
query, bind_vars=bind_vars, count=True, full_count=True
|
280
|
+
)
|
281
|
+
except Exception as e:
|
282
|
+
logging.exception(e)
|
283
|
+
raise ValidationError("aql: cannot process request")
|
284
|
+
if paginate:
|
285
|
+
return self.get_paginated_response(
|
286
|
+
cursor,
|
287
|
+
self.page,
|
288
|
+
self.count,
|
289
|
+
cursor.statistics()["fullCount"],
|
290
|
+
result_key=self.result_key,
|
291
|
+
)
|
292
|
+
return list(cursor)
|
293
|
+
|
294
|
+
def get_offset_and_count(self, count, page) -> tuple[int, int]:
|
295
|
+
page = page or 1
|
296
|
+
if page >= 2**32:
|
297
|
+
raise ValidationError(f"invalid page `{page}`")
|
298
|
+
offset = (page - 1) * count
|
299
|
+
return offset, count
|
300
|
+
|
301
|
+
def get_scos(self, matcher={}):
|
302
|
+
types = SCO_TYPES
|
303
|
+
other_filters = []
|
304
|
+
|
305
|
+
if new_types := self.query_as_array("types"):
|
306
|
+
types = types.intersection(new_types)
|
307
|
+
bind_vars = {
|
308
|
+
"@collection": self.collection,
|
309
|
+
"types": list(types),
|
310
|
+
}
|
311
|
+
if value := self.query.get("value"):
|
312
|
+
bind_vars["search_value"] = value.lower()
|
313
|
+
other_filters.append(
|
314
|
+
"""
|
315
|
+
(
|
316
|
+
doc.type == 'artifact' AND CONTAINS(LOWER(doc.payload_bin), @search_value) OR
|
317
|
+
doc.type == 'autonomous-system' AND CONTAINS(LOWER(doc.number), @search_value) OR
|
318
|
+
doc.type == 'bank-account' AND CONTAINS(LOWER(doc.iban_number), @search_value) OR
|
319
|
+
doc.type == 'bank-card' AND CONTAINS(LOWER(doc.number), @search_value) OR
|
320
|
+
doc.type == 'cryptocurrency-transaction' AND CONTAINS(LOWER(doc.hash), @search_value) OR
|
321
|
+
doc.type == 'cryptocurrency-wallet' AND CONTAINS(LOWER(doc.hash), @search_value) OR
|
322
|
+
doc.type == 'directory' AND CONTAINS(LOWER(doc.path), @search_value) OR
|
323
|
+
doc.type == 'domain-name' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
324
|
+
doc.type == 'email-addr' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
325
|
+
doc.type == 'email-message' AND CONTAINS(LOWER(doc.body), @search_value) OR
|
326
|
+
doc.type == 'file' AND CONTAINS(LOWER(doc.name), @search_value) OR
|
327
|
+
doc.type == 'ipv4-addr' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
328
|
+
doc.type == 'ipv6-addr' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
329
|
+
doc.type == 'mac-addr' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
330
|
+
doc.type == 'mutex' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
331
|
+
doc.type == 'network-traffic' AND CONTAINS(LOWER(doc.protocols), @search_value) OR
|
332
|
+
doc.type == 'phone-number' AND CONTAINS(LOWER(doc.number), @search_value) OR
|
333
|
+
doc.type == 'process' AND CONTAINS(LOWER(doc.pid), @search_value) OR
|
334
|
+
doc.type == 'software' AND CONTAINS(LOWER(doc.name), @search_value) OR
|
335
|
+
doc.type == 'url' AND CONTAINS(LOWER(doc.value), @search_value) OR
|
336
|
+
doc.type == 'user-account' AND CONTAINS(LOWER(doc.display_name), @search_value) OR
|
337
|
+
doc.type == 'user-agent' AND CONTAINS(LOWER(doc.string), @search_value) OR
|
338
|
+
doc.type == 'windows-registry-key' AND CONTAINS(LOWER(doc.key), @search_value) OR
|
339
|
+
doc.type == 'x509-certificate' AND CONTAINS(LOWER(doc.subject), @search_value)
|
340
|
+
//generic
|
341
|
+
OR
|
342
|
+
CONTAINS(LOWER(doc.value), @search_value) OR
|
343
|
+
CONTAINS(LOWER(doc.name), @search_value) OR
|
344
|
+
CONTAINS(LOWER(doc.number), @search_value)
|
345
|
+
)
|
346
|
+
""".strip()
|
347
|
+
)
|
348
|
+
|
349
|
+
if matcher:
|
350
|
+
bind_vars["matcher"] = matcher
|
351
|
+
other_filters.insert(0, "MATCHES(doc, @matcher)")
|
352
|
+
|
353
|
+
if other_filters:
|
354
|
+
other_filters = "FILTER " + " AND ".join(other_filters)
|
355
|
+
|
356
|
+
query = f"""
|
357
|
+
FOR doc in @@collection SEARCH doc.type IN @types AND doc._is_latest == TRUE
|
358
|
+
{other_filters or ""}
|
359
|
+
{self.get_sort_stmt(SCO_SORT_FIELDS)}
|
360
|
+
|
361
|
+
COLLECT id = doc.id INTO docs
|
362
|
+
LET doc = FIRST(FOR d in docs[*].doc SORT d.modified OR d.created DESC, d._record_modified DESC RETURN d)
|
363
|
+
|
364
|
+
LIMIT @offset, @count
|
365
|
+
RETURN KEEP(doc, KEYS(doc, true))
|
366
|
+
"""
|
367
|
+
return self.execute_query(query, bind_vars=bind_vars)
|
368
|
+
|
369
|
+
def get_smos(self):
|
370
|
+
types = SMO_TYPES
|
371
|
+
if new_types := self.query_as_array("types"):
|
372
|
+
types = types.intersection(new_types)
|
373
|
+
bind_vars = {
|
374
|
+
"@collection": self.collection,
|
375
|
+
"types": list(types),
|
376
|
+
}
|
377
|
+
other_filters = {}
|
378
|
+
query = f"""
|
379
|
+
FOR doc in @@collection
|
380
|
+
SEARCH doc.type IN @types AND doc._is_latest == TRUE
|
381
|
+
{other_filters or ""}
|
382
|
+
{self.get_sort_stmt(SMO_SORT_FIELDS)}
|
383
|
+
|
384
|
+
|
385
|
+
COLLECT id = doc.id INTO docs
|
386
|
+
LET doc = FIRST(FOR d in docs[*].doc SORT d.modified OR d.created DESC, d._record_modified DESC RETURN d)
|
387
|
+
|
388
|
+
LIMIT @offset, @count
|
389
|
+
RETURN KEEP(doc, KEYS(doc, true))
|
390
|
+
"""
|
391
|
+
return self.execute_query(query, bind_vars=bind_vars)
|
392
|
+
|
393
|
+
def get_sdos(self):
|
394
|
+
types = SDO_TYPES
|
395
|
+
if new_types := self.query_as_array("types"):
|
396
|
+
types = types.intersection(new_types)
|
397
|
+
|
398
|
+
bind_vars = {
|
399
|
+
"@collection": self.collection,
|
400
|
+
"types": list(types),
|
401
|
+
}
|
402
|
+
other_filters = []
|
403
|
+
search_filters = ["doc._is_latest == TRUE"]
|
404
|
+
if term := self.query.get("labels"):
|
405
|
+
bind_vars["labels"] = term
|
406
|
+
other_filters.append("doc.labels[? ANY FILTER CONTAINS(CURRENT, @labels)]")
|
407
|
+
|
408
|
+
if term := self.query.get("name"):
|
409
|
+
bind_vars["name"] = "%" + self.get_like_literal(term) + "%"
|
410
|
+
search_filters.append("doc.name LIKE @name")
|
411
|
+
|
412
|
+
if q := self.query.get("visible_to"):
|
413
|
+
bind_vars["visible_to"] = q
|
414
|
+
bind_vars["marking_visible_to_all"] = (
|
415
|
+
"marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb",
|
416
|
+
"marking-definition--94868c89-83c2-464b-929b-a1a8aa3c8487",
|
417
|
+
)
|
418
|
+
search_filters.append(
|
419
|
+
"(doc.created_by_ref IN [@visible_to, NULL] OR @marking_visible_to_all ANY IN doc.object_marking_refs)"
|
420
|
+
)
|
421
|
+
|
422
|
+
if other_filters:
|
423
|
+
other_filters = "FILTER " + " AND ".join(other_filters)
|
424
|
+
|
425
|
+
query = f"""
|
426
|
+
FOR doc in @@collection
|
427
|
+
SEARCH doc.type IN @types AND {' AND '.join(search_filters)}
|
428
|
+
{other_filters or ""}
|
429
|
+
{self.get_sort_stmt(SDO_SORT_FIELDS)}
|
430
|
+
|
431
|
+
|
432
|
+
COLLECT id = doc.id INTO docs
|
433
|
+
LET doc = FIRST(FOR d in docs[*].doc SORT d.modified OR d.created DESC, d._record_modified DESC RETURN d)
|
434
|
+
|
435
|
+
LIMIT @offset, @count
|
436
|
+
RETURN KEEP(doc, KEYS(doc, true))
|
437
|
+
"""
|
438
|
+
# return HttpResponse(f"{query}\n\n// {__import__('json').dumps(bind_vars)}")
|
439
|
+
return self.execute_query(query, bind_vars=bind_vars)
|
440
|
+
|
441
|
+
def get_objects_by_id(self, id):
|
442
|
+
bind_vars = {
|
443
|
+
"@view": self.collection,
|
444
|
+
"id": id,
|
445
|
+
}
|
446
|
+
query = """
|
447
|
+
FOR doc in @@view
|
448
|
+
SEARCH doc.id == @id AND doc._is_latest == TRUE
|
449
|
+
LIMIT 1
|
450
|
+
RETURN KEEP(doc, KEYS(doc, true))
|
451
|
+
"""
|
452
|
+
objs = self.execute_query(query, bind_vars=bind_vars, paginate=False)
|
453
|
+
if not objs:
|
454
|
+
raise NotFound("No object with id")
|
455
|
+
return Response(objs[0])
|
456
|
+
|
457
|
+
def get_object_bundle(self, stix_id):
|
458
|
+
bind_vars = {
|
459
|
+
"@view": self.collection,
|
460
|
+
"id": stix_id,
|
461
|
+
}
|
462
|
+
rel_search_filters = []
|
463
|
+
search_filters = []
|
464
|
+
if not self.query_as_bool("include_embedded_refs", True):
|
465
|
+
rel_search_filters.append("doc._is_ref != TRUE")
|
466
|
+
|
467
|
+
if types := self.query_as_array("types"):
|
468
|
+
rel_search_filters.append(
|
469
|
+
"(doc._target_type IN @types OR doc._source_type IN @types)"
|
470
|
+
)
|
471
|
+
search_filters.append("FILTER doc.type IN @types")
|
472
|
+
bind_vars["types"] = types
|
473
|
+
|
474
|
+
visible_to_filter = ""
|
475
|
+
if q := self.query.get("visible_to"):
|
476
|
+
bind_vars["visible_to"] = q
|
477
|
+
bind_vars["marking_visible_to_all"] = (
|
478
|
+
"marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb",
|
479
|
+
"marking-definition--94868c89-83c2-464b-929b-a1a8aa3c8487",
|
480
|
+
)
|
481
|
+
visible_to_filter = "FILTER doc.created_by_ref == @visible_to OR @marking_visible_to_all ANY IN doc.object_marking_refs OR doc.created_by_ref == NULL"
|
482
|
+
|
483
|
+
query = """
|
484
|
+
LET bundle_ids = FLATTEN(FOR doc in @@view SEARCH (doc.source_ref == @id or doc.target_ref == @id) AND doc._is_latest == TRUE /* rel_search_extras */ RETURN [doc._id, doc._from, doc._to])
|
485
|
+
FOR doc IN @@view
|
486
|
+
SEARCH (doc._id IN bundle_ids OR (doc.id == @id AND doc._is_latest == TRUE))
|
487
|
+
// extra_search
|
488
|
+
// visible_to_filter
|
489
|
+
LIMIT @offset, @count
|
490
|
+
RETURN KEEP(doc, KEYS(doc, TRUE))
|
491
|
+
"""
|
492
|
+
if rel_search_filters:
|
493
|
+
query = query.replace(
|
494
|
+
"/* rel_search_extras */", " AND " + " AND ".join(rel_search_filters)
|
495
|
+
)
|
496
|
+
if search_filters:
|
497
|
+
query = query.replace("// extra_search", "\n".join(search_filters))
|
498
|
+
|
499
|
+
if visible_to_filter:
|
500
|
+
query = query.replace("// visible_to_filter", visible_to_filter)
|
501
|
+
return self.execute_query(query, bind_vars=bind_vars)
|
502
|
+
|
503
|
+
def get_containing_reports(self, id):
|
504
|
+
bind_vars = {
|
505
|
+
"@view": self.collection,
|
506
|
+
"id": id,
|
507
|
+
}
|
508
|
+
query = """
|
509
|
+
LET report_ids = (
|
510
|
+
FOR doc in @@view
|
511
|
+
FILTER doc.id == @id
|
512
|
+
RETURN DISTINCT doc._stixify_report_id
|
513
|
+
)
|
514
|
+
FOR report in @@view
|
515
|
+
SEARCH report.type == 'report' AND report.id IN report_ids
|
516
|
+
LIMIT @offset, @count
|
517
|
+
RETURN KEEP(report, KEYS(report, TRUE))
|
518
|
+
"""
|
519
|
+
return self.execute_query(query, bind_vars=bind_vars)
|
520
|
+
|
521
|
+
def get_sros(self):
|
522
|
+
bind_vars = {
|
523
|
+
"@collection": self.collection,
|
524
|
+
}
|
525
|
+
|
526
|
+
search_filters = ["doc._is_latest == TRUE"]
|
527
|
+
|
528
|
+
if terms := self.query_as_array("source_ref_type"):
|
529
|
+
bind_vars["source_ref_type"] = terms
|
530
|
+
search_filters.append("doc._source_type IN @source_ref_type")
|
531
|
+
|
532
|
+
if terms := self.query_as_array("target_ref_type"):
|
533
|
+
bind_vars["target_ref_type"] = terms
|
534
|
+
search_filters.append("doc._target_type IN @target_ref_type")
|
535
|
+
|
536
|
+
if term := self.query.get("relationship_type"):
|
537
|
+
bind_vars["relationship_type"] = "%" + self.get_like_literal(term) + "%"
|
538
|
+
search_filters.append("doc.relationship_type LIKE @relationship_type")
|
539
|
+
|
540
|
+
if not self.query_as_bool("include_embedded_refs", True):
|
541
|
+
search_filters.append("doc._is_ref != TRUE")
|
542
|
+
|
543
|
+
if term := self.query.get("target_ref"):
|
544
|
+
bind_vars["target_ref"] = term
|
545
|
+
search_filters.append("doc.target_ref == @target_ref")
|
546
|
+
|
547
|
+
if term := self.query.get("source_ref"):
|
548
|
+
bind_vars["source_ref"] = term
|
549
|
+
search_filters.append("doc.source_ref == @source_ref")
|
550
|
+
|
551
|
+
if not self.SRO_OBJECTS_ONLY_LATEST:
|
552
|
+
search_filters[0] = (
|
553
|
+
"(doc._is_latest == TRUE OR doc._target_type IN @sco_types OR doc._source_type IN @sco_types)"
|
554
|
+
)
|
555
|
+
bind_vars["sco_types"] = list(SCO_TYPES)
|
556
|
+
|
557
|
+
if q := self.query.get("visible_to"):
|
558
|
+
bind_vars["visible_to"] = q
|
559
|
+
bind_vars["marking_visible_to_all"] = (
|
560
|
+
"marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb",
|
561
|
+
"marking-definition--94868c89-83c2-464b-929b-a1a8aa3c8487",
|
562
|
+
)
|
563
|
+
search_filters.append(
|
564
|
+
"(doc.created_by_ref IN [@visible_to, NULL] OR @marking_visible_to_all ANY IN doc.object_marking_refs)"
|
565
|
+
)
|
566
|
+
|
567
|
+
query = f"""
|
568
|
+
FOR doc in @@collection
|
569
|
+
SEARCH doc.type == 'relationship' AND { ' AND '.join(search_filters) }
|
570
|
+
{self.get_sort_stmt(SRO_SORT_FIELDS)}
|
571
|
+
|
572
|
+
COLLECT id = doc.id INTO docs
|
573
|
+
LET doc = FIRST(FOR d in docs[*].doc SORT d.modified OR d.created DESC, d._record_modified DESC RETURN d)
|
574
|
+
|
575
|
+
LIMIT @offset, @count
|
576
|
+
RETURN KEEP(doc, KEYS(doc, true))
|
577
|
+
|
578
|
+
"""
|
579
|
+
# return HttpResponse(content=f"{query}\n\n// {__import__('json').dumps(bind_vars)}")
|
580
|
+
return self.execute_query(query, bind_vars=bind_vars)
|
581
|
+
|
582
|
+
def delete_report_object(self, report_id, object_id):
|
583
|
+
db_service = ArangoDBService(
|
584
|
+
self.DB_NAME,
|
585
|
+
[],
|
586
|
+
[],
|
587
|
+
create=False,
|
588
|
+
create_db=False,
|
589
|
+
create_collection=False,
|
590
|
+
username=settings.ARANGODB_USERNAME,
|
591
|
+
password=settings.ARANGODB_PASSWORD,
|
592
|
+
host_url=settings.ARANGODB_HOST_URL,
|
593
|
+
)
|
594
|
+
query = """
|
595
|
+
let doc_ids = (
|
596
|
+
FOR doc IN @@view
|
597
|
+
SEARCH doc.id IN [@object_id, @report_id] AND doc._stixify_report_id == @report_id
|
598
|
+
SORT doc.object_refs
|
599
|
+
RETURN [doc._id, doc.id]
|
600
|
+
)
|
601
|
+
LET doc_id = FIRST(doc_ids[* FILTER CURRENT[1]== @object_id])
|
602
|
+
LET report_id = FIRST(FIRST(doc_ids[* FILTER CURRENT[1] == @report_id]))
|
603
|
+
|
604
|
+
RETURN [report_id, doc_id, (FOR d IN APPEND([doc_id], (
|
605
|
+
FOR doc IN @@view
|
606
|
+
SEARCH doc._from == doc_id[0] OR doc._to == doc_id[0]
|
607
|
+
RETURN [doc._id, doc.id])
|
608
|
+
)
|
609
|
+
FILTER d != NULL
|
610
|
+
RETURN d)]
|
611
|
+
"""
|
612
|
+
bind_vars = {
|
613
|
+
"@view": self.collection,
|
614
|
+
"object_id": object_id,
|
615
|
+
"report_id": report_id,
|
616
|
+
}
|
617
|
+
report_idkey, doc_id, ids_to_be_removed = self.execute_query(
|
618
|
+
query, bind_vars=bind_vars, paginate=False
|
619
|
+
)[0]
|
620
|
+
# separate into collections
|
621
|
+
collections = {}
|
622
|
+
bind_vars = {"ckeys": {}}
|
623
|
+
queries = []
|
624
|
+
stix_ids = []
|
625
|
+
for key_id, stix_id in ids_to_be_removed:
|
626
|
+
stix_ids.append(stix_id)
|
627
|
+
try:
|
628
|
+
db_service.db.delete_document(key_id)
|
629
|
+
except Exception as e:
|
630
|
+
logging.exception("failed to delete object %s", key_id)
|
631
|
+
|
632
|
+
if not stix_ids:
|
633
|
+
return response.Response(status=status.HTTP_204_NO_CONTENT)
|
634
|
+
resp = self.execute_query(
|
635
|
+
"""
|
636
|
+
FOR doc in @@collection FILTER doc._id == @report_idkey
|
637
|
+
UPDATE {_key: doc._key} WITH {object_refs: REMOVE_VALUES(doc.object_refs, @stix_ids)} IN @@collection
|
638
|
+
RETURN {new_length: LENGTH(NEW.object_refs), old_length: LENGTH(doc.object_refs)}
|
639
|
+
""",
|
640
|
+
bind_vars={
|
641
|
+
"report_idkey": report_idkey,
|
642
|
+
"stix_ids": stix_ids,
|
643
|
+
"@collection": report_idkey.split("/")[0],
|
644
|
+
},
|
645
|
+
paginate=False,
|
646
|
+
)
|
647
|
+
logging.info(
|
648
|
+
f"removed references from report.object_refs: {resp} // {ids_to_be_removed}"
|
649
|
+
)
|
650
|
+
if doc_id:
|
651
|
+
doc_collection_name = doc_id[0].split("/")[0]
|
652
|
+
db_service.update_is_latest_several_chunked(
|
653
|
+
list(set([object_id] + stix_ids)),
|
654
|
+
doc_collection_name,
|
655
|
+
doc_collection_name.removesuffix("_vertex_collection").removesuffix(
|
656
|
+
"_edge_collection"
|
657
|
+
)
|
658
|
+
+ "_edge_collection",
|
659
|
+
)
|
660
|
+
return response.Response(status=status.HTTP_204_NO_CONTENT)
|