clue-api 1.6.0.dev359__tar.gz → 1.6.0.dev370__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.dev359 → clue_api-1.6.0.dev370}/PKG-INFO +1 -1
  2. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/lookup_service.py +19 -11
  3. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/mongo_service.py +42 -6
  4. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/pyproject.toml +1 -1
  5. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/LICENSE +0 -0
  6. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/README.md +0 -0
  7. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/.gitignore +0 -0
  8. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/__init__.py +0 -0
  9. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/__init__.py +0 -0
  10. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/base.py +0 -0
  11. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/__init__.py +0 -0
  12. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/actions.py +0 -0
  13. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/auth.py +0 -0
  14. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/configs.py +0 -0
  15. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/fetchers.py +0 -0
  16. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/lookup.py +0 -0
  17. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/registration.py +0 -0
  18. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/static.py +0 -0
  19. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/api/v1/sync.py +0 -0
  20. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/app.py +0 -0
  21. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/cache/__init__.py +0 -0
  22. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/__init__.py +0 -0
  23. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/bytes_utils.py +0 -0
  24. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/classification.py +0 -0
  25. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/classification.yml +0 -0
  26. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/dict_utils.py +0 -0
  27. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/exceptions.py +0 -0
  28. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/forge.py +0 -0
  29. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/json_utils.py +0 -0
  30. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/list_utils.py +0 -0
  31. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/logging/__init__.py +0 -0
  32. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/logging/audit.py +0 -0
  33. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/logging/format.py +0 -0
  34. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/regex.py +0 -0
  35. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/str_utils.py +0 -0
  36. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/swagger.py +0 -0
  37. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/common/uid.py +0 -0
  38. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/config.py +0 -0
  39. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/constants/__init__.py +0 -0
  40. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/constants/env.py +0 -0
  41. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/constants/supported_types.py +0 -0
  42. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/cronjobs/__init__.py +0 -0
  43. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/cronjobs/plugins.py +0 -0
  44. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/error.py +0 -0
  45. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/extensions/__init__.py +0 -0
  46. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/extensions/config.py +0 -0
  47. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/gunicorn_config.py +0 -0
  48. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/healthz.py +0 -0
  49. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/helper/discover.py +0 -0
  50. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/helper/headers.py +0 -0
  51. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/helper/oauth.py +0 -0
  52. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/__init__.py +0 -0
  53. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/actions.py +0 -0
  54. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/config.py +0 -0
  55. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/fetchers.py +0 -0
  56. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/graph.py +0 -0
  57. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/model_list.py +0 -0
  58. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/network.py +0 -0
  59. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/__init__.py +0 -0
  60. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/base.py +0 -0
  61. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/file.py +0 -0
  62. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/graph.py +0 -0
  63. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/image.py +0 -0
  64. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/status.py +0 -0
  65. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/results/validation.py +0 -0
  66. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/schema.py +0 -0
  67. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/selector.py +0 -0
  68. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/sync.py +0 -0
  69. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/models/validators.py +0 -0
  70. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/patched.py +0 -0
  71. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/__init__.py +0 -0
  72. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/celery_app.py +0 -0
  73. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/helpers/__init__.py +0 -0
  74. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/helpers/central_server.py +0 -0
  75. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/helpers/email_render.py +0 -0
  76. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/helpers/token.py +0 -0
  77. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/helpers/trino.py +0 -0
  78. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/models.py +0 -0
  79. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/plugin/utils.py +0 -0
  80. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/py.typed +0 -0
  81. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/__init__.py +0 -0
  82. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/__init__.py +0 -0
  83. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/cache.py +0 -0
  84. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/events.py +0 -0
  85. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/hash.py +0 -0
  86. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/queues/__init__.py +0 -0
  87. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/queues/comms.py +0 -0
  88. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/set.py +0 -0
  89. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/remote/datatypes/user_quota_tracker.py +0 -0
  90. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/security/__init__.py +0 -0
  91. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/security/obo.py +0 -0
  92. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/security/utils.py +0 -0
  93. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/action_service.py +0 -0
  94. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/auth_service.py +0 -0
  95. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/config_service.py +0 -0
  96. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/fetcher_service.py +0 -0
  97. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/jwt_service.py +0 -0
  98. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/clue/services/type_service.py +0 -0
  99. {clue_api-1.6.0.dev359 → clue_api-1.6.0.dev370}/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.dev359
3
+ Version: 1.6.0.dev370
4
4
  Summary: Clue distributed enrichment service
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -678,6 +678,13 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
678
678
  query_sources = query_params.query_sources
679
679
  excluded_sources = query_params.excluded_sources
680
680
  available_sources = get_sources(user)
681
+ selected_sources = [
682
+ source
683
+ for source in available_sources
684
+ if (not query_sources or source.name in query_sources)
685
+ and source.name not in excluded_sources
686
+ and (query_sources or source.include_default)
687
+ ]
681
688
 
682
689
  logger.debug(
683
690
  f"Beginning enrichment for {len(data)} selectors on sources "
@@ -698,12 +705,20 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
698
705
  bulk_result.setdefault(entry.type, {})
699
706
  bulk_result[entry.type].setdefault(entry.value, {})
700
707
 
701
- pool_size = min(len(data) * len(query_sources or available_sources), int(os.environ.get("EXECUTOR_THREADS", 32)))
702
- thread_pool = Pool(pool_size)
708
+ if not selected_sources:
709
+ raise InvalidDataException("You must provide at least one source.")
703
710
 
711
+ pool_size = min(len(data) * len(selected_sources), int(os.environ.get("EXECUTOR_THREADS", 32)))
712
+ thread_pool = Pool(pool_size)
704
713
  existing_results: dict[str, list[dict[str, str]]] = {}
705
714
  if config.ui.replication:
706
- existing_results = mongo_service.existing_results(user["uname"], "selectors", data, available_sources)
715
+ if query_params.no_cache:
716
+ # no_cache bypasses the mongo cache check entirely (forcing every source to be re-queried) and
717
+ # invalidates whatever was previously cached so the fresh results replace it, rather than piling
718
+ # up as duplicates alongside it.
719
+ mongo_service.invalidate_existing(user["uname"], "selectors", data, selected_sources)
720
+ else:
721
+ existing_results = mongo_service.existing_results(user["uname"], "selectors", data, selected_sources)
707
722
 
708
723
  sources_per_entry: dict[tuple[str, str], tuple[list[str], list[str]]] = {}
709
724
  for entry in data:
@@ -723,14 +738,7 @@ def bulk_enrich(data: list[Selector], user: dict[str, Any]): # noqa: C901
723
738
  )
724
739
 
725
740
  greenlets: list[tuple[list[Selector], ExternalSource, Greenlet[Any, dict[str, dict[str, QueryResult]]]]] = []
726
- for source in available_sources:
727
- if query_sources and source.name not in query_sources:
728
- continue
729
- if excluded_sources and source.name in excluded_sources:
730
- continue
731
- if not query_sources and not source.include_default:
732
- continue
733
-
741
+ for source in selected_sources:
734
742
  obo_access_token, error = auth_service.check_obo(source, access_token, user["uname"])
735
743
 
736
744
  if error:
@@ -4,7 +4,7 @@ load_dotenv()
4
4
 
5
5
  import json
6
6
  import time
7
- from datetime import datetime
7
+ from datetime import datetime, timezone
8
8
  from typing import Any
9
9
 
10
10
  from flask import Response
@@ -19,7 +19,7 @@ from clue.config import config, get_redis
19
19
  from clue.models.config import ExternalSource
20
20
  from clue.models.schema import get_bson_schema
21
21
  from clue.models.selector import Selector
22
- from clue.models.sync import ChangeRow, Checkpoint, PublishEvent, SelectorDocument
22
+ from clue.models.sync import ChangeRow, Checkpoint, PublishEvent, SelectorDocument, generate_updated_at
23
23
 
24
24
  logger = get_logger(__file__)
25
25
 
@@ -329,9 +329,8 @@ def existing_results(
329
329
  return {}
330
330
 
331
331
  try:
332
- types = [selector.type for selector in selectors]
333
- values = [selector.value for selector in selectors]
334
332
  sources = [source.name for source in external_sources]
333
+ selector_pairs = [{"type": selector.type, "value": selector.value} for selector in selectors]
335
334
 
336
335
  raw_result = (
337
336
  _get_collection(user, collection)
@@ -339,11 +338,14 @@ def existing_results(
339
338
  [
340
339
  {
341
340
  "$match": {
342
- "type": {"$in": types},
343
- "value": {"$in": values},
344
341
  "source": {"$in": sources},
345
342
  "_deleted": False,
346
343
  "error": None,
344
+ "$and": [
345
+ {"$or": selector_pairs},
346
+ # Only records with no expiry, or one that hasn't passed yet, count as "existing".
347
+ {"$or": [{"expiry": None}, {"expiry": {"$gt": datetime.now(timezone.utc)}}]},
348
+ ],
347
349
  }
348
350
  },
349
351
  {"$group": {"_id": "$source", "records": {"$push": {"type": "$type", "value": "$value"}}}},
@@ -356,3 +358,37 @@ def existing_results(
356
358
  except ClueRuntimeError:
357
359
  # ClueRuntimeError if mongodb isn't functional - we'll silently fail so enrichment can continue
358
360
  return {}
361
+
362
+
363
+ def invalidate_existing(
364
+ user: str, collection: str, selectors: list[Selector], external_sources: list[ExternalSource]
365
+ ) -> None:
366
+ """Mark cached documents matching the given selectors/sources as deleted.
367
+
368
+ Used when a caller requests `no_cache`, so the stale cached results don't stick around
369
+ (as duplicates) once the freshly queried results are replicated back into the collection.
370
+
371
+ Args:
372
+ user (str): The username to use as the collection name.
373
+ collection (str): The collection name within the user's database.
374
+ selectors (list[Selector]): List of selectors (type/value pairs) to invalidate.
375
+ external_sources (list[ExternalSource]): List of external sources to filter by.
376
+ """
377
+ if not config.ui.replication:
378
+ return
379
+
380
+ try:
381
+ sources = [source.name for source in external_sources]
382
+ selector_pairs = [{"type": selector.type, "value": selector.value} for selector in selectors]
383
+
384
+ _get_collection(user, collection).update_many(
385
+ {
386
+ "source": {"$in": sources},
387
+ "_deleted": False,
388
+ "$or": selector_pairs,
389
+ },
390
+ {"$set": {"_deleted": True, "updated_at": generate_updated_at()}},
391
+ )
392
+ except ClueRuntimeError:
393
+ # ClueRuntimeError if mongodb isn't functional - we'll silently fail so enrichment can continue
394
+ pass
@@ -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.dev359"
144
+ version = "1.6.0.dev370"
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