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.
Files changed (37) hide show
  1. dogesec_commons/__init__.py +0 -0
  2. dogesec_commons/asgi.py +16 -0
  3. dogesec_commons/objects/__init__.py +1 -0
  4. dogesec_commons/objects/apps.py +10 -0
  5. dogesec_commons/objects/conf.py +8 -0
  6. dogesec_commons/objects/db_view_creator.py +164 -0
  7. dogesec_commons/objects/helpers.py +660 -0
  8. dogesec_commons/objects/views.py +427 -0
  9. dogesec_commons/settings.py +161 -0
  10. dogesec_commons/stixifier/__init__.py +0 -0
  11. dogesec_commons/stixifier/apps.py +5 -0
  12. dogesec_commons/stixifier/conf.py +1 -0
  13. dogesec_commons/stixifier/migrations/0001_initial.py +36 -0
  14. dogesec_commons/stixifier/migrations/0002_profile_ai_content_check_variable.py +18 -0
  15. dogesec_commons/stixifier/migrations/0003_rename_ai_content_check_variable_profile_ai_content_check_provider_and_more.py +23 -0
  16. dogesec_commons/stixifier/migrations/0004_profile_identity_id.py +18 -0
  17. dogesec_commons/stixifier/migrations/0005_profile_generate_pdf.py +18 -0
  18. dogesec_commons/stixifier/migrations/__init__.py +0 -0
  19. dogesec_commons/stixifier/models.py +57 -0
  20. dogesec_commons/stixifier/serializers.py +192 -0
  21. dogesec_commons/stixifier/stixifier.py +252 -0
  22. dogesec_commons/stixifier/summarizer.py +62 -0
  23. dogesec_commons/stixifier/views.py +193 -0
  24. dogesec_commons/urls.py +45 -0
  25. dogesec_commons/utils/__init__.py +3 -0
  26. dogesec_commons/utils/autoschema.py +88 -0
  27. dogesec_commons/utils/exceptions.py +28 -0
  28. dogesec_commons/utils/filters.py +66 -0
  29. dogesec_commons/utils/ordering.py +47 -0
  30. dogesec_commons/utils/pagination.py +81 -0
  31. dogesec_commons/utils/schemas.py +27 -0
  32. dogesec_commons/utils/serializers.py +47 -0
  33. dogesec_commons/wsgi.py +16 -0
  34. dogesec_commons-1.0.2.dist-info/METADATA +57 -0
  35. dogesec_commons-1.0.2.dist-info/RECORD +37 -0
  36. dogesec_commons-1.0.2.dist-info/WHEEL +4 -0
  37. 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)