clue-api 1.5.0.dev261__tar.gz → 1.5.0.dev271__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.5.0.dev261 → clue_api-1.5.0.dev271}/PKG-INFO +1 -1
  2. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/network.py +5 -3
  3. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/__init__.py +146 -156
  4. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/pyproject.toml +1 -1
  5. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/LICENSE +0 -0
  6. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/README.md +0 -0
  7. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/.gitignore +0 -0
  8. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/__init__.py +0 -0
  9. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/__init__.py +0 -0
  10. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/base.py +0 -0
  11. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/__init__.py +0 -0
  12. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/actions.py +0 -0
  13. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/auth.py +0 -0
  14. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/configs.py +0 -0
  15. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/fetchers.py +0 -0
  16. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/lookup.py +0 -0
  17. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/registration.py +0 -0
  18. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/static.py +0 -0
  19. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/sync.py +0 -0
  20. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/app.py +0 -0
  21. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/cache/__init__.py +0 -0
  22. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/__init__.py +0 -0
  23. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/bytes_utils.py +0 -0
  24. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/classification.py +0 -0
  25. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/classification.yml +0 -0
  26. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/dict_utils.py +0 -0
  27. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/exceptions.py +0 -0
  28. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/forge.py +0 -0
  29. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/json_utils.py +0 -0
  30. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/list_utils.py +0 -0
  31. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/logging/__init__.py +0 -0
  32. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/logging/audit.py +0 -0
  33. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/logging/format.py +0 -0
  34. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/regex.py +0 -0
  35. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/str_utils.py +0 -0
  36. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/swagger.py +0 -0
  37. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/uid.py +0 -0
  38. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/config.py +0 -0
  39. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/constants/__init__.py +0 -0
  40. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/constants/env.py +0 -0
  41. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/constants/supported_types.py +0 -0
  42. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/cronjobs/__init__.py +0 -0
  43. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/cronjobs/plugins.py +0 -0
  44. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/error.py +0 -0
  45. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/extensions/__init__.py +0 -0
  46. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/extensions/config.py +0 -0
  47. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/gunicorn_config.py +0 -0
  48. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/healthz.py +0 -0
  49. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/helper/discover.py +0 -0
  50. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/helper/headers.py +0 -0
  51. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/helper/oauth.py +0 -0
  52. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/__init__.py +0 -0
  53. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/actions.py +0 -0
  54. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/config.py +0 -0
  55. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/fetchers.py +0 -0
  56. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/graph.py +0 -0
  57. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/model_list.py +0 -0
  58. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/__init__.py +0 -0
  59. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/base.py +0 -0
  60. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/file.py +0 -0
  61. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/graph.py +0 -0
  62. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/image.py +0 -0
  63. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/status.py +0 -0
  64. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/validation.py +0 -0
  65. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/schema.py +0 -0
  66. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/selector.py +0 -0
  67. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/sync.py +0 -0
  68. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/validators.py +0 -0
  69. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/patched.py +0 -0
  70. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/celery_app.py +0 -0
  71. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/__init__.py +0 -0
  72. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/central_server.py +0 -0
  73. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/email_render.py +0 -0
  74. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/token.py +0 -0
  75. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/trino.py +0 -0
  76. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/models.py +0 -0
  77. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/utils.py +0 -0
  78. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/py.typed +0 -0
  79. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/__init__.py +0 -0
  80. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/__init__.py +0 -0
  81. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/cache.py +0 -0
  82. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/events.py +0 -0
  83. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/hash.py +0 -0
  84. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/queues/__init__.py +0 -0
  85. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/queues/comms.py +0 -0
  86. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/set.py +0 -0
  87. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/user_quota_tracker.py +0 -0
  88. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/security/__init__.py +0 -0
  89. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/security/obo.py +0 -0
  90. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/security/utils.py +0 -0
  91. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/action_service.py +0 -0
  92. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/auth_service.py +0 -0
  93. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/config_service.py +0 -0
  94. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/fetcher_service.py +0 -0
  95. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/jwt_service.py +0 -0
  96. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/lookup_service.py +0 -0
  97. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/mongo_service.py +0 -0
  98. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/type_service.py +0 -0
  99. {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/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.5.0.dev261
3
+ Version: 1.5.0.dev271
4
4
  Summary: Clue distributed enrichment service
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -362,6 +362,7 @@ class QueryEntry(BaseModel):
362
362
  examples=sample(sorted(CLASSIFICATION.list_all_classification_combinations()), k=5),
363
363
  ),
364
364
  ] = "TLP:CLEAR"
365
+
365
366
  count: Annotated[
366
367
  int,
367
368
  Field(
@@ -369,14 +370,17 @@ class QueryEntry(BaseModel):
369
370
  examples=sorted([floor(i / 10) for i in randbytes(5)]), # noqa: S311
370
371
  ),
371
372
  ] = 1
373
+
372
374
  link: Annotated[
373
375
  Optional[Url],
374
376
  Field(description="Link to more information", examples=[Url("https://example.com/moreinfo"), None]),
375
377
  ] = None
378
+
376
379
  annotations: Annotated[
377
380
  list[Annotation],
378
381
  Field(description="A list of annotations returned from the service for this entry"),
379
382
  ] = []
383
+
380
384
  raw_data: Annotated[
381
385
  Any,
382
386
  Field(
@@ -385,9 +389,7 @@ class QueryEntry(BaseModel):
385
389
  ),
386
390
  ] = None
387
391
 
388
- expiry: Annotated[
389
- datetime | None, Field(description="When should this record expire?", default_factory=generate_expiry)
390
- ]
392
+ expiry: datetime | None = Field(description="When should this record expire?", default_factory=generate_expiry)
391
393
 
392
394
  model_config = ConfigDict(validate_assignment=True)
393
395
 
@@ -18,7 +18,6 @@ from pydantic_core import PydanticSerializationError
18
18
 
19
19
  from clue.cache import Cache
20
20
  from clue.common.exceptions import (
21
- AuthenticationException,
22
21
  ClueException,
23
22
  ClueValueError,
24
23
  InvalidDataException,
@@ -515,34 +514,6 @@ class CluePlugin:
515
514
 
516
515
  self.logger.debug("Initialization complete!")
517
516
 
518
- def __check_actions(self) -> list[Action] | None:
519
- """Validate token and retrieve dynamic actions if setup_actions is configured.
520
-
521
- This method handles token validation when required and calls the setup_actions
522
- function to get a potentially user-specific or dynamically generated list of actions.
523
-
524
- Returns:
525
- list[Action] | None: List of actions if setup_actions is configured, None otherwise
526
-
527
- Raises:
528
- AuthenticationException: If token validation fails
529
- """
530
- if self.setup_actions:
531
- # Validate token if token validation is configured
532
- if self.validate_token:
533
- token, error = self.validate_token()
534
-
535
- if error:
536
- self.logger.error("Error on token validation: %s", error)
537
- raise AuthenticationException(error)
538
- else:
539
- token = None
540
-
541
- # Call user-defined setup_actions with base actions and validated token
542
- return self.setup_actions(self.actions or [], token)
543
-
544
- return None
545
-
546
517
  def __init_apm(self):
547
518
  """Initialize Application Performance Monitoring (APM) using Elastic APM.
548
519
 
@@ -790,6 +761,96 @@ class CluePlugin:
790
761
  status_code,
791
762
  )
792
763
 
764
+ def _resolve_token(self: Self, context: str | None = None) -> tuple[str | None, Response | None]:
765
+ """Call the plugin's validate_token function and return the token, or an error response.
766
+
767
+ Args:
768
+ context: Optional name of the calling endpoint (e.g. "action", "fetcher") used
769
+ in the warning message when no validator is configured.
770
+
771
+ Returns:
772
+ tuple[str | None, Response | None]: ``(token, None)`` on success,
773
+ or ``(None, error_response)`` when validation fails or raises.
774
+ """
775
+ if not self.validate_token:
776
+ if context:
777
+ self.logger.warning(
778
+ "No token validator provided. The access token will not be provided to the %s.", context
779
+ )
780
+ else:
781
+ self.logger.warning("No token validator provided")
782
+ return None, None
783
+
784
+ self.logger.debug("Executing plugin-provided token validator")
785
+ try:
786
+ token, error = self.validate_token()
787
+ except Exception:
788
+ self.logger.exception("Catastrophic error in validate_token")
789
+ return None, self.make_api_response(None, "An internal error has occurred", 500)
790
+
791
+ if error:
792
+ return None, self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
793
+
794
+ self.logger.debug("Token is valid")
795
+ return token, None
796
+
797
+ def _call_plugin_func(self: Self, func: Callable, *args: Any, **kwargs: Any) -> tuple[Any, Response | None]:
798
+ """Call a plugin-provided function with standard enrichment exception handling.
799
+
800
+ Maps the well-known Clue exceptions to their corresponding HTTP responses and
801
+ catches any other exception as a 500. Intended for ``enrich`` and
802
+ ``alternate_bulk_lookup`` calls.
803
+
804
+ Args:
805
+ func: The plugin function to call.
806
+ *args: Positional arguments forwarded to the function.
807
+ **kwargs: Keyword arguments forwarded to the function.
808
+
809
+ Returns:
810
+ tuple[Any, Response | None]: ``(result, None)`` on success,
811
+ or ``(None, error_response)`` when the function raises.
812
+ """
813
+ try:
814
+ return func(*args, **kwargs), None
815
+ except InvalidDataException as e:
816
+ return None, self.make_api_response(None, e.message, 400)
817
+ except NotFoundException:
818
+ return None, self.make_api_response([], "", 404)
819
+ except TimeoutException as e:
820
+ return None, self.make_api_response(None, e.message or "Request timed out", 408)
821
+ except UnprocessableException as e:
822
+ return None, self.make_api_response(None, e.message, 422)
823
+ except Exception:
824
+ self.logger.exception("Unknown internal exception")
825
+ return None, self.make_api_response(None, "An internal error has occurred", 500)
826
+
827
+ def _get_actions(self: Self) -> tuple[list[Action], Response | None]:
828
+ """Retrieve the action list, running setup_actions if configured.
829
+
830
+ If setup_actions is configured, validates the token first and then calls it
831
+ to get a potentially user-specific or dynamic list of actions.
832
+
833
+ Returns:
834
+ tuple[list[Action], Response | None]: ``(actions, None)`` on success,
835
+ or ``([], error_response)`` if token validation or setup_actions fails.
836
+ """
837
+ try:
838
+ if self.setup_actions:
839
+ token, error_response = self._resolve_token(context="action setup")
840
+ if error_response:
841
+ return [], error_response
842
+
843
+ # Call user-defined setup_actions with base actions and validated token
844
+ actions = self.setup_actions(self.actions or [], token)
845
+ if actions is None:
846
+ return self.actions or [], None
847
+ return actions, None
848
+
849
+ return self.actions or [], None
850
+ except Exception:
851
+ self.logger.exception("Exception on setup actions:")
852
+ return [], self.make_api_response({}, err="Error on action setup.", status_code=500)
853
+
793
854
  def get_type_names(self: Self) -> Response:
794
855
  """Return the list of supported selector types with their classifications.
795
856
 
@@ -859,12 +920,10 @@ class CluePlugin:
859
920
  422,
860
921
  )
861
922
 
862
- token: str | None = None
863
- if self.validate_token:
864
- token, error = self.validate_token()
923
+ token, error_response = self._resolve_token()
924
+ if error_response:
925
+ return error_response
865
926
 
866
- if error:
867
- return self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
868
927
  try:
869
928
  if self.cache and params.use_cache:
870
929
  if result := self.cache.get(type_name, value, params):
@@ -879,22 +938,12 @@ class CluePlugin:
879
938
  except Exception:
880
939
  self.logger.exception("Unknown internal exception on cache check, continuing to standard enrichment")
881
940
 
882
- try:
883
- results = self.enrich(type_name, value, params, token)
941
+ results, error_response = self._call_plugin_func(self.enrich, type_name, value, params, token)
942
+ if error_response:
943
+ return error_response
884
944
 
885
- if not isinstance(results, list):
886
- results = [results]
887
- except InvalidDataException as e:
888
- return self.make_api_response(None, e.message, 400)
889
- except NotFoundException:
890
- return self.make_api_response([], "", 404)
891
- except TimeoutException as e:
892
- return self.make_api_response(None, e.message or "Request timed out", 408)
893
- except UnprocessableException as e:
894
- return self.make_api_response(None, e.message, 422)
895
- except Exception as e:
896
- self.logger.exception("Unknown internal exception")
897
- return self.make_api_response(None, f"Something went wrong when enriching: {e}", 500)
945
+ if not isinstance(results, list):
946
+ results = [results]
898
947
 
899
948
  try:
900
949
  serialized_reult = TypeAdapter(list[QueryEntry]).dump_python(results, mode="json", exclude_none=True)
@@ -994,18 +1043,9 @@ class CluePlugin:
994
1043
 
995
1044
  remaining_items.append(entry)
996
1045
 
997
- token: str | None = None
998
- if self.validate_token:
999
- self.logger.debug("Executing plugin-provided token validator")
1000
-
1001
- token, error = self.validate_token()
1002
-
1003
- if error:
1004
- return self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
1005
-
1006
- self.logger.debug("Token is valid")
1007
- else:
1008
- self.logger.warning("No token validator provided")
1046
+ token, error_response = self._resolve_token()
1047
+ if error_response:
1048
+ return error_response
1009
1049
 
1010
1050
  # All results were cached
1011
1051
  if len(remaining_items) == 0:
@@ -1014,23 +1054,22 @@ class CluePlugin:
1014
1054
  elif self.alternate_bulk_lookup:
1015
1055
  self.logger.debug("Executing plugin-provided alternate bulk lookup script")
1016
1056
 
1017
- try:
1018
- alternate_results = self.alternate_bulk_lookup(remaining_items, params, token)
1057
+ alternate_results, error_response = self._call_plugin_func(
1058
+ self.alternate_bulk_lookup, remaining_items, params, token
1059
+ )
1060
+ if error_response:
1061
+ return error_response
1019
1062
 
1020
- for _type, _values in alternate_results.items():
1021
- for _value, _result in _values.items():
1022
- bulk_result[_type][_value] = _result
1023
- except InvalidDataException as e:
1024
- return self.make_api_response(None, e.message, 400)
1025
- except NotFoundException:
1026
- return self.make_api_response([], "", 404)
1027
- except TimeoutException as e:
1028
- return self.make_api_response(None, e.message or "Request timed out", 408)
1029
- except UnprocessableException as e:
1030
- return self.make_api_response(None, e.message, 422)
1031
- except Exception as e:
1032
- self.logger.exception("Unknown internal exception")
1033
- return self.make_api_response(None, f"Something went wrong when enriching: {e}", 500)
1063
+ if not isinstance(alternate_results, dict):
1064
+ self.logger.error(
1065
+ "alternate_bulk_lookup returned unexpected type: %s",
1066
+ type(alternate_results).__name__,
1067
+ )
1068
+ return self.make_api_response(None, "Something went wrong when enriching: unexpected result type", 500)
1069
+
1070
+ for _type, _values in alternate_results.items():
1071
+ for _value, _result in _values.items():
1072
+ bulk_result[_type][_value] = _result
1034
1073
 
1035
1074
  if self.cache and len(remaining_items) > 0:
1036
1075
  self.logger.info("Caching results for %s selectors", len(remaining_items))
@@ -1043,7 +1082,11 @@ class CluePlugin:
1043
1082
  self.logger.warning("Selector not present in bulk result, skipping cache step")
1044
1083
  # Default bulk lookup
1045
1084
  else:
1046
- self.__default_bulk_lookup(bulk_result, remaining_items, params, token)
1085
+ try:
1086
+ self.__default_bulk_lookup(bulk_result, remaining_items, params, token)
1087
+ except Exception as e:
1088
+ self.logger.exception("Catastrophic error in default bulk lookup")
1089
+ return self.make_api_response(None, f"Something went wrong when enriching: {e}", 500)
1047
1090
 
1048
1091
  # Calculate how close we came to the deadline (positive = time remaining, negative = overrun)
1049
1092
  variance = params.deadline - time.time()
@@ -1077,20 +1120,15 @@ class CluePlugin:
1077
1120
  ...
1078
1121
  }
1079
1122
  """
1080
- try:
1081
- actions = self.__check_actions()
1082
- except Exception:
1083
- self.logger.exception("Exception on setup actions:")
1084
-
1085
- return self.make_api_response({}, err="Error on action setup.", status_code=500)
1123
+ actions, error_response = self._get_actions()
1124
+ if error_response:
1125
+ return error_response
1086
1126
 
1087
- if actions is None:
1088
- actions = self.actions or []
1089
-
1090
- if not self.validate_token or not (token := self.validate_token()[0]):
1091
- self.logger.debug("Returning %s actions for unknown user", len(actions))
1092
- else:
1127
+ token, _ = self._resolve_token()
1128
+ if token:
1093
1129
  self.logger.debug("Returning %s actions for user %s", len(actions), get_username(token))
1130
+ else:
1131
+ self.logger.debug("Returning %s actions for unknown user", len(actions))
1094
1132
 
1095
1133
  results: dict[str, dict[str, Any]] = {}
1096
1134
  for action in actions:
@@ -1118,32 +1156,17 @@ class CluePlugin:
1118
1156
  if not self.run_action:
1119
1157
  return self.make_api_response({}, err=f"{self.app_name} does not support any actions.", status_code=400)
1120
1158
 
1121
- try:
1122
- actions = self.__check_actions()
1123
- except Exception:
1124
- self.logger.exception("Exception on setup actions:")
1125
-
1126
- return self.make_api_response({}, err="Error on action setup.", status_code=500)
1127
-
1128
- if actions is None:
1129
- actions = self.actions or []
1159
+ actions, error_response = self._get_actions()
1160
+ if error_response:
1161
+ return error_response
1130
1162
 
1131
1163
  action_to_run = next((action for action in actions if action.id == action_id), None)
1132
1164
  if not action_to_run:
1133
1165
  return self.make_api_response({}, err="Action does not exist", status_code=404)
1134
1166
 
1135
- token: str | None = None
1136
- if self.validate_token:
1137
- self.logger.debug("Executing plugin-provided token validator")
1138
-
1139
- token, error = self.validate_token()
1140
-
1141
- if error:
1142
- return self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
1143
-
1144
- self.logger.debug("Token is valid")
1145
- else:
1146
- self.logger.warning("No token validation provided. The access token will not be provided to the action.")
1167
+ token, error_response = self._resolve_token(context="action")
1168
+ if error_response:
1169
+ return error_response
1147
1170
 
1148
1171
  # Extract the parameter type from the action definition for validation
1149
1172
  param_type: Any = action_to_run.model_fields["params"].annotation or Any
@@ -1213,32 +1236,17 @@ class CluePlugin:
1213
1236
  {}, err=f"{self.app_name} does not support the get action status functions.", status_code=400
1214
1237
  )
1215
1238
 
1216
- try:
1217
- actions = self.__check_actions()
1218
- except Exception:
1219
- self.logger.exception("Exception on setup actions:")
1220
-
1221
- return self.make_api_response({}, err="Error on action setup.", status_code=500)
1222
-
1223
- if actions is None:
1224
- actions = self.actions or []
1239
+ actions, error_response = self._get_actions()
1240
+ if error_response:
1241
+ return error_response
1225
1242
 
1226
1243
  action_to_check = next((action for action in actions if action.id == action_id), None)
1227
1244
  if not action_to_check:
1228
1245
  return self.make_api_response({}, err="Action does not exist", status_code=404)
1229
1246
 
1230
- token: str | None = None
1231
- if self.validate_token:
1232
- self.logger.debug("Executing plugin-provided token validator")
1233
-
1234
- token, error = self.validate_token()
1235
-
1236
- if error:
1237
- return self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
1238
-
1239
- self.logger.debug("Token is valid")
1240
- else:
1241
- self.logger.warning("No token validation provided. The access token will not be provided to the action.")
1247
+ token, error_response = self._resolve_token(context="action")
1248
+ if error_response:
1249
+ return error_response
1242
1250
 
1243
1251
  try:
1244
1252
  # Validate request body against the action's parameter schema
@@ -1326,18 +1334,9 @@ class CluePlugin:
1326
1334
  if not fetcher_to_run:
1327
1335
  return self.make_api_response({}, err=f"Fetcher {fetcher_id} does not exist", status_code=404)
1328
1336
 
1329
- token: str | None = None
1330
- if self.validate_token:
1331
- self.logger.debug("Executing plugin-provided token validator")
1332
-
1333
- token, error = self.validate_token()
1334
-
1335
- if error:
1336
- return self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
1337
-
1338
- self.logger.debug("Token is valid")
1339
- else:
1340
- self.logger.warning("No token validation provided. The access token will not be provided to the fetcher.")
1337
+ token, error_response = self._resolve_token(context="fetcher")
1338
+ if error_response:
1339
+ return error_response
1341
1340
 
1342
1341
  status_code = 200
1343
1342
  try:
@@ -1419,18 +1418,9 @@ class CluePlugin:
1419
1418
  if not fetcher:
1420
1419
  return self.make_api_response({}, err=f"Fetcher {fetcher_id} does not exist", status_code=404)
1421
1420
 
1422
- token: str | None = None
1423
- if self.validate_token:
1424
- self.logger.debug("Executing plugin-provided token validator")
1425
-
1426
- token, error = self.validate_token()
1427
-
1428
- if error:
1429
- return self.make_api_response(None, f"Error on token validation: {error}", status_code=401)
1430
-
1431
- self.logger.debug("Token is valid")
1432
- else:
1433
- self.logger.warning("No token validation provided. The access token will not be provided to the fetcher.")
1421
+ token, error_response = self._resolve_token(context="fetcher")
1422
+ if error_response:
1423
+ return error_response
1434
1424
 
1435
1425
  status_code = 200
1436
1426
  try:
@@ -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.5.0.dev261"
144
+ version = "1.5.0.dev271"
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