clue-api 1.3.0.dev20__tar.gz → 1.3.0.dev32__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 (91) hide show
  1. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/PKG-INFO +1 -1
  2. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/lookup.py +4 -1
  3. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/constants/supported_types.py +1 -1
  4. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/selector.py +4 -4
  5. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/security/obo.py +6 -1
  6. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/type_service.py +5 -5
  7. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/pyproject.toml +1 -1
  8. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/LICENSE +0 -0
  9. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/README.md +0 -0
  10. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/.gitignore +0 -0
  11. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/__init__.py +0 -0
  12. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/__init__.py +0 -0
  13. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/base.py +0 -0
  14. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/__init__.py +0 -0
  15. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/actions.py +0 -0
  16. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/auth.py +0 -0
  17. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/configs.py +0 -0
  18. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/fetchers.py +0 -0
  19. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/registration.py +0 -0
  20. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/api/v1/static.py +0 -0
  21. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/app.py +0 -0
  22. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/cache/__init__.py +0 -0
  23. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/__init__.py +0 -0
  24. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/classification.py +0 -0
  25. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/classification.yml +0 -0
  26. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/dict_utils.py +0 -0
  27. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/exceptions.py +0 -0
  28. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/forge.py +0 -0
  29. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/json_utils.py +0 -0
  30. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/list_utils.py +0 -0
  31. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/logging/__init__.py +0 -0
  32. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/logging/audit.py +0 -0
  33. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/logging/format.py +0 -0
  34. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/regex.py +0 -0
  35. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/str_utils.py +0 -0
  36. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/swagger.py +0 -0
  37. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/common/uid.py +0 -0
  38. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/config.py +0 -0
  39. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/constants/__init__.py +0 -0
  40. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/cronjobs/__init__.py +0 -0
  41. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/cronjobs/plugins.py +0 -0
  42. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/error.py +0 -0
  43. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/extensions/__init__.py +0 -0
  44. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/extensions/config.py +0 -0
  45. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/gunicorn_config.py +0 -0
  46. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/healthz.py +0 -0
  47. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/helper/discover.py +0 -0
  48. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/helper/headers.py +0 -0
  49. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/helper/oauth.py +0 -0
  50. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/__init__.py +0 -0
  51. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/actions.py +0 -0
  52. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/config.py +0 -0
  53. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/fetchers.py +0 -0
  54. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/graph.py +0 -0
  55. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/model_list.py +0 -0
  56. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/network.py +0 -0
  57. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/results/__init__.py +0 -0
  58. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/results/base.py +0 -0
  59. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/results/graph.py +0 -0
  60. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/results/image.py +0 -0
  61. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/results/status.py +0 -0
  62. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/results/validation.py +0 -0
  63. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/models/validators.py +0 -0
  64. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/patched.py +0 -0
  65. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/__init__.py +0 -0
  66. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/helpers/__init__.py +0 -0
  67. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/helpers/central_server.py +0 -0
  68. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/helpers/email_render.py +0 -0
  69. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/helpers/token.py +0 -0
  70. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/helpers/trino.py +0 -0
  71. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/models.py +0 -0
  72. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/plugin/utils.py +0 -0
  73. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/py.typed +0 -0
  74. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/__init__.py +0 -0
  75. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/__init__.py +0 -0
  76. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/cache.py +0 -0
  77. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/events.py +0 -0
  78. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/hash.py +0 -0
  79. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/queues/__init__.py +0 -0
  80. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/queues/comms.py +0 -0
  81. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/set.py +0 -0
  82. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/remote/datatypes/user_quota_tracker.py +0 -0
  83. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/security/__init__.py +0 -0
  84. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/security/utils.py +0 -0
  85. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/action_service.py +0 -0
  86. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/auth_service.py +0 -0
  87. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/config_service.py +0 -0
  88. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/fetcher_service.py +0 -0
  89. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/jwt_service.py +0 -0
  90. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/clue/services/lookup_service.py +0 -0
  91. {clue_api-1.3.0.dev20 → clue_api-1.3.0.dev32}/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.3.0.dev20
3
+ Version: 1.3.0.dev32
4
4
  Summary: Clue distributed enrichment service
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -205,13 +205,16 @@ def enrich(type_name: str, value: str, **kwargs) -> dict[str, QueryResult]:
205
205
  user = kwargs["user"]
206
206
 
207
207
  # For backwards compatability, if eml is used it is replaced with email
208
- type_name = type_name.replace("eml", "email")
208
+ type_name = type_name.lower().replace("eml", "email")
209
209
 
210
210
  if type_name == "telemetry":
211
211
  try:
212
212
  json.loads(urllib.parse.unquote(value))
213
213
  except json.JSONDecodeError:
214
214
  return bad_request(err="If type is telemetry, value must be a valid JSON object.")
215
+ else:
216
+ # Normalize to lowercase all non-telemetry inputs
217
+ value = value.lower()
215
218
 
216
219
  # re-encode the type after being decoded going through flask/wsgi route
217
220
  value = urllib.parse.quote(value, safe="")
@@ -37,4 +37,4 @@ SUPPORTED_TYPES = {
37
37
  "tenant-id": UUID4_REGEX,
38
38
  }
39
39
 
40
- CASE_INSENSITIVE_TYPES = ["ip", "domain", "port", "tenant-id"]
40
+ CASE_INSENSITIVE_TYPES = ["ip", "domain", "port", "tenant-id", "hbs_oid", "hbs_agent_id"]
@@ -1,9 +1,10 @@
1
1
  # ruff: noqa: D101
2
2
  import ipaddress
3
3
  import json
4
+ from typing import Annotated
4
5
 
5
6
  from flask import request
6
- from pydantic import BaseModel, Field, model_validator
7
+ from pydantic import BaseModel, Field, StringConstraints, model_validator
7
8
  from typing_extensions import Self
8
9
 
9
10
  from clue.common.logging import get_logger
@@ -14,7 +15,7 @@ logger = get_logger(__file__)
14
15
 
15
16
 
16
17
  class Selector(BaseModel):
17
- type: str
18
+ type: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)]
18
19
  value: str
19
20
  classification: str | None = Field(default=None)
20
21
  sources: list[str] | None = Field(default=None)
@@ -41,8 +42,7 @@ class Selector(BaseModel):
41
42
  json.loads(self.value)
42
43
  except json.JSONDecodeError as e:
43
44
  raise AssertionError("If type is telemetry, value must be a valid JSON object.") from e
44
-
45
- if self.type in CASE_INSENSITIVE_TYPES:
45
+ elif self.type in CASE_INSENSITIVE_TYPES:
46
46
  self.value = self.value.lower()
47
47
 
48
48
  if not self.classification:
@@ -27,7 +27,12 @@ def _get_token_raw(service: str, user: str) -> Optional[str]:
27
27
  token_store = _get_obo_token_store(service, user)
28
28
 
29
29
  if token_store.length() > 0:
30
- return token_store.random(1)[0]
30
+ result = token_store.random(1)
31
+
32
+ if len(result) > 0:
33
+ return result[0]
34
+
35
+ logger.warning("Token store reported at least one entry, but was empty on fetch.")
31
36
 
32
37
  return None
33
38
 
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
- import elasticapm
4
3
  import requests
4
+ from elasticapm.traces import capture_span
5
5
  from flask import request
6
6
  from requests import exceptions
7
7
 
@@ -62,12 +62,12 @@ def get_supported_types(source_url: str, access_token: str | None = None, obo_ac
62
62
  return result
63
63
 
64
64
  logger.debug("Cache miss, polling plugin")
65
- with elasticapm.capture_span(f"GET {url}", span_type="http"):
65
+ with capture_span(f"GET {url}", span_type="http"):
66
66
  headers = generate_headers(obo_access_token or access_token, access_token if obo_access_token else None)
67
67
 
68
68
  try:
69
69
  rsp = requests.get(url, headers=headers, timeout=3.0)
70
- except exceptions.ConnectionError:
70
+ except (exceptions.ConnectionError, exceptions.ReadTimeout):
71
71
  # any errors are logged and no result is saved to local cache to enable retry on next query
72
72
  logger.exception(f"Unable to connect: {url}")
73
73
  return None
@@ -81,7 +81,7 @@ def get_supported_types(source_url: str, access_token: str | None = None, obo_ac
81
81
  except requests.exceptions.JSONDecodeError:
82
82
  logger.exception(
83
83
  f"Parsing error in error ({rsp.status_code}) response - unknown format\n"
84
- f"Raw response: {rsp.content}"
84
+ f"Raw response: {rsp.text}"
85
85
  )
86
86
  return None
87
87
  except KeyError:
@@ -103,7 +103,7 @@ def get_supported_types(source_url: str, access_token: str | None = None, obo_ac
103
103
  CACHE.set(url, types_result)
104
104
  return types_result
105
105
  except requests.exceptions.JSONDecodeError:
106
- logger.exception("Parsing error in OK response - unknown format\n" f"Raw response: {rsp.content}")
106
+ logger.exception("Parsing error in OK response - unknown format\n" f"Raw response: {rsp.text}")
107
107
  return None
108
108
  except Exception:
109
109
  logger.exception("External API did not return expected format:")
@@ -146,7 +146,7 @@ log_cli_level = "WARN"
146
146
  [tool.poetry]
147
147
  package-mode = true
148
148
  name = "clue-api"
149
- version = "1.3.0.dev20"
149
+ version = "1.3.0.dev32"
150
150
  description = "Clue distributed enrichment service"
151
151
  authors = ["Canadian Centre for Cyber Security <contact@cyber.gc.ca>"]
152
152
  license = "MIT"
File without changes
File without changes