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.
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/PKG-INFO +1 -1
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/network.py +5 -3
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/__init__.py +146 -156
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/pyproject.toml +1 -1
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/LICENSE +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/README.md +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/.gitignore +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/base.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/actions.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/auth.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/configs.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/fetchers.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/lookup.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/registration.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/static.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/api/v1/sync.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/app.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/cache/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/bytes_utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/classification.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/classification.yml +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/dict_utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/exceptions.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/forge.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/json_utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/list_utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/logging/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/logging/audit.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/logging/format.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/regex.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/str_utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/swagger.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/common/uid.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/config.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/constants/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/constants/env.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/constants/supported_types.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/cronjobs/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/cronjobs/plugins.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/error.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/extensions/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/extensions/config.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/gunicorn_config.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/healthz.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/helper/discover.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/helper/headers.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/helper/oauth.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/actions.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/config.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/fetchers.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/graph.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/model_list.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/base.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/file.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/graph.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/image.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/status.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/results/validation.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/schema.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/selector.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/sync.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/models/validators.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/patched.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/celery_app.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/central_server.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/email_render.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/token.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/helpers/trino.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/models.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/plugin/utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/py.typed +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/cache.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/events.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/hash.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/queues/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/queues/comms.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/set.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/remote/datatypes/user_quota_tracker.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/security/__init__.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/security/obo.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/security/utils.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/action_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/auth_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/config_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/fetcher_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/jwt_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/lookup_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/mongo_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/type_service.py +0 -0
- {clue_api-1.5.0.dev261 → clue_api-1.5.0.dev271}/clue/services/user_service.py +0 -0
|
@@ -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:
|
|
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
|
|
863
|
-
if
|
|
864
|
-
|
|
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
|
-
|
|
883
|
-
|
|
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
|
-
|
|
886
|
-
|
|
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
|
|
998
|
-
if
|
|
999
|
-
|
|
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
|
-
|
|
1018
|
-
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
|
1136
|
-
if
|
|
1137
|
-
|
|
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
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
|
1231
|
-
if
|
|
1232
|
-
|
|
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
|
|
1330
|
-
if
|
|
1331
|
-
|
|
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
|
|
1423
|
-
if
|
|
1424
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|