pyoaev 2.0.14__tar.gz → 2.1.0__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 (84) hide show
  1. {pyoaev-2.0.14/pyoaev.egg-info → pyoaev-2.1.0}/PKG-INFO +5 -4
  2. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/__init__.py +1 -1
  3. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/_version.py +1 -1
  4. pyoaev-2.1.0/pyoaev/configuration/__init__.py +15 -0
  5. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/configuration/configuration.py +26 -1
  6. pyoaev-2.1.0/pyoaev/configuration/connector_config_schema_generator.py +127 -0
  7. pyoaev-2.1.0/pyoaev/configuration/settings_loader.py +125 -0
  8. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/contracts/contract_config.py +13 -0
  9. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/daemons/base_daemon.py +8 -0
  10. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/helpers.py +17 -7
  11. pyoaev-2.1.0/pyoaev/security_domain/types.py +16 -0
  12. {pyoaev-2.0.14 → pyoaev-2.1.0/pyoaev.egg-info}/PKG-INFO +5 -4
  13. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev.egg-info/SOURCES.txt +4 -0
  14. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev.egg-info/requires.txt +4 -3
  15. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyproject.toml +4 -3
  16. pyoaev-2.1.0/test/signatures/__init__.py +0 -0
  17. pyoaev-2.0.14/pyoaev/configuration/__init__.py +0 -3
  18. {pyoaev-2.0.14 → pyoaev-2.1.0}/LICENSE +0 -0
  19. {pyoaev-2.0.14 → pyoaev-2.1.0}/README.md +0 -0
  20. {pyoaev-2.0.14 → pyoaev-2.1.0}/docs/conf.py +0 -0
  21. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/__init__.py +0 -0
  22. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/attack_pattern.py +0 -0
  23. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/collector.py +0 -0
  24. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/cve.py +0 -0
  25. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/document.py +0 -0
  26. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/endpoint.py +0 -0
  27. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inject.py +0 -0
  28. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inject_expectation/__init__.py +0 -0
  29. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inject_expectation/inject_expectation.py +0 -0
  30. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inject_expectation/model/__init__.py +0 -0
  31. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inject_expectation/model/expectation.py +0 -0
  32. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inject_expectation_trace.py +0 -0
  33. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/injector.py +0 -0
  34. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/injector_contract.py +0 -0
  35. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inputs/__init__.py +0 -0
  36. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/inputs/search.py +0 -0
  37. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/kill_chain_phase.py +0 -0
  38. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/me.py +0 -0
  39. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/organization.py +0 -0
  40. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/payload.py +0 -0
  41. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/security_platform.py +0 -0
  42. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/tag.py +0 -0
  43. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/team.py +0 -0
  44. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/apis/user.py +0 -0
  45. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/backends/__init__.py +0 -0
  46. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/backends/backend.py +0 -0
  47. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/backends/protocol.py +0 -0
  48. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/base.py +0 -0
  49. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/client.py +0 -0
  50. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/configuration/sources.py +0 -0
  51. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/contracts/__init__.py +0 -0
  52. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/contracts/contract_builder.py +0 -0
  53. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/contracts/contract_utils.py +0 -0
  54. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/contracts/variable_helper.py +0 -0
  55. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/daemons/__init__.py +0 -0
  56. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/daemons/collector_daemon.py +0 -0
  57. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/exceptions.py +0 -0
  58. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/mixins.py +0 -0
  59. {pyoaev-2.0.14/pyoaev/signatures → pyoaev-2.1.0/pyoaev/security_domain}/__init__.py +0 -0
  60. {pyoaev-2.0.14/test → pyoaev-2.1.0/pyoaev/signatures}/__init__.py +0 -0
  61. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/signatures/signature_match.py +0 -0
  62. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/signatures/signature_type.py +0 -0
  63. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/signatures/types.py +0 -0
  64. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev/utils.py +0 -0
  65. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev.egg-info/dependency_links.txt +0 -0
  66. {pyoaev-2.0.14 → pyoaev-2.1.0}/pyoaev.egg-info/top_level.txt +0 -0
  67. {pyoaev-2.0.14 → pyoaev-2.1.0}/scripts/release.py +0 -0
  68. {pyoaev-2.0.14 → pyoaev-2.1.0}/setup.cfg +0 -0
  69. {pyoaev-2.0.14/test/apis → pyoaev-2.1.0/test}/__init__.py +0 -0
  70. {pyoaev-2.0.14/test/apis/endpoint → pyoaev-2.1.0/test/apis}/__init__.py +0 -0
  71. {pyoaev-2.0.14/test/apis/expectation → pyoaev-2.1.0/test/apis/endpoint}/__init__.py +0 -0
  72. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/apis/endpoint/test_endpoint.py +0 -0
  73. {pyoaev-2.0.14/test/apis/injector_contract → pyoaev-2.1.0/test/apis/expectation}/__init__.py +0 -0
  74. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/apis/expectation/test_expectation.py +0 -0
  75. {pyoaev-2.0.14/test/configuration → pyoaev-2.1.0/test/apis/injector_contract}/__init__.py +0 -0
  76. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/apis/injector_contract/test_injector_contract.py +0 -0
  77. {pyoaev-2.0.14/test/daemons → pyoaev-2.1.0/test/configuration}/__init__.py +0 -0
  78. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/configuration/test_configuration.py +0 -0
  79. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/configuration/test_sources.py +0 -0
  80. {pyoaev-2.0.14/test/signatures → pyoaev-2.1.0/test/daemons}/__init__.py +0 -0
  81. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/daemons/test_base_daemon.py +0 -0
  82. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/daemons/test_collector_daemon.py +0 -0
  83. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/signatures/test_signature_match.py +0 -0
  84. {pyoaev-2.0.14 → pyoaev-2.1.0}/test/signatures/test_signature_type.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyoaev
3
- Version: 2.0.14
3
+ Version: 2.1.0
4
4
  Summary: Python API client for OpenAEV.
5
5
  Author-email: Filigran <contact@filigran.io>
6
6
  Maintainer-email: Filigran <contact@filigran.io>
@@ -26,6 +26,7 @@ Requires-Dist: python-magic-bin<0.5,>=0.4.14; sys_platform == "win32"
26
26
  Requires-Dist: python_json_logger<3.4.0,>=3.3.0
27
27
  Requires-Dist: PyYAML<6.1,>=6.0
28
28
  Requires-Dist: pydantic<2.12.0,>=2.11.3
29
+ Requires-Dist: pydantic-settings<2.12.0,>=2.11.0
29
30
  Requires-Dist: requests<2.33.0,>=2.32.3
30
31
  Requires-Dist: setuptools<80.10.0,>=80.9.0
31
32
  Requires-Dist: cachetools<5.6.0,>=5.5.0
@@ -36,9 +37,9 @@ Requires-Dist: requests-toolbelt<1.1.0,>=1.0.0
36
37
  Requires-Dist: dataclasses-json<0.7.0,>=0.6.4
37
38
  Requires-Dist: thefuzz<0.23,>=0.22
38
39
  Provides-Extra: dev
39
- Requires-Dist: black<25.2.0,>=25.1.0; extra == "dev"
40
- Requires-Dist: build<1.3.0,>=1.2.1; extra == "dev"
41
- Requires-Dist: isort<6.1.0,>=6.0.0; extra == "dev"
40
+ Requires-Dist: black<25.12.0,>=25.11.0; extra == "dev"
41
+ Requires-Dist: build<1.4.0,>=1.3.0; extra == "dev"
42
+ Requires-Dist: isort<6.2.0,>=6.1.0; extra == "dev"
42
43
  Requires-Dist: types-pytz<2025.3.0.0,>=2025.2.0.20250326; extra == "dev"
43
44
  Requires-Dist: pre-commit<4.3.0,>=4.2.0; extra == "dev"
44
45
  Requires-Dist: types-python-dateutil<2.10.0,>=2.9.0; extra == "dev"
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- __version__ = "2.0.14"
2
+ __version__ = "2.1.0"
3
3
 
4
4
  from pyoaev._version import ( # noqa: F401
5
5
  __author__,
@@ -3,4 +3,4 @@ __copyright__ = "Copyright 2025 Filigran"
3
3
  __email__ = "contact@filigran.io"
4
4
  __license__ = "Apache 2.0"
5
5
  __title__ = "python-openaev"
6
- __version__ = "2.0.14"
6
+ __version__ = "2.1.0"
@@ -0,0 +1,15 @@
1
+ from .configuration import Configuration
2
+ from .settings_loader import (
3
+ BaseConfigModel,
4
+ ConfigLoaderCollector,
5
+ ConfigLoaderOAEV,
6
+ SettingsLoader,
7
+ )
8
+
9
+ __all__ = [
10
+ "Configuration",
11
+ "ConfigLoaderOAEV",
12
+ "ConfigLoaderCollector",
13
+ "SettingsLoader",
14
+ "BaseConfigModel",
15
+ ]
@@ -4,7 +4,11 @@ from typing import Any, Dict, Optional
4
4
 
5
5
  import yaml
6
6
  from pydantic import BaseModel, Field
7
+ from pydantic_settings import BaseSettings
7
8
 
9
+ from pyoaev.configuration.connector_config_schema_generator import (
10
+ ConnectorConfigSchemaGenerator,
11
+ )
8
12
  from pyoaev.configuration.sources import DictionarySource, EnvironmentSource
9
13
 
10
14
  CONFIGURATION_TYPES = str | int | bool | Any | None
@@ -111,6 +115,7 @@ class Configuration:
111
115
  config_hints: Dict[str, dict | str],
112
116
  config_values: dict = None,
113
117
  config_file_path: str = os.path.join(os.curdir, "config.yml"),
118
+ config_base_model: BaseSettings = None,
114
119
  ):
115
120
  self.__config_hints = {
116
121
  key: (
@@ -129,6 +134,8 @@ class Configuration:
129
134
 
130
135
  self.__config_values = (config_values or {}) | file_contents
131
136
 
137
+ self.__base_model = config_base_model
138
+
132
139
  def get(self, config_key: str) -> CONFIGURATION_TYPES:
133
140
  """Gets the value pointed to by the configuration key. If the key is defined
134
141
  with actual hints (as opposed to a discrete value), it will use those hints to
@@ -146,7 +153,12 @@ class Configuration:
146
153
  return None
147
154
 
148
155
  return self.__process_value_to_type(
149
- config.data or self.__dig_config_sources_for_key(config), config.is_number
156
+ (
157
+ self.__dig_config_sources_for_key(config)
158
+ if config.data is None
159
+ else config.data
160
+ ),
161
+ config.is_number,
150
162
  )
151
163
 
152
164
  def set(self, config_key: str, value: CONFIGURATION_TYPES):
@@ -164,6 +176,19 @@ class Configuration:
164
176
  else:
165
177
  self.__config_hints[config_key].data = value
166
178
 
179
+ def schema(self):
180
+ """
181
+ Generates the complete connector schema using a custom schema generator compatible with Pydantic.
182
+ Isolate custom class generator, Pydantic expects a class, not an instance
183
+ Always subclass GenerateJsonSchema and pass the class to Pydantic, not an instance
184
+ :return: The generated connector schema as a dictionary.
185
+ """
186
+ return self.__base_model.model_json_schema(
187
+ by_alias=False,
188
+ schema_generator=ConnectorConfigSchemaGenerator,
189
+ mode="validation",
190
+ )
191
+
167
192
  @staticmethod
168
193
  def __process_value_to_type(value: CONFIGURATION_TYPES, is_number_hint: bool):
169
194
  if value is None:
@@ -0,0 +1,127 @@
1
+ ## ADAPTED FROM https://github.com/OpenCTI-Platform/connectors/blob/5c8cf1235f62f5651c9c08d0b67f1bd182662c8a/shared/tools/composer/generate_connectors_config_schemas/generate_connector_config_json_schema.py.sample
2
+
3
+ from copy import deepcopy
4
+ from typing import override
5
+
6
+ from pydantic.json_schema import GenerateJsonSchema
7
+
8
+ # attributes filtered from the connector configuration before generating the manifest
9
+ __FILTERED_ATTRIBUTES__ = [
10
+ # connector id is generated
11
+ "CONNECTOR_ID",
12
+ ]
13
+
14
+
15
+ class ConnectorConfigSchemaGenerator(GenerateJsonSchema):
16
+ @staticmethod
17
+ def dereference_schema(schema_with_refs):
18
+ """Return a new schema with all internal $ref resolved."""
19
+
20
+ def _resolve(schema, root):
21
+ if isinstance(schema, dict):
22
+ if "$ref" in schema:
23
+ ref_path = schema["$ref"]
24
+ if ref_path.startswith("#/$defs/"):
25
+ def_name = ref_path.split("/")[-1]
26
+ # Deep copy to avoid mutating $defs
27
+ resolved = deepcopy(root["$defs"][def_name])
28
+ return _resolve(resolved, root)
29
+ else:
30
+ raise ValueError(f"Unsupported ref format: {ref_path}")
31
+ else:
32
+ return {
33
+ schema_key: _resolve(schema_value, root)
34
+ for schema_key, schema_value in schema.items()
35
+ }
36
+ elif isinstance(schema, list):
37
+ return [_resolve(item, root) for item in schema]
38
+ else:
39
+ return schema
40
+
41
+ return _resolve(deepcopy(schema_with_refs), schema_with_refs)
42
+
43
+ @staticmethod
44
+ def flatten_config_loader_schema(root_schema: dict):
45
+ """
46
+ Flatten config loader schema so all config vars are described at root level.
47
+
48
+ :param root_schema: Original schema.
49
+ :return: Flatten schema.
50
+ """
51
+ flat_json_schema = {
52
+ "$schema": root_schema["$schema"],
53
+ "$id": root_schema["$id"],
54
+ "type": "object",
55
+ "properties": {},
56
+ "required": [],
57
+ "additionalProperties": root_schema.get("additionalProperties", True),
58
+ }
59
+
60
+ for (
61
+ config_loader_namespace_name,
62
+ config_loader_namespace_schema,
63
+ ) in root_schema["properties"].items():
64
+ config_schema = config_loader_namespace_schema.get("properties", {})
65
+ required_config_vars = config_loader_namespace_schema.get("required", [])
66
+
67
+ for config_var_name, config_var_schema in config_schema.items():
68
+ property_name = (
69
+ f"{config_loader_namespace_name.upper()}_{config_var_name.upper()}"
70
+ )
71
+
72
+ config_var_schema.pop("title", None)
73
+
74
+ flat_json_schema["properties"][property_name] = config_var_schema
75
+
76
+ if config_var_name in required_config_vars:
77
+ flat_json_schema["required"].append(property_name)
78
+
79
+ return flat_json_schema
80
+
81
+ @staticmethod
82
+ def filter_schema(schema):
83
+ for filtered_attribute in __FILTERED_ATTRIBUTES__:
84
+ if filtered_attribute in schema["properties"]:
85
+ del schema["properties"][filtered_attribute]
86
+ schema.update(
87
+ {
88
+ "required": [
89
+ item
90
+ for item in schema["required"]
91
+ if item != filtered_attribute
92
+ ]
93
+ }
94
+ )
95
+
96
+ return schema
97
+
98
+ @override
99
+ def generate(self, schema, mode="validation"):
100
+ json_schema = super().generate(schema, mode=mode)
101
+
102
+ json_schema["$schema"] = self.schema_dialect
103
+ json_schema["$id"] = "config.schema.json"
104
+ dereferenced_schema = self.dereference_schema(json_schema)
105
+ flattened_schema = self.flatten_config_loader_schema(dereferenced_schema)
106
+ return self.filter_schema(flattened_schema)
107
+
108
+ @override
109
+ def nullable_schema(self, schema):
110
+ """Generates a JSON schema that matches a schema that allows null values.
111
+
112
+ Args:
113
+ schema: The core schema.
114
+
115
+ Returns:
116
+ The generated JSON schema.
117
+
118
+ Notes:
119
+ This method overrides `GenerateJsonSchema.nullable_schema` to generate schemas without `anyOf` keyword.
120
+ """
121
+ null_schema = {"type": "null"}
122
+ inner_json_schema = self.generate_inner(schema["schema"])
123
+
124
+ if inner_json_schema == null_schema:
125
+ return null_schema
126
+ else:
127
+ return inner_json_schema
@@ -0,0 +1,125 @@
1
+ import os
2
+ from abc import ABC
3
+ from datetime import timedelta
4
+ from pathlib import Path
5
+ from typing import Annotated, Literal
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field, HttpUrl, PlainSerializer
8
+ from pydantic_settings import (
9
+ BaseSettings,
10
+ DotEnvSettingsSource,
11
+ PydanticBaseSettingsSource,
12
+ SettingsConfigDict,
13
+ YamlConfigSettingsSource,
14
+ )
15
+
16
+
17
+ class BaseConfigModel(BaseModel, ABC):
18
+ """Base class for global config models
19
+ To prevent attributes from being modified after initialization.
20
+ """
21
+
22
+ model_config = ConfigDict(extra="allow", frozen=True, validate_default=True)
23
+
24
+
25
+ class SettingsLoader(BaseSettings):
26
+ model_config = SettingsConfigDict(
27
+ frozen=True,
28
+ extra="allow",
29
+ env_nested_delimiter="_",
30
+ env_nested_max_split=1,
31
+ enable_decoding=False,
32
+ )
33
+
34
+ @classmethod
35
+ def settings_customise_sources(
36
+ cls,
37
+ settings_cls: type[BaseSettings],
38
+ init_settings: PydanticBaseSettingsSource,
39
+ env_settings: PydanticBaseSettingsSource,
40
+ dotenv_settings: PydanticBaseSettingsSource,
41
+ file_secret_settings: PydanticBaseSettingsSource,
42
+ ) -> tuple[PydanticBaseSettingsSource, ...]:
43
+ """Customise the sources of settings for the connector.
44
+
45
+ This method is called by the Pydantic BaseSettings class to determine the order of sources.
46
+ The configuration come in this order either from:
47
+ 1. Environment variables
48
+ 2. YAML file
49
+ 3. .env file
50
+ 4. Default values
51
+
52
+ The variables loading order will remain the same as in `pycti.get_config_variable()`:
53
+ 1. If a config.yml file is found, the order will be: `ENV VAR` → config.yml → default value
54
+ 2. If a .env file is found, the order will be: `ENV VAR` → .env → default value
55
+ """
56
+ _main_path = os.curdir
57
+
58
+ settings_cls.model_config["env_file"] = f"{_main_path}/../.env"
59
+
60
+ if not settings_cls.model_config["yaml_file"]:
61
+ if Path(f"{_main_path}/config.yml").is_file():
62
+ settings_cls.model_config["yaml_file"] = f"{_main_path}/config.yml"
63
+ if Path(f"{_main_path}/../config.yml").is_file():
64
+ settings_cls.model_config["yaml_file"] = f"{_main_path}/../config.yml"
65
+
66
+ if Path(settings_cls.model_config["yaml_file"] or "").is_file(): # type: ignore
67
+ return (
68
+ env_settings,
69
+ YamlConfigSettingsSource(settings_cls),
70
+ )
71
+ if Path(settings_cls.model_config["env_file"] or "").is_file(): # type: ignore
72
+ return (
73
+ env_settings,
74
+ DotEnvSettingsSource(settings_cls),
75
+ )
76
+ return (env_settings,)
77
+
78
+
79
+ LogLevelToLower = Annotated[
80
+ Literal["debug", "info", "warn", "error"],
81
+ PlainSerializer(lambda v: "".join(v), return_type=str),
82
+ ]
83
+
84
+ HttpUrlToString = Annotated[HttpUrl, PlainSerializer(str, return_type=str)]
85
+ TimedeltaInSeconds = Annotated[
86
+ timedelta, PlainSerializer(lambda v: int(v.total_seconds()), return_type=int)
87
+ ]
88
+
89
+
90
+ class ConfigLoaderOAEV(BaseConfigModel):
91
+ """OpenAEV/OpenAEV platform configuration settings.
92
+
93
+ Contains URL and authentication token for connecting to the OpenAEV platform.
94
+ """
95
+
96
+ url: HttpUrlToString = Field(
97
+ description="The OpenAEV platform URL.",
98
+ )
99
+ token: str = Field(
100
+ description="The token for the OpenAEV platform.",
101
+ )
102
+
103
+
104
+ class ConfigLoaderCollector(BaseConfigModel):
105
+ """Base collector configuration settings.
106
+
107
+ Contains common collector settings including identification, logging,
108
+ scheduling, and platform information.
109
+ """
110
+
111
+ id: str = Field(description="ID of the collector.")
112
+
113
+ name: str = Field(description="Name of the collector")
114
+
115
+ log_level: LogLevelToLower | None = Field(
116
+ default="error",
117
+ description="Determines the verbosity of the logs.",
118
+ )
119
+ period: timedelta | None = Field(
120
+ default=timedelta(minutes=1),
121
+ description="Duration between two scheduled runs of the collector (ISO 8601 format).",
122
+ )
123
+ icon_filepath: str | None = Field(
124
+ description="Path to the icon file of the collector.",
125
+ )
@@ -120,6 +120,15 @@ class ContractConfig:
120
120
  color_light: str
121
121
 
122
122
 
123
+ @dataclass
124
+ class Domain:
125
+ domain_id: str
126
+ domain_name: str
127
+ domain_color: str
128
+ domain_created_at: str
129
+ domain_updated_at: str
130
+
131
+
123
132
  @dataclass
124
133
  class Contract:
125
134
  contract_id: str
@@ -141,6 +150,7 @@ class Contract:
141
150
  is_atomic_testing: bool = True
142
151
  platforms: List[str] = field(default_factory=list)
143
152
  external_id: str = None
153
+ domains: List[Domain] = None
144
154
 
145
155
  def add_attack_pattern(self, var: str):
146
156
  self.contract_attack_patterns_external_ids.append(var)
@@ -163,6 +173,7 @@ class Contract:
163
173
  "contract_content": json.dumps(self, cls=utils.EnhancedJSONEncoder),
164
174
  "is_atomic_testing": self.is_atomic_testing,
165
175
  "contract_platforms": self.platforms,
176
+ "contract_domains": self.domains,
166
177
  }
167
178
 
168
179
  def to_contract_update_input(self):
@@ -174,6 +185,7 @@ class Contract:
174
185
  "contract_content": json.dumps(self, cls=utils.EnhancedJSONEncoder),
175
186
  "is_atomic_testing": self.is_atomic_testing,
176
187
  "contract_platforms": self.platforms,
188
+ "contract_domains": self.domains,
177
189
  }
178
190
 
179
191
 
@@ -203,6 +215,7 @@ def prepare_contracts(contracts):
203
215
  "contract_attack_patterns_external_ids": c.contract_attack_patterns_external_ids,
204
216
  "contract_content": json.dumps(c, cls=utils.EnhancedJSONEncoder),
205
217
  "contract_platforms": c.platforms,
218
+ "contract_domains": c.domains,
206
219
  },
207
220
  contracts,
208
221
  )
@@ -1,3 +1,4 @@
1
+ import argparse
1
2
  from abc import ABC, abstractmethod
2
3
  from inspect import signature
3
4
  from types import FunctionType
@@ -101,6 +102,13 @@ class BaseDaemon(ABC):
101
102
  follow-up with the main execution loop. Note that at this point, if there is no
102
103
  configured callback, the method will abort and kill the daemon.
103
104
  """
105
+ parser = argparse.ArgumentParser(description="parse daemon options")
106
+ parser.add_argument("--dump-config-schema", action="store_true")
107
+ args = parser.parse_args()
108
+ if args.dump_config_schema:
109
+ print(self._configuration.schema())
110
+ return
111
+
104
112
  if self._callback is None:
105
113
  raise OpenAEVError("This daemon has no configured callback.")
106
114
  self._setup()
@@ -228,13 +228,23 @@ class PingAlive(utils.PingAlive):
228
228
 
229
229
  ### DEPRECATED
230
230
  class OpenAEVConfigHelper:
231
- def __init__(self, base_path, variables: Dict):
232
- self.__config_obj = Configuration(
233
- config_hints=variables,
234
- config_file_path=os.path.join(
235
- os.path.dirname(os.path.abspath(base_path)), "config.yml"
236
- ),
237
- )
231
+ def __init__(self, base_path, variables: Dict | None, config_obj: Configuration):
232
+ if config_obj is not None:
233
+ self.__config_obj = config_obj
234
+ else:
235
+ self.__config_obj = Configuration(
236
+ config_hints=variables,
237
+ config_file_path=os.path.join(
238
+ os.path.dirname(os.path.abspath(base_path)), "config.yml"
239
+ ),
240
+ )
241
+
242
+ @staticmethod
243
+ def from_configuration_object(config: Configuration):
244
+ return OpenAEVConfigHelper(None, None, config)
245
+
246
+ def get_config_obj(self) -> Configuration:
247
+ return self.__config_obj
238
248
 
239
249
  def get_conf(self, variable, is_number=None, default=None, required=None):
240
250
  result = None
@@ -0,0 +1,16 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SecurityDomains(Enum):
5
+ ENDPOINT = {"domain_name": "Endpoint", "domain_color": "#389CFF"}
6
+ NETWORK = {"domain_name": "Network", "domain_color": "#009933"}
7
+ WEB_APP = {"domain_name": "Web App", "domain_color": "#FF9933"}
8
+ EMAIL_INFILTRATION = {
9
+ "domain_name": "E-mail Infiltration",
10
+ "domain_color": "#FF6666",
11
+ }
12
+ DATA_EXFILTRATION = {"domain_name": "Data Exfiltration", "domain_color": "#9933CC"}
13
+ URL_FILTERING = {"domain_name": "URL Filtering", "domain_color": "#66CCFF"}
14
+ CLOUD = {"domain_name": "Cloud", "domain_color": "#9999CC"}
15
+ TABLE_TOP = {"domain_name": "Tabletop", "domain_color": "#FFCC33"}
16
+ TOCLASSIFY = {"domain_name": "To classify", "domain_color": "#FFFFFF"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyoaev
3
- Version: 2.0.14
3
+ Version: 2.1.0
4
4
  Summary: Python API client for OpenAEV.
5
5
  Author-email: Filigran <contact@filigran.io>
6
6
  Maintainer-email: Filigran <contact@filigran.io>
@@ -26,6 +26,7 @@ Requires-Dist: python-magic-bin<0.5,>=0.4.14; sys_platform == "win32"
26
26
  Requires-Dist: python_json_logger<3.4.0,>=3.3.0
27
27
  Requires-Dist: PyYAML<6.1,>=6.0
28
28
  Requires-Dist: pydantic<2.12.0,>=2.11.3
29
+ Requires-Dist: pydantic-settings<2.12.0,>=2.11.0
29
30
  Requires-Dist: requests<2.33.0,>=2.32.3
30
31
  Requires-Dist: setuptools<80.10.0,>=80.9.0
31
32
  Requires-Dist: cachetools<5.6.0,>=5.5.0
@@ -36,9 +37,9 @@ Requires-Dist: requests-toolbelt<1.1.0,>=1.0.0
36
37
  Requires-Dist: dataclasses-json<0.7.0,>=0.6.4
37
38
  Requires-Dist: thefuzz<0.23,>=0.22
38
39
  Provides-Extra: dev
39
- Requires-Dist: black<25.2.0,>=25.1.0; extra == "dev"
40
- Requires-Dist: build<1.3.0,>=1.2.1; extra == "dev"
41
- Requires-Dist: isort<6.1.0,>=6.0.0; extra == "dev"
40
+ Requires-Dist: black<25.12.0,>=25.11.0; extra == "dev"
41
+ Requires-Dist: build<1.4.0,>=1.3.0; extra == "dev"
42
+ Requires-Dist: isort<6.2.0,>=6.1.0; extra == "dev"
42
43
  Requires-Dist: types-pytz<2025.3.0.0,>=2025.2.0.20250326; extra == "dev"
43
44
  Requires-Dist: pre-commit<4.3.0,>=4.2.0; extra == "dev"
44
45
  Requires-Dist: types-python-dateutil<2.10.0,>=2.9.0; extra == "dev"
@@ -44,6 +44,8 @@ pyoaev/backends/backend.py
44
44
  pyoaev/backends/protocol.py
45
45
  pyoaev/configuration/__init__.py
46
46
  pyoaev/configuration/configuration.py
47
+ pyoaev/configuration/connector_config_schema_generator.py
48
+ pyoaev/configuration/settings_loader.py
47
49
  pyoaev/configuration/sources.py
48
50
  pyoaev/contracts/__init__.py
49
51
  pyoaev/contracts/contract_builder.py
@@ -53,6 +55,8 @@ pyoaev/contracts/variable_helper.py
53
55
  pyoaev/daemons/__init__.py
54
56
  pyoaev/daemons/base_daemon.py
55
57
  pyoaev/daemons/collector_daemon.py
58
+ pyoaev/security_domain/__init__.py
59
+ pyoaev/security_domain/types.py
56
60
  pyoaev/signatures/__init__.py
57
61
  pyoaev/signatures/signature_match.py
58
62
  pyoaev/signatures/signature_type.py
@@ -3,6 +3,7 @@ pika<1.4.0,>=1.3.0
3
3
  python_json_logger<3.4.0,>=3.3.0
4
4
  PyYAML<6.1,>=6.0
5
5
  pydantic<2.12.0,>=2.11.3
6
+ pydantic-settings<2.12.0,>=2.11.0
6
7
  requests<2.33.0,>=2.32.3
7
8
  setuptools<80.10.0,>=80.9.0
8
9
  cachetools<5.6.0,>=5.5.0
@@ -20,9 +21,9 @@ python-magic<0.5,>=0.4.27
20
21
  python-magic-bin<0.5,>=0.4.14
21
22
 
22
23
  [dev]
23
- black<25.2.0,>=25.1.0
24
- build<1.3.0,>=1.2.1
25
- isort<6.1.0,>=6.0.0
24
+ black<25.12.0,>=25.11.0
25
+ build<1.4.0,>=1.3.0
26
+ isort<6.2.0,>=6.1.0
26
27
  types-pytz<2025.3.0.0,>=2025.2.0.20250326
27
28
  pre-commit<4.3.0,>=4.2.0
28
29
  types-python-dateutil<2.10.0,>=2.9.0
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "python_json_logger (>=3.3.0,<3.4.0)",
33
33
  "PyYAML (>=6.0,<6.1)",
34
34
  "pydantic (>=2.11.3,<2.12.0)",
35
+ "pydantic-settings (>=2.11.0,<2.12.0)",
35
36
  "requests (>=2.32.3,<2.33.0)",
36
37
  "setuptools (>=80.9.0,<80.10.0)",
37
38
  "cachetools (>=5.5.0,<5.6.0)",
@@ -46,9 +47,9 @@ dependencies = [
46
47
 
47
48
  [project.optional-dependencies]
48
49
  dev = [
49
- "black (>=25.1.0,<25.2.0)",
50
- "build (>=1.2.1,<1.3.0)",
51
- "isort (>=6.0.0,<6.1.0)",
50
+ "black (>=25.11.0,<25.12.0)",
51
+ "build (>=1.3.0,<1.4.0)",
52
+ "isort (>=6.1.0,<6.2.0)",
52
53
  "types-pytz (>=2025.2.0.20250326,<2025.3.0.0)",
53
54
  "pre-commit (>=4.2.0,<4.3.0)",
54
55
  "types-python-dateutil (>=2.9.0,<2.10.0)",
File without changes
@@ -1,3 +0,0 @@
1
- from .configuration import Configuration
2
-
3
- __all__ = ["Configuration"]
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