clue-api 1.6.0.dev335__tar.gz → 1.6.0.dev337__tar.gz

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 (99) hide show
  1. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/PKG-INFO +1 -1
  2. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/lookup.py +8 -2
  3. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/lookup_service.py +68 -6
  4. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/pyproject.toml +1 -1
  5. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/LICENSE +0 -0
  6. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/README.md +0 -0
  7. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/.gitignore +0 -0
  8. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/__init__.py +0 -0
  9. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/__init__.py +0 -0
  10. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/base.py +0 -0
  11. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/__init__.py +0 -0
  12. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/actions.py +0 -0
  13. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/auth.py +0 -0
  14. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/configs.py +0 -0
  15. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/fetchers.py +0 -0
  16. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/registration.py +0 -0
  17. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/static.py +0 -0
  18. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/api/v1/sync.py +0 -0
  19. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/app.py +0 -0
  20. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/cache/__init__.py +0 -0
  21. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/__init__.py +0 -0
  22. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/bytes_utils.py +0 -0
  23. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/classification.py +0 -0
  24. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/classification.yml +0 -0
  25. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/dict_utils.py +0 -0
  26. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/exceptions.py +0 -0
  27. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/forge.py +0 -0
  28. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/json_utils.py +0 -0
  29. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/list_utils.py +0 -0
  30. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/logging/__init__.py +0 -0
  31. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/logging/audit.py +0 -0
  32. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/logging/format.py +0 -0
  33. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/regex.py +0 -0
  34. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/str_utils.py +0 -0
  35. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/swagger.py +0 -0
  36. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/common/uid.py +0 -0
  37. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/config.py +0 -0
  38. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/constants/__init__.py +0 -0
  39. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/constants/env.py +0 -0
  40. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/constants/supported_types.py +0 -0
  41. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/cronjobs/__init__.py +0 -0
  42. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/cronjobs/plugins.py +0 -0
  43. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/error.py +0 -0
  44. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/extensions/__init__.py +0 -0
  45. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/extensions/config.py +0 -0
  46. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/gunicorn_config.py +0 -0
  47. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/healthz.py +0 -0
  48. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/helper/discover.py +0 -0
  49. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/helper/headers.py +0 -0
  50. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/helper/oauth.py +0 -0
  51. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/__init__.py +0 -0
  52. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/actions.py +0 -0
  53. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/config.py +0 -0
  54. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/fetchers.py +0 -0
  55. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/graph.py +0 -0
  56. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/model_list.py +0 -0
  57. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/network.py +0 -0
  58. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/__init__.py +0 -0
  59. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/base.py +0 -0
  60. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/file.py +0 -0
  61. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/graph.py +0 -0
  62. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/image.py +0 -0
  63. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/status.py +0 -0
  64. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/results/validation.py +0 -0
  65. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/schema.py +0 -0
  66. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/selector.py +0 -0
  67. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/sync.py +0 -0
  68. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/models/validators.py +0 -0
  69. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/patched.py +0 -0
  70. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/__init__.py +0 -0
  71. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/celery_app.py +0 -0
  72. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/helpers/__init__.py +0 -0
  73. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/helpers/central_server.py +0 -0
  74. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/helpers/email_render.py +0 -0
  75. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/helpers/token.py +0 -0
  76. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/helpers/trino.py +0 -0
  77. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/models.py +0 -0
  78. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/plugin/utils.py +0 -0
  79. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/py.typed +0 -0
  80. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/__init__.py +0 -0
  81. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/__init__.py +0 -0
  82. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/cache.py +0 -0
  83. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/events.py +0 -0
  84. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/hash.py +0 -0
  85. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/queues/__init__.py +0 -0
  86. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/queues/comms.py +0 -0
  87. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/set.py +0 -0
  88. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/remote/datatypes/user_quota_tracker.py +0 -0
  89. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/security/__init__.py +0 -0
  90. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/security/obo.py +0 -0
  91. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/security/utils.py +0 -0
  92. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/action_service.py +0 -0
  93. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/auth_service.py +0 -0
  94. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/config_service.py +0 -0
  95. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/fetcher_service.py +0 -0
  96. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/jwt_service.py +0 -0
  97. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/mongo_service.py +0 -0
  98. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/type_service.py +0 -0
  99. {clue_api-1.6.0.dev335 → clue_api-1.6.0.dev337}/clue/services/user_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clue-api
3
- Version: 1.6.0.dev335
3
+ Version: 1.6.0.dev337
4
4
  Summary: Clue distributed enrichment service
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -90,7 +90,10 @@ def bulk_enrich(**kwargs) -> dict[str, dict[str, dict[str, QueryResult]]]:
90
90
 
91
91
  Optional Arguments:
92
92
  classification: string => Classification of the type [Default: minimum configured classification]
93
- sources: string => | separated list of data sources. If empty, all configured sources are used.
93
+ sources: string => | separated list of data sources.
94
+ A source prefixed with '-' will be excluded. Exclusion takes precedence over inclusion.
95
+ If sources is empty or only exclusions, all default configured sources are used (with exclusions applied).
96
+ Note, a source list that includes and excludes the same sources (e.g. sources=vt|-vt) is not treated as empty.
94
97
  max_timeout: number => Maximum execution time for the call in seconds
95
98
  limit: number => limit the amount of returned results counted per source
96
99
  no_annotation: boolean => Do not return any anotations
@@ -171,7 +174,10 @@ def enrich(type_name: str, value: str, **kwargs) -> dict[str, QueryResult]:
171
174
 
172
175
  Optional Arguments:
173
176
  classification: string => Classification of the type [Default: minimum configured classification]
174
- sources: string => | separated list of data sources. If empty, all configured sources are used.
177
+ sources: string => | separated list of data sources.
178
+ A source prefixed with '-' will be excluded. Exclusion takes precedence over inclusion.
179
+ If sources is empty or only exclusions, all default configured sources are used (with exclusions applied).
180
+ Note, a source list that includes and excludes the same sources (e.g. sources=vt|-vt) is not treated as empty.
175
181
  max_timeout: number => Maximum execution time for the call in seconds
176
182
  limit: number => limit the amount of returned results counted per source
177
183
  no_annotation: boolean => Do not return any anotations
@@ -107,6 +107,7 @@ class ParsedParams(BaseModel):
107
107
  "Validation of parameters parsed from request"
108
108
 
109
109
  query_sources: list[str]
110
+ excluded_sources: list[str]
110
111
  max_timeout: float
111
112
  limit: int
112
113
  type_classification: str
@@ -153,8 +154,11 @@ def parse_query_params(request: Request, limit: int = 10, timeout: float = 5.0):
153
154
  else:
154
155
  query_sources = []
155
156
 
157
+ include_sources, exclude_sources = _parse_source_list(query_sources)
158
+
156
159
  return ParsedParams(
157
- query_sources=query_sources,
160
+ query_sources=include_sources,
161
+ excluded_sources=exclude_sources,
158
162
  max_timeout=parse_timeout(timeout),
159
163
  limit=limit,
160
164
  type_classification=type_classification,
@@ -456,6 +460,7 @@ def enrich(type_name: str, value: str, user: dict[str, Any]): # noqa: C901
456
460
  """
457
461
  query_params = parse_query_params(request=request)
458
462
  query_sources = query_params.query_sources
463
+ excluded_sources = query_params.excluded_sources
459
464
  available_sources = get_sources(user)
460
465
 
461
466
  access_token = request.headers.get("Authorization", type=str)
@@ -465,7 +470,8 @@ def enrich(type_name: str, value: str, user: dict[str, Any]): # noqa: C901
465
470
 
466
471
  logger.debug(
467
472
  f"Beginning enrichment for single selector on sources "
468
- f"[{','.join(query_sources or [source.name for source in available_sources])}]"
473
+ f"[{','.join(query_sources or [source.name for source in available_sources if source.include_default])}] "
474
+ f"excluding sources [{','.join(excluded_sources)}]"
469
475
  )
470
476
 
471
477
  results: dict[str, QueryResult] = {}
@@ -478,7 +484,9 @@ def enrich(type_name: str, value: str, user: dict[str, Any]): # noqa: C901
478
484
  for source in available_sources:
479
485
  if query_sources and source.name not in query_sources:
480
486
  continue
481
- elif not query_sources and not source.include_default:
487
+ if excluded_sources and source.name in excluded_sources:
488
+ continue
489
+ if not query_sources and not source.include_default:
482
490
  continue
483
491
 
484
492
  finish_result = functools.partial(build_result, type_name, value, source)
@@ -668,11 +676,13 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
668
676
  """create searches for external sources"""
669
677
  query_params = parse_query_params(request=request)
670
678
  query_sources = query_params.query_sources
679
+ excluded_sources = query_params.excluded_sources
671
680
  available_sources = get_sources(user)
672
681
 
673
682
  logger.debug(
674
683
  f"Beginning enrichment for {len(data)} selectors on sources "
675
- f"[{','.join(query_sources or [source.name for source in available_sources])}]"
684
+ f"[{','.join(query_sources or [source.name for source in available_sources if source.include_default])}] "
685
+ f"excluding sources [{','.join(excluded_sources)}]"
676
686
  )
677
687
 
678
688
  access_token = request.headers.get("Authorization", type=str)
@@ -695,11 +705,30 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
695
705
  if config.ui.replication:
696
706
  existing_results = mongo_service.existing_results(user["uname"], "selectors", data, available_sources)
697
707
 
708
+ sources_per_entry: dict[tuple[str, str], tuple[list[str], list[str]]] = {}
709
+ for entry in data:
710
+ if entry.sources is not None:
711
+ entry_key = (entry.type, entry.value)
712
+ if entry_key not in sources_per_entry:
713
+ sources_per_entry[entry_key] = _parse_source_list(entry.sources)
714
+
715
+ else:
716
+ logger.warning("Duplicate sources found for entry %s:%s. Merging sources.", entry.type, entry.value)
717
+
718
+ existing_include_list, existing_exclude_list = sources_per_entry[entry_key]
719
+ include_sources, exclude_sources = _parse_source_list(entry.sources)
720
+ sources_per_entry[entry_key] = (
721
+ list(set(existing_include_list + include_sources)),
722
+ list(set(existing_exclude_list + exclude_sources)),
723
+ )
724
+
698
725
  greenlets: list[tuple[list[Selector], ExternalSource, Greenlet[Any, dict[str, dict[str, QueryResult]]]]] = []
699
726
  for source in available_sources:
700
727
  if query_sources and source.name not in query_sources:
701
728
  continue
702
- elif not query_sources and not source.include_default:
729
+ if excluded_sources and source.name in excluded_sources:
730
+ continue
731
+ if not query_sources and not source.include_default:
703
732
  continue
704
733
 
705
734
  obo_access_token, error = auth_service.check_obo(source, access_token, user["uname"])
@@ -720,9 +749,15 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
720
749
  # check query against the max supported classification of the external system
721
750
  # if this is not supported, we should let the user know.
722
751
  for entry in data:
723
- if entry.sources is not None and source.name not in entry.sources:
752
+ if entry.sources == []: # support old behaviour of empty entry sources -> use no sources
724
753
  continue
725
754
 
755
+ parsed_sources = sources_per_entry.get((entry.type, entry.value))
756
+ if parsed_sources is not None:
757
+ include_sources, exclude_sources = parsed_sources
758
+ if (include_sources and source.name not in include_sources) or source.name in exclude_sources:
759
+ continue
760
+
726
761
  if (
727
762
  source.name in existing_results
728
763
  and {"type": entry.type, "value": entry.value} in existing_results[source.name]
@@ -802,3 +837,30 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
802
837
  thread_pool.kill(block=False)
803
838
 
804
839
  return bulk_result
840
+
841
+
842
+ def _parse_source_list(source_list: list[str]) -> tuple[list[str], list[str]]:
843
+ """Splits the provided source list into include and exclude lists.
844
+
845
+ Args:
846
+ source_list (list[str]): The list of sources to parse.
847
+
848
+ Returns:
849
+ tuple[list[str], list[str]]: (include_sources, exclude_sources)
850
+ The list of sources to include and the list of sources to exclude.
851
+ """
852
+ include_sources = []
853
+ exclude_sources = []
854
+
855
+ for source in source_list:
856
+ source = source.strip()
857
+ if not source:
858
+ continue
859
+ if source.startswith("-"):
860
+ excluded_source = source[1:].strip()
861
+ if excluded_source:
862
+ exclude_sources.append(excluded_source)
863
+ else:
864
+ include_sources.append(source)
865
+
866
+ return include_sources, exclude_sources
@@ -141,7 +141,7 @@ log_cli_level = "WARN"
141
141
  [tool.poetry]
142
142
  package-mode = true
143
143
  name = "clue-api"
144
- version = "1.6.0.dev335"
144
+ version = "1.6.0.dev337"
145
145
  description = "Clue distributed enrichment service"
146
146
  authors = ["Canadian Centre for Cyber Security <contact@cyber.gc.ca>"]
147
147
  license = "MIT"
File without changes