splunk-soar-sdk 2.3.7__py3-none-any.whl → 3.1.0__py3-none-any.whl
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.
- soar_sdk/abstract.py +38 -41
- soar_sdk/action_results.py +41 -18
- soar_sdk/actions_manager.py +5 -7
- soar_sdk/apis/utils.py +3 -3
- soar_sdk/apis/vault.py +10 -10
- soar_sdk/app.py +58 -51
- soar_sdk/app_cli_runner.py +8 -8
- soar_sdk/app_client.py +10 -10
- soar_sdk/asset.py +45 -33
- soar_sdk/async_utils.py +2 -2
- soar_sdk/cli/init/cli.py +7 -9
- soar_sdk/cli/manifests/deserializers.py +15 -15
- soar_sdk/cli/manifests/processors.py +4 -10
- soar_sdk/cli/manifests/serializers.py +16 -8
- soar_sdk/cli/package/cli.py +6 -6
- soar_sdk/cli/package/utils.py +1 -1
- soar_sdk/code_renderers/action_renderer.py +35 -18
- soar_sdk/code_renderers/app_renderer.py +1 -2
- soar_sdk/code_renderers/asset_renderer.py +4 -5
- soar_sdk/code_renderers/renderer.py +2 -2
- soar_sdk/code_renderers/templates/pyproject.toml.jinja +1 -1
- soar_sdk/compat.py +6 -6
- soar_sdk/decorators/action.py +14 -15
- soar_sdk/decorators/make_request.py +4 -3
- soar_sdk/decorators/on_poll.py +5 -4
- soar_sdk/decorators/test_connectivity.py +2 -2
- soar_sdk/decorators/view_handler.py +11 -17
- soar_sdk/decorators/webhook.py +1 -2
- soar_sdk/exceptions.py +1 -4
- soar_sdk/field_utils.py +8 -0
- soar_sdk/input_spec.py +13 -17
- soar_sdk/logging.py +3 -3
- soar_sdk/meta/actions.py +6 -22
- soar_sdk/meta/app.py +10 -7
- soar_sdk/meta/dependencies.py +48 -42
- soar_sdk/meta/webhooks.py +12 -12
- soar_sdk/models/artifact.py +20 -23
- soar_sdk/models/container.py +30 -33
- soar_sdk/models/finding.py +54 -0
- soar_sdk/models/vault_attachment.py +6 -6
- soar_sdk/models/view.py +10 -13
- soar_sdk/params.py +57 -39
- soar_sdk/shims/phantom/action_result.py +4 -4
- soar_sdk/shims/phantom/base_connector.py +5 -5
- soar_sdk/shims/phantom/ph_ipc.py +3 -3
- soar_sdk/shims/phantom/vault.py +35 -34
- soar_sdk/types.py +3 -2
- soar_sdk/views/template_filters.py +4 -4
- soar_sdk/views/template_renderer.py +2 -2
- soar_sdk/views/view_parser.py +3 -4
- soar_sdk/webhooks/models.py +7 -6
- soar_sdk/webhooks/routing.py +4 -3
- {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/METADATA +5 -6
- splunk_soar_sdk-3.1.0.dist-info/RECORD +105 -0
- splunk_soar_sdk-2.3.7.dist-info/RECORD +0 -103
- {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/WHEEL +0 -0
- {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/entry_points.txt +0 -0
- {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
from typing import ClassVar
|
|
1
|
+
from typing import ClassVar
|
|
2
2
|
from collections.abc import Iterator
|
|
3
3
|
import typing
|
|
4
4
|
import ast
|
|
5
|
-
from
|
|
5
|
+
from pydantic_core import PydanticUndefined
|
|
6
6
|
|
|
7
7
|
from soar_sdk.action_results import ActionOutput
|
|
8
8
|
from soar_sdk.cli.utils import normalize_field_name
|
|
9
9
|
from soar_sdk.code_renderers.renderer import AstRenderer
|
|
10
10
|
from soar_sdk.meta.actions import ActionMeta
|
|
11
11
|
from soar_sdk.params import Params
|
|
12
|
+
from soar_sdk.field_utils import parse_json_schema_extra
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class ActionRenderer(AstRenderer[ActionMeta]):
|
|
@@ -201,7 +202,7 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
201
202
|
yield ast.fix_missing_locations(node)
|
|
202
203
|
|
|
203
204
|
def render_outputs_ast(
|
|
204
|
-
self, model:
|
|
205
|
+
self, model: type[ActionOutput] | None = None
|
|
205
206
|
) -> Iterator[ast.ClassDef]:
|
|
206
207
|
"""Generates the AST for the action outputs.
|
|
207
208
|
|
|
@@ -221,16 +222,24 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
221
222
|
|
|
222
223
|
field_defs: list[ast.stmt] = []
|
|
223
224
|
|
|
224
|
-
for field_name_str, field in model.
|
|
225
|
+
for field_name_str, field in model.model_fields.items():
|
|
225
226
|
annotation = field.annotation
|
|
227
|
+
if annotation is None:
|
|
228
|
+
continue
|
|
229
|
+
|
|
226
230
|
annotation_str = "{name}"
|
|
227
231
|
while typing.get_origin(annotation) is list:
|
|
228
232
|
annotation_str = f"list[{annotation_str}]"
|
|
229
233
|
annotation = typing.get_args(annotation)[0]
|
|
234
|
+
|
|
235
|
+
# Ensure annotation is a valid type after unwrapping
|
|
236
|
+
if not isinstance(annotation, type):
|
|
237
|
+
continue
|
|
238
|
+
|
|
230
239
|
annotation_str = annotation_str.format(name=annotation.__name__)
|
|
231
240
|
|
|
232
241
|
field_name = normalize_field_name(field_name_str)
|
|
233
|
-
if field.alias != field_name.normalized:
|
|
242
|
+
if field.alias is not None and field.alias != field_name.normalized:
|
|
234
243
|
field_name.original = field.alias
|
|
235
244
|
field_name.modified = True
|
|
236
245
|
|
|
@@ -240,9 +249,10 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
240
249
|
simple=1,
|
|
241
250
|
)
|
|
242
251
|
|
|
243
|
-
if issubclass(annotation, ActionOutput):
|
|
252
|
+
if isinstance(annotation, type) and issubclass(annotation, ActionOutput):
|
|
244
253
|
# If the field is a Pydantic model, recursively print its fields
|
|
245
|
-
|
|
254
|
+
# In Pydantic v2, use annotation directly (no field.type_)
|
|
255
|
+
for model_ast in self.render_outputs_ast(annotation):
|
|
246
256
|
model_tree[model_ast.name] = model_ast
|
|
247
257
|
|
|
248
258
|
if field_name.modified:
|
|
@@ -258,7 +268,9 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
258
268
|
)
|
|
259
269
|
else:
|
|
260
270
|
keywords = []
|
|
261
|
-
|
|
271
|
+
extras = {**parse_json_schema_extra(field.json_schema_extra)}
|
|
272
|
+
|
|
273
|
+
if extras or field_name.modified:
|
|
262
274
|
extras["example_values"] = extras.pop("examples", None)
|
|
263
275
|
if extras["example_values"] == [True, False]:
|
|
264
276
|
extras["example_values"] = None
|
|
@@ -315,7 +327,10 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
315
327
|
keywords=[],
|
|
316
328
|
)
|
|
317
329
|
|
|
318
|
-
for field_name, field_def in self.action_meta.parameters.
|
|
330
|
+
for field_name, field_def in self.action_meta.parameters.model_fields.items():
|
|
331
|
+
if field_def.annotation is None:
|
|
332
|
+
continue
|
|
333
|
+
|
|
319
334
|
field_type = ast.Name(id=field_def.annotation.__name__, ctx=ast.Load())
|
|
320
335
|
|
|
321
336
|
param = ast.Call(
|
|
@@ -324,29 +339,31 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
324
339
|
keywords=[],
|
|
325
340
|
)
|
|
326
341
|
|
|
327
|
-
|
|
342
|
+
json_schema_extra = parse_json_schema_extra(field_def.json_schema_extra)
|
|
343
|
+
|
|
344
|
+
if field_def.description:
|
|
328
345
|
param.keywords.append(
|
|
329
346
|
ast.keyword(
|
|
330
347
|
arg="description",
|
|
331
|
-
value=ast.Constant(value=field_def.
|
|
348
|
+
value=ast.Constant(value=field_def.description),
|
|
332
349
|
)
|
|
333
350
|
)
|
|
334
|
-
if not
|
|
351
|
+
if not json_schema_extra.get("required", True):
|
|
335
352
|
param.keywords.append(
|
|
336
353
|
ast.keyword(arg="required", value=ast.Constant(value=False))
|
|
337
354
|
)
|
|
338
|
-
if
|
|
355
|
+
if json_schema_extra.get("primary", False):
|
|
339
356
|
param.keywords.append(
|
|
340
357
|
ast.keyword(arg="primary", value=ast.Constant(value=True))
|
|
341
358
|
)
|
|
342
|
-
if
|
|
359
|
+
if field_def.default not in (PydanticUndefined, None):
|
|
343
360
|
param.keywords.append(
|
|
344
361
|
ast.keyword(
|
|
345
362
|
arg="default",
|
|
346
|
-
value=ast.Constant(value=field_def.
|
|
363
|
+
value=ast.Constant(value=field_def.default),
|
|
347
364
|
)
|
|
348
365
|
)
|
|
349
|
-
if value_list :=
|
|
366
|
+
if value_list := json_schema_extra.get("value_list"):
|
|
350
367
|
param.keywords.append(
|
|
351
368
|
ast.keyword(
|
|
352
369
|
arg="value_list",
|
|
@@ -356,7 +373,7 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
356
373
|
),
|
|
357
374
|
)
|
|
358
375
|
)
|
|
359
|
-
if cef_types :=
|
|
376
|
+
if cef_types := json_schema_extra.get("cef_types"):
|
|
360
377
|
param.keywords.append(
|
|
361
378
|
ast.keyword(
|
|
362
379
|
arg="cef_types",
|
|
@@ -366,7 +383,7 @@ class ActionRenderer(AstRenderer[ActionMeta]):
|
|
|
366
383
|
),
|
|
367
384
|
)
|
|
368
385
|
)
|
|
369
|
-
if
|
|
386
|
+
if json_schema_extra.get("allow_list", False):
|
|
370
387
|
param.keywords.append(
|
|
371
388
|
ast.keyword(arg="allow_list", value=ast.Constant(value=True))
|
|
372
389
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import ast
|
|
3
|
-
from typing import Union
|
|
4
3
|
from collections.abc import Iterator
|
|
5
4
|
|
|
6
5
|
|
|
@@ -50,7 +49,7 @@ class AppRenderer:
|
|
|
50
49
|
self.context = context
|
|
51
50
|
|
|
52
51
|
@staticmethod
|
|
53
|
-
def create_default_imports() -> Iterator[
|
|
52
|
+
def create_default_imports() -> Iterator[ast.Import | ast.ImportFrom]:
|
|
54
53
|
"""Create default imports for the App module.
|
|
55
54
|
|
|
56
55
|
Returns:
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
from typing import Optional, Union
|
|
3
2
|
import ast
|
|
4
3
|
from collections.abc import Iterator
|
|
5
4
|
|
|
@@ -12,12 +11,12 @@ class AssetContext:
|
|
|
12
11
|
"""Context for rendering individual configuration keys of an Asset class."""
|
|
13
12
|
|
|
14
13
|
name: str
|
|
15
|
-
description:
|
|
14
|
+
description: str | None
|
|
16
15
|
required: bool
|
|
17
|
-
default:
|
|
16
|
+
default: str | int | float | bool | None
|
|
18
17
|
data_type: str
|
|
19
|
-
value_list:
|
|
20
|
-
alias:
|
|
18
|
+
value_list: list[str] | None
|
|
19
|
+
alias: str | None = None
|
|
21
20
|
|
|
22
21
|
@property
|
|
23
22
|
def is_str(self) -> bool:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
-
from typing import TypeVar, Generic
|
|
2
|
+
from typing import TypeVar, Generic
|
|
3
3
|
import jinja2 as j2
|
|
4
4
|
import ast
|
|
5
5
|
from collections.abc import Iterator
|
|
@@ -12,7 +12,7 @@ class Renderer(Generic[ContextT], abc.ABC):
|
|
|
12
12
|
"""Abstract base class for rendering code using Jinja2 templates."""
|
|
13
13
|
|
|
14
14
|
def __init__(
|
|
15
|
-
self, context: ContextT, jinja_env:
|
|
15
|
+
self, context: ContextT, jinja_env: j2.Environment | None = None
|
|
16
16
|
) -> None:
|
|
17
17
|
self.context = context
|
|
18
18
|
self.jinja_env = jinja_env or j2.Environment(
|
soar_sdk/compat.py
CHANGED
|
@@ -2,7 +2,7 @@ from enum import Enum
|
|
|
2
2
|
import functools
|
|
3
3
|
from packaging.version import Version
|
|
4
4
|
|
|
5
|
-
MIN_PHANTOM_VERSION = "
|
|
5
|
+
MIN_PHANTOM_VERSION = "7.0.0"
|
|
6
6
|
|
|
7
7
|
UPDATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
8
8
|
|
|
@@ -27,8 +27,8 @@ def remove_when_soar_newer_than(
|
|
|
27
27
|
class PythonVersion(str, Enum):
|
|
28
28
|
"""Enum to represent supported Python versions."""
|
|
29
29
|
|
|
30
|
-
PY_3_9 = "3.9"
|
|
31
30
|
PY_3_13 = "3.13"
|
|
31
|
+
PY_3_14 = "3.14"
|
|
32
32
|
|
|
33
33
|
def __str__(self) -> str:
|
|
34
34
|
"""Returns the string representation of the Python version."""
|
|
@@ -41,10 +41,10 @@ class PythonVersion(str, Enum):
|
|
|
41
41
|
Raises ValueError if the version is not supported.
|
|
42
42
|
"""
|
|
43
43
|
# "3" is a special case for connectors that don't properly define their Python version
|
|
44
|
-
if version_str in ("3", "3.
|
|
45
|
-
return cls.PY_3_9
|
|
46
|
-
if version_str == "3.13":
|
|
44
|
+
if version_str in ("3", "3.13"):
|
|
47
45
|
return cls.PY_3_13
|
|
46
|
+
if version_str == "3.14":
|
|
47
|
+
return cls.PY_3_14
|
|
48
48
|
|
|
49
49
|
raise ValueError(f"Unsupported Python version: {version_str}")
|
|
50
50
|
|
|
@@ -67,7 +67,7 @@ class PythonVersion(str, Enum):
|
|
|
67
67
|
@classmethod
|
|
68
68
|
def all(cls) -> list["PythonVersion"]:
|
|
69
69
|
"""Returns a list of all supported Python versions."""
|
|
70
|
-
return [cls.
|
|
70
|
+
return [cls.PY_3_13, cls.PY_3_14]
|
|
71
71
|
|
|
72
72
|
@classmethod
|
|
73
73
|
def all_csv(cls) -> str:
|
soar_sdk/decorators/action.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
3
|
from collections.abc import Iterator
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, get_args, get_origin
|
|
5
|
+
from collections.abc import Callable
|
|
5
6
|
from collections.abc import AsyncGenerator
|
|
6
7
|
|
|
7
8
|
from soar_sdk.abstract import SOARClient
|
|
@@ -25,24 +26,22 @@ class ActionDecorator:
|
|
|
25
26
|
def __init__(
|
|
26
27
|
self,
|
|
27
28
|
app: "App",
|
|
28
|
-
name:
|
|
29
|
-
identifier:
|
|
30
|
-
description:
|
|
29
|
+
name: str | None = None,
|
|
30
|
+
identifier: str | None = None,
|
|
31
|
+
description: str | None = None,
|
|
31
32
|
verbose: str = "",
|
|
32
33
|
action_type: str = "generic",
|
|
33
34
|
read_only: bool = True,
|
|
34
|
-
params_class:
|
|
35
|
-
output_class:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
render_as: Optional[str] = None,
|
|
43
|
-
view_handler: Optional[Callable] = None,
|
|
35
|
+
params_class: type[Params] | None = None,
|
|
36
|
+
output_class: None
|
|
37
|
+
| type[ActionOutput]
|
|
38
|
+
| Iterator[type[ActionOutput]]
|
|
39
|
+
| AsyncGenerator[type[ActionOutput]]
|
|
40
|
+
| list[type[ActionOutput]] = None,
|
|
41
|
+
render_as: str | None = None,
|
|
42
|
+
view_handler: Callable | None = None,
|
|
44
43
|
versions: str = "EQ(*)",
|
|
45
|
-
summary_type:
|
|
44
|
+
summary_type: type[ActionOutput] | None = None,
|
|
46
45
|
enable_concurrency_lock: bool = False,
|
|
47
46
|
) -> None:
|
|
48
47
|
self.app = app
|
|
@@ -12,7 +12,8 @@ from soar_sdk.logging import getLogger
|
|
|
12
12
|
from functools import wraps
|
|
13
13
|
import traceback
|
|
14
14
|
|
|
15
|
-
from typing import TYPE_CHECKING,
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
from collections.abc import Callable
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from soar_sdk.app import App
|
|
@@ -24,7 +25,7 @@ class MakeRequestDecorator:
|
|
|
24
25
|
def __init__(
|
|
25
26
|
self,
|
|
26
27
|
app: "App",
|
|
27
|
-
output_class:
|
|
28
|
+
output_class: type[ActionOutput] | None = None,
|
|
28
29
|
) -> None:
|
|
29
30
|
self.app = app
|
|
30
31
|
self.output_class = output_class
|
|
@@ -97,7 +98,7 @@ class MakeRequestDecorator:
|
|
|
97
98
|
**kwargs: Any, # noqa: ANN401
|
|
98
99
|
) -> bool:
|
|
99
100
|
try:
|
|
100
|
-
action_params = validated_params_class.
|
|
101
|
+
action_params = validated_params_class.model_validate(params)
|
|
101
102
|
except Exception as e:
|
|
102
103
|
logger.info(f"Parameter validation error: {e!s}")
|
|
103
104
|
return self.app._adapt_action_result(
|
soar_sdk/decorators/on_poll.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from collections.abc import Iterator
|
|
5
6
|
|
|
6
7
|
from soar_sdk.abstract import SOARClient
|
|
@@ -80,7 +81,7 @@ class OnPollDecorator:
|
|
|
80
81
|
try:
|
|
81
82
|
# Validate poll params
|
|
82
83
|
try:
|
|
83
|
-
action_params = validated_params_class.
|
|
84
|
+
action_params = validated_params_class.model_validate(params)
|
|
84
85
|
except Exception as e:
|
|
85
86
|
logger.info(f"Parameter validation error: {e!s}")
|
|
86
87
|
return self.app._adapt_action_result(
|
|
@@ -177,8 +178,8 @@ class OnPollDecorator:
|
|
|
177
178
|
|
|
178
179
|
# Custom ActionMeta class for on_poll (has no output)
|
|
179
180
|
class OnPollActionMeta(ActionMeta):
|
|
180
|
-
def
|
|
181
|
-
data = super().
|
|
181
|
+
def model_dump(self, *args: object, **kwargs: object) -> dict[str, Any]:
|
|
182
|
+
data = super().model_dump(*args, **kwargs)
|
|
182
183
|
# Poll actions have no output
|
|
183
184
|
data["output"] = []
|
|
184
185
|
return data
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Callable
|
|
4
4
|
|
|
5
5
|
from soar_sdk.abstract import SOARClient
|
|
6
6
|
from soar_sdk.action_results import ActionResult
|
|
@@ -48,7 +48,7 @@ class ConnectivityTestDecorator:
|
|
|
48
48
|
@action_protocol
|
|
49
49
|
@wraps(function)
|
|
50
50
|
def inner(
|
|
51
|
-
_param:
|
|
51
|
+
_param: dict | None = None,
|
|
52
52
|
soar: SOARClient = self.app.soar_client,
|
|
53
53
|
) -> bool:
|
|
54
54
|
kwargs = self.app._build_magic_args(function, soar=soar)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
|
|
5
6
|
from soar_sdk.action_results import ActionResult
|
|
6
7
|
from soar_sdk.views.component_registry import COMPONENT_REGISTRY
|
|
@@ -10,9 +11,7 @@ from soar_sdk.views.view_parser import ViewFunctionParser
|
|
|
10
11
|
from soar_sdk.views.template_renderer import (
|
|
11
12
|
get_template_renderer,
|
|
12
13
|
get_templates_dir,
|
|
13
|
-
BASE_TEMPLATE_PATH,
|
|
14
14
|
)
|
|
15
|
-
from soar_sdk.compat import remove_when_soar_newer_than
|
|
16
15
|
|
|
17
16
|
from typing import TYPE_CHECKING
|
|
18
17
|
|
|
@@ -23,15 +22,15 @@ if TYPE_CHECKING:
|
|
|
23
22
|
class ViewHandlerDecorator:
|
|
24
23
|
"""Class-based decorator for view handler functionality."""
|
|
25
24
|
|
|
26
|
-
def __init__(self, app: "App", *, template:
|
|
25
|
+
def __init__(self, app: "App", *, template: str | None = None) -> None:
|
|
27
26
|
self.app = app
|
|
28
27
|
self.template = template
|
|
29
28
|
|
|
30
29
|
@staticmethod
|
|
31
30
|
def _validate_view_function_signature(
|
|
32
31
|
function: Callable,
|
|
33
|
-
template:
|
|
34
|
-
component_type:
|
|
32
|
+
template: str | None = None,
|
|
33
|
+
component_type: str | None = None,
|
|
35
34
|
) -> None:
|
|
36
35
|
"""Validate that the function signature is compatible with view handlers."""
|
|
37
36
|
signature = inspect.signature(function)
|
|
@@ -92,14 +91,9 @@ class ViewHandlerDecorator:
|
|
|
92
91
|
**kwargs: Any, # noqa: ANN401
|
|
93
92
|
) -> str:
|
|
94
93
|
def handle_html_output(html: str) -> str:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if context.get("accepts_prerender"):
|
|
99
|
-
context["prerender"] = True
|
|
100
|
-
return html
|
|
101
|
-
context["html_content"] = html
|
|
102
|
-
return BASE_TEMPLATE_PATH
|
|
94
|
+
# SOAR 7.0+ fully supports prerendering
|
|
95
|
+
context["prerender"] = True
|
|
96
|
+
return html
|
|
103
97
|
|
|
104
98
|
def render_with_error_handling(
|
|
105
99
|
render_func: Callable[[], str], error_type: str, target_name: str
|
|
@@ -121,12 +115,12 @@ class ViewHandlerDecorator:
|
|
|
121
115
|
parser: ViewFunctionParser = ViewFunctionParser(function)
|
|
122
116
|
|
|
123
117
|
# Parse context to ViewContext (coming from app_interface)
|
|
124
|
-
parsed_context = ViewContext.
|
|
118
|
+
parsed_context = ViewContext.model_validate(context)
|
|
125
119
|
|
|
126
120
|
# Parse all_app_runs to AllAppRuns (coming from app_interface)
|
|
127
121
|
parsed_all_app_runs: AllAppRuns = []
|
|
128
122
|
for app_run_data, action_results in all_app_runs:
|
|
129
|
-
result_summary = ResultSummary.
|
|
123
|
+
result_summary = ResultSummary.model_validate(app_run_data)
|
|
130
124
|
parsed_all_app_runs.append((result_summary, action_results))
|
|
131
125
|
|
|
132
126
|
result = parser.execute(
|
|
@@ -158,7 +152,7 @@ class ViewHandlerDecorator:
|
|
|
158
152
|
|
|
159
153
|
# Reusable component
|
|
160
154
|
if isinstance(result, BaseModel):
|
|
161
|
-
result_dict = result.
|
|
155
|
+
result_dict = result.model_dump()
|
|
162
156
|
template_name = f"components/{component_type}.html"
|
|
163
157
|
err_msg = "Component Rendering Failed"
|
|
164
158
|
err_context = f"component '{component_type}'"
|
soar_sdk/decorators/webhook.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from typing import Optional
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
from soar_sdk.cli.path_utils import relative_to_cwd
|
|
@@ -19,7 +18,7 @@ class WebhookDecorator:
|
|
|
19
18
|
"""Class-based decorator for webhook functionality."""
|
|
20
19
|
|
|
21
20
|
def __init__(
|
|
22
|
-
self, app: "App", url_pattern: str, allowed_methods:
|
|
21
|
+
self, app: "App", url_pattern: str, allowed_methods: list[str] | None = None
|
|
23
22
|
) -> None:
|
|
24
23
|
self.app = app
|
|
25
24
|
self.url_pattern = url_pattern
|
soar_sdk/exceptions.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
class ActionFailure(Exception):
|
|
5
2
|
"""Exception raised when an action fails to execute successfully."""
|
|
6
3
|
|
|
7
|
-
def __init__(self, message: str, action_name:
|
|
4
|
+
def __init__(self, message: str, action_name: str | None = None) -> None:
|
|
8
5
|
self.message = message
|
|
9
6
|
self.action_name = action_name
|
|
10
7
|
super().__init__(self.message)
|
soar_sdk/field_utils.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_json_schema_extra(json_schema_extra: Any) -> dict[str, Any]: # noqa: ANN401
|
|
5
|
+
"""Extract json_schema_extra as a dict, handling both dict and callable forms."""
|
|
6
|
+
if callable(json_schema_extra):
|
|
7
|
+
return {}
|
|
8
|
+
return json_schema_extra or {}
|
soar_sdk/input_spec.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from uuid import uuid4
|
|
2
|
-
from pydantic import BaseModel, Field,
|
|
3
|
-
from typing import Literal,
|
|
2
|
+
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
3
|
+
from typing import Literal, Any
|
|
4
4
|
import random
|
|
5
5
|
|
|
6
6
|
|
|
@@ -20,20 +20,18 @@ class AppConfig(BaseModel):
|
|
|
20
20
|
|
|
21
21
|
app_version: str
|
|
22
22
|
directory: str
|
|
23
|
-
ingest:
|
|
23
|
+
ingest: IngestConfig | None = None
|
|
24
24
|
main_module: str
|
|
25
25
|
# TODO: The platform should deprecate this unused field
|
|
26
26
|
appname: Literal["-"] = "-"
|
|
27
27
|
|
|
28
28
|
# NOTE: Inputs will intermix the keys of the asset config with the keys here
|
|
29
|
-
|
|
30
|
-
"""Configuration for the AppConfig model."""
|
|
31
|
-
|
|
32
|
-
extra = "allow"
|
|
29
|
+
model_config = ConfigDict(extra="allow")
|
|
33
30
|
|
|
34
31
|
def get_asset_config(self) -> dict[str, Any]:
|
|
35
32
|
"""Get the asset configuration from the app config."""
|
|
36
|
-
|
|
33
|
+
# In Pydantic v2 extra fields are stored in __pydantic_extra__
|
|
34
|
+
return dict(self.__pydantic_extra__) if self.__pydantic_extra__ else {}
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
class EnvironmentVariable(BaseModel):
|
|
@@ -65,10 +63,7 @@ class ActionParameter(BaseModel):
|
|
|
65
63
|
# context: Optional[ParameterContext] = None # noqa: ERA001
|
|
66
64
|
|
|
67
65
|
# Additional keys are action-specific and not predictable here.
|
|
68
|
-
|
|
69
|
-
"""Configuration for the ActionParameter model."""
|
|
70
|
-
|
|
71
|
-
extra = "allow"
|
|
66
|
+
model_config = ConfigDict(extra="allow")
|
|
72
67
|
|
|
73
68
|
|
|
74
69
|
class SoarAuth(BaseModel):
|
|
@@ -78,7 +73,8 @@ class SoarAuth(BaseModel):
|
|
|
78
73
|
username: str
|
|
79
74
|
password: str
|
|
80
75
|
|
|
81
|
-
@
|
|
76
|
+
@field_validator("phantom_url")
|
|
77
|
+
@classmethod
|
|
82
78
|
def validate_phantom_url(cls, value: str) -> str:
|
|
83
79
|
"""Ensure the URL starts with http:// or https://."""
|
|
84
80
|
return (
|
|
@@ -137,9 +133,9 @@ class InputSpecification(BaseModel):
|
|
|
137
133
|
}
|
|
138
134
|
"""
|
|
139
135
|
|
|
140
|
-
action:
|
|
136
|
+
action: str | None = None
|
|
141
137
|
action_run_id: int = Field(default_factory=id_factory)
|
|
142
|
-
app_config:
|
|
138
|
+
app_config: Any | None = None
|
|
143
139
|
asset_id: str = Field(default_factory=lambda: str(id_factory()))
|
|
144
140
|
config: AppConfig
|
|
145
141
|
connector_run_id: int = Field(default_factory=id_factory)
|
|
@@ -148,6 +144,6 @@ class InputSpecification(BaseModel):
|
|
|
148
144
|
dec_key: str = Field(default_factory=lambda: str(id_factory()))
|
|
149
145
|
environment_variables: dict[str, EnvironmentVariable] = Field(default_factory=dict)
|
|
150
146
|
identifier: str
|
|
151
|
-
parameters: list[ActionParameter] = Field(default_factory=
|
|
147
|
+
parameters: list[ActionParameter] = Field(default_factory=list)
|
|
152
148
|
user_session_token: str = ""
|
|
153
|
-
soar_auth:
|
|
149
|
+
soar_auth: SoarAuth | None = None
|
soar_sdk/logging.py
CHANGED
|
@@ -4,7 +4,7 @@ from soar_sdk.colors import ANSIColor
|
|
|
4
4
|
from soar_sdk.shims.phantom.install_info import is_soar_available, get_product_version
|
|
5
5
|
from soar_sdk.shims.phantom.ph_ipc import ph_ipc
|
|
6
6
|
from packaging.version import Version
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
from soar_sdk.compat import remove_when_soar_newer_than
|
|
9
9
|
|
|
10
10
|
PROGRESS_LEVEL = 25
|
|
@@ -38,7 +38,7 @@ class SOARHandler(logging.Handler):
|
|
|
38
38
|
self,
|
|
39
39
|
) -> None:
|
|
40
40
|
super().__init__()
|
|
41
|
-
self.__handle:
|
|
41
|
+
self.__handle: int | None = None
|
|
42
42
|
|
|
43
43
|
def emit(self, record: logging.LogRecord) -> None:
|
|
44
44
|
is_new_soar = Version(get_product_version()) >= Version("7.0.0")
|
|
@@ -79,7 +79,7 @@ class SOARHandler(logging.Handler):
|
|
|
79
79
|
except Exception:
|
|
80
80
|
self.handleError(record)
|
|
81
81
|
|
|
82
|
-
def set_handle(self, handle:
|
|
82
|
+
def set_handle(self, handle: int | None) -> None:
|
|
83
83
|
"""Set the action handle for the SOAR client."""
|
|
84
84
|
self.__handle = handle
|
|
85
85
|
|
soar_sdk/meta/actions.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
from typing import Any, Type,
|
|
1
|
+
from typing import Any, Type, Callable # noqa: UP035
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
5
|
from soar_sdk.cli.manifests.serializers import ParamsSerializer, OutputsSerializer
|
|
6
|
-
from soar_sdk.compat import remove_when_soar_newer_than
|
|
7
6
|
from soar_sdk.params import Params
|
|
8
7
|
from soar_sdk.action_results import ActionOutput
|
|
9
8
|
|
|
@@ -20,14 +19,14 @@ class ActionMeta(BaseModel):
|
|
|
20
19
|
verbose: str = ""
|
|
21
20
|
parameters: Type[Params] = Field(default=Params) # noqa: UP006
|
|
22
21
|
output: Type[ActionOutput] = Field(default=ActionOutput) # noqa: UP006
|
|
23
|
-
render_as:
|
|
24
|
-
view_handler:
|
|
25
|
-
summary_type:
|
|
22
|
+
render_as: str | None = None
|
|
23
|
+
view_handler: Callable | None = None
|
|
24
|
+
summary_type: Type[ActionOutput] | None = Field(default=None, exclude=True) # noqa: UP006
|
|
26
25
|
enable_concurrency_lock: bool = False
|
|
27
26
|
|
|
28
|
-
def
|
|
27
|
+
def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]: # noqa: ANN401
|
|
29
28
|
"""Serializes the action metadata to a dictionary."""
|
|
30
|
-
data = super().
|
|
29
|
+
data = super().model_dump(*args, **kwargs)
|
|
31
30
|
data["parameters"] = ParamsSerializer.serialize_fields_info(self.parameters)
|
|
32
31
|
data["output"] = OutputsSerializer.serialize_datapaths(
|
|
33
32
|
self.parameters, self.output, summary_class=self.summary_type
|
|
@@ -40,21 +39,6 @@ class ActionMeta(BaseModel):
|
|
|
40
39
|
"type": self.render_as,
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
if self.view_handler:
|
|
44
|
-
remove_when_soar_newer_than("6.4.1")
|
|
45
|
-
# Get the module path and function name for the view
|
|
46
|
-
module = self.view_handler.__module__
|
|
47
|
-
# Convert module path from dot notation to the expected format
|
|
48
|
-
# e.g., "example_app.src.app" -> "src.app"
|
|
49
|
-
module_parts = module.split(".")
|
|
50
|
-
if len(module_parts) > 1:
|
|
51
|
-
# Remove the package name (first part) to get relative module path
|
|
52
|
-
relative_module = ".".join(module_parts[1:])
|
|
53
|
-
else:
|
|
54
|
-
relative_module = module
|
|
55
|
-
|
|
56
|
-
data["render"]["view"] = f"{relative_module}.{self.view_handler.__name__}"
|
|
57
|
-
|
|
58
42
|
# Remove view_handler from the output since in render
|
|
59
43
|
data.pop("view_handler", None)
|
|
60
44
|
data.pop("render_as", None)
|