splunk-soar-sdk 3.6.0__py3-none-any.whl → 3.7.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/actions_manager.py +40 -0
- soar_sdk/app.py +52 -4
- soar_sdk/asset.py +49 -69
- soar_sdk/asset_state.py +13 -3
- soar_sdk/auth/__init__.py +41 -0
- soar_sdk/auth/client.py +540 -0
- soar_sdk/auth/factories.py +120 -0
- soar_sdk/auth/flows.py +172 -0
- soar_sdk/auth/httpx_auth.py +97 -0
- soar_sdk/auth/models.py +101 -0
- soar_sdk/cli/package/cli.py +35 -17
- soar_sdk/meta/dependencies.py +28 -3
- soar_sdk/shims/phantom/base_connector.py +7 -0
- {splunk_soar_sdk-3.6.0.dist-info → splunk_soar_sdk-3.7.0.dist-info}/METADATA +3 -1
- {splunk_soar_sdk-3.6.0.dist-info → splunk_soar_sdk-3.7.0.dist-info}/RECORD +18 -12
- {splunk_soar_sdk-3.6.0.dist-info → splunk_soar_sdk-3.7.0.dist-info}/WHEEL +0 -0
- {splunk_soar_sdk-3.6.0.dist-info → splunk_soar_sdk-3.7.0.dist-info}/entry_points.txt +0 -0
- {splunk_soar_sdk-3.6.0.dist-info → splunk_soar_sdk-3.7.0.dist-info}/licenses/LICENSE +0 -0
soar_sdk/actions_manager.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
2
5
|
from pathlib import Path
|
|
3
6
|
from typing import Any
|
|
4
7
|
|
|
@@ -135,3 +138,40 @@ class ActionsManager(BaseConnector):
|
|
|
135
138
|
This is useful for contexts such as Webhooks, where the app dir isn't necessarily the cwd, but we still need to load the app JSON reliably.
|
|
136
139
|
"""
|
|
137
140
|
self.__app_dir = app_dir
|
|
141
|
+
|
|
142
|
+
def _get_state_file_path(self, asset_id: str) -> Path:
|
|
143
|
+
"""Get the state file path for an asset."""
|
|
144
|
+
return Path(self.get_state_dir()) / f"{asset_id}_state.json"
|
|
145
|
+
|
|
146
|
+
def load_state_from_file(self, asset_id: str) -> dict:
|
|
147
|
+
"""Load state directly from file."""
|
|
148
|
+
state_file = self._get_state_file_path(asset_id)
|
|
149
|
+
if state_file.exists():
|
|
150
|
+
return json.loads(state_file.read_text())
|
|
151
|
+
return {}
|
|
152
|
+
|
|
153
|
+
def save_state_to_file(self, asset_id: str, state: dict) -> None:
|
|
154
|
+
"""Save state directly to file using atomic write."""
|
|
155
|
+
state_file = self._get_state_file_path(asset_id)
|
|
156
|
+
state_file.parent.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
|
|
158
|
+
fd, tmp_path = tempfile.mkstemp(dir=state_file.parent)
|
|
159
|
+
tmp_file = Path(tmp_path)
|
|
160
|
+
try:
|
|
161
|
+
with os.fdopen(fd, "w") as f:
|
|
162
|
+
f.write(json.dumps(state))
|
|
163
|
+
shutil.move(tmp_file, state_file)
|
|
164
|
+
except Exception:
|
|
165
|
+
if tmp_file.exists():
|
|
166
|
+
tmp_file.unlink()
|
|
167
|
+
raise
|
|
168
|
+
|
|
169
|
+
def reload_state_from_file(self, asset_id: str) -> dict:
|
|
170
|
+
"""Reload state from file and update in-memory state.
|
|
171
|
+
|
|
172
|
+
Needed for OAuth flow where one process (webhook) updates the state file and another process (action) needs to see those changes.
|
|
173
|
+
"""
|
|
174
|
+
state = self.load_state_from_file(asset_id)
|
|
175
|
+
if state:
|
|
176
|
+
self.save_state(state)
|
|
177
|
+
return state
|
soar_sdk/app.py
CHANGED
|
@@ -6,6 +6,7 @@ import uuid
|
|
|
6
6
|
from collections.abc import Callable, Iterator
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
9
10
|
from zoneinfo import ZoneInfo
|
|
10
11
|
|
|
11
12
|
from soar_sdk.abstract import SOARClient, SOARClientAuth
|
|
@@ -226,12 +227,16 @@ class App:
|
|
|
226
227
|
self._asset = self.asset_cls.model_validate(self._raw_asset_config)
|
|
227
228
|
|
|
228
229
|
asset_id = self.soar_client.get_asset_id()
|
|
229
|
-
|
|
230
|
+
app_id = str(self.app_meta_info["appid"])
|
|
231
|
+
|
|
232
|
+
self._asset._auth_state = AssetState(
|
|
233
|
+
self.actions_manager, "auth", asset_id, app_id=app_id
|
|
234
|
+
)
|
|
230
235
|
self._asset._cache_state = AssetState(
|
|
231
|
-
self.actions_manager, "cache", asset_id
|
|
236
|
+
self.actions_manager, "cache", asset_id, app_id=app_id
|
|
232
237
|
)
|
|
233
238
|
self._asset._ingest_state = AssetState(
|
|
234
|
-
self.actions_manager, "ingest", asset_id
|
|
239
|
+
self.actions_manager, "ingest", asset_id, app_id=app_id
|
|
235
240
|
)
|
|
236
241
|
return self._asset
|
|
237
242
|
|
|
@@ -789,6 +794,47 @@ class App:
|
|
|
789
794
|
"""Decorator for registering a webhook handler."""
|
|
790
795
|
return WebhookDecorator(self, url_pattern, allowed_methods)
|
|
791
796
|
|
|
797
|
+
def get_webhook_url(self, route: str) -> str:
|
|
798
|
+
"""Build the full URL for a webhook route (used for OAuth flow)."""
|
|
799
|
+
system_info = self.soar_client.get("rest/system_info").json()
|
|
800
|
+
base_url = system_info.get("base_url", "").rstrip("/")
|
|
801
|
+
parsed = urlparse(base_url)
|
|
802
|
+
|
|
803
|
+
webhook_port = self._get_webhook_port()
|
|
804
|
+
webhook_base = f"{parsed.scheme}://{parsed.hostname}:{webhook_port}"
|
|
805
|
+
|
|
806
|
+
config = self.actions_manager.get_config()
|
|
807
|
+
directory = config.get(
|
|
808
|
+
"directory", f"{self.app_meta_info['name']}_{self.app_meta_info['appid']}"
|
|
809
|
+
)
|
|
810
|
+
asset_id = str(self.soar_client.get_asset_id())
|
|
811
|
+
|
|
812
|
+
return f"{webhook_base}/webhook/{directory}/{asset_id}/{route}"
|
|
813
|
+
|
|
814
|
+
def _get_webhook_port(self) -> int:
|
|
815
|
+
"""Get the webhook port from the feature flag configuration."""
|
|
816
|
+
try:
|
|
817
|
+
response = self.soar_client.get("rest/feature_flag/webhooks")
|
|
818
|
+
if response.status_code == 200:
|
|
819
|
+
data = response.json()
|
|
820
|
+
config = data.get("config", {})
|
|
821
|
+
if port := config.get("webhooks_port"):
|
|
822
|
+
return int(port)
|
|
823
|
+
except Exception: # noqa: S110
|
|
824
|
+
pass
|
|
825
|
+
return 3500
|
|
826
|
+
|
|
827
|
+
def _load_webhook_state(self, asset_id: str) -> None:
|
|
828
|
+
"""Load state from file for webhooks."""
|
|
829
|
+
state = self.actions_manager.load_state_from_file(asset_id)
|
|
830
|
+
if state:
|
|
831
|
+
self.actions_manager.save_state(state)
|
|
832
|
+
|
|
833
|
+
def _save_webhook_state(self, asset_id: str) -> None:
|
|
834
|
+
"""Save state to file for webhooks."""
|
|
835
|
+
state = self.actions_manager.load_state() or {}
|
|
836
|
+
self.actions_manager.save_state_to_file(asset_id, state)
|
|
837
|
+
|
|
792
838
|
def handle_webhook(
|
|
793
839
|
self,
|
|
794
840
|
method: str,
|
|
@@ -813,7 +859,7 @@ class App:
|
|
|
813
859
|
user_session_token=soar_auth_token,
|
|
814
860
|
base_url=soar_base_url,
|
|
815
861
|
)
|
|
816
|
-
self.soar_client.update_client(soar_auth, asset_id)
|
|
862
|
+
self.soar_client.update_client(soar_auth, str(asset_id))
|
|
817
863
|
|
|
818
864
|
normalized_query = {}
|
|
819
865
|
for key, value in query.items():
|
|
@@ -829,6 +875,7 @@ class App:
|
|
|
829
875
|
|
|
830
876
|
self.actions_manager.override_app_dir(self.app_root)
|
|
831
877
|
self.actions_manager._load_app_json()
|
|
878
|
+
self._load_webhook_state(str(asset_id))
|
|
832
879
|
request = WebhookRequest(
|
|
833
880
|
method=method,
|
|
834
881
|
headers=headers,
|
|
@@ -846,4 +893,5 @@ class App:
|
|
|
846
893
|
raise TypeError(
|
|
847
894
|
f"Webhook handler must return a WebhookResponse, got {type(response)}"
|
|
848
895
|
)
|
|
896
|
+
self._save_webhook_state(str(asset_id))
|
|
849
897
|
return response.model_dump()
|
soar_sdk/asset.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from enum import Enum
|
|
1
2
|
from typing import Any, NotRequired
|
|
2
3
|
from zoneinfo import ZoneInfo
|
|
3
4
|
|
|
@@ -17,6 +18,14 @@ remove_when_soar_newer_than(
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
class FieldCategory(str, Enum):
|
|
22
|
+
"""Categories used to group asset configuration fields in the SOAR UI."""
|
|
23
|
+
|
|
24
|
+
CONNECTIVITY = "connectivity"
|
|
25
|
+
ACTION = "action"
|
|
26
|
+
INGEST = "ingest"
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
def AssetField(
|
|
21
30
|
description: str | None = None,
|
|
22
31
|
required: bool = True,
|
|
@@ -24,27 +33,25 @@ def AssetField(
|
|
|
24
33
|
value_list: list | None = None,
|
|
25
34
|
sensitive: bool = False,
|
|
26
35
|
alias: str | None = None,
|
|
36
|
+
category: FieldCategory = FieldCategory.CONNECTIVITY,
|
|
27
37
|
) -> Any: # noqa: ANN401
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
The field needs extra metadata that is later used for the configuration of the app.
|
|
31
|
-
This function takes care of the required information for the manifest JSON file and fills in defaults.
|
|
38
|
+
"""Define an asset configuration field with SOAR-specific metadata.
|
|
32
39
|
|
|
33
40
|
Args:
|
|
34
|
-
description:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
description: Human-friendly label for the field shown in the asset form.
|
|
42
|
+
required: Whether the field must be provided. When True and ``default`` is
|
|
43
|
+
``None``, the field is marked as required in the manifest.
|
|
44
|
+
default: Default value for optional fields. Ignored when ``required`` is
|
|
45
|
+
True and no explicit default is provided.
|
|
46
|
+
value_list: Optional dropdown options presented to the user.
|
|
47
|
+
sensitive: Marks the field as secret so it is encrypted and hidden from logs.
|
|
48
|
+
alias: Alternate name to emit in the manifest instead of the attribute name.
|
|
49
|
+
category: Grouping used to organize fields in the SOAR UI.
|
|
43
50
|
|
|
44
51
|
Returns:
|
|
45
|
-
|
|
52
|
+
A Pydantic ``Field`` carrying the metadata needed for manifest generation.
|
|
46
53
|
"""
|
|
47
|
-
json_schema_extra: dict[str, Any] = {}
|
|
54
|
+
json_schema_extra: dict[str, Any] = {"category": category}
|
|
48
55
|
if required is not None:
|
|
49
56
|
json_schema_extra["required"] = required
|
|
50
57
|
if value_list is not None:
|
|
@@ -59,7 +66,7 @@ def AssetField(
|
|
|
59
66
|
default=field_default,
|
|
60
67
|
description=description,
|
|
61
68
|
alias=alias,
|
|
62
|
-
json_schema_extra=json_schema_extra
|
|
69
|
+
json_schema_extra=json_schema_extra,
|
|
63
70
|
)
|
|
64
71
|
|
|
65
72
|
|
|
@@ -80,6 +87,7 @@ class AssetFieldSpecification(TypedDict):
|
|
|
80
87
|
"""
|
|
81
88
|
|
|
82
89
|
data_type: str
|
|
90
|
+
category: FieldCategory
|
|
83
91
|
description: NotRequired[str]
|
|
84
92
|
required: NotRequired[bool]
|
|
85
93
|
default: NotRequired[str | int | float | bool]
|
|
@@ -114,7 +122,10 @@ class BaseAsset(BaseModel):
|
|
|
114
122
|
|
|
115
123
|
Note:
|
|
116
124
|
Field names cannot start with "_reserved_" or use names reserved by
|
|
117
|
-
the SOAR platform to avoid conflicts with internal fields.
|
|
125
|
+
the SOAR platform to avoid conflicts with internal fields. The runtime
|
|
126
|
+
attaches ``auth_state``, ``cache_state``, and ``ingest_state`` when an
|
|
127
|
+
app context is available; accessing them without that context raises
|
|
128
|
+
``AppContextRequired``.
|
|
118
129
|
"""
|
|
119
130
|
|
|
120
131
|
model_config = ConfigDict(
|
|
@@ -124,25 +135,15 @@ class BaseAsset(BaseModel):
|
|
|
124
135
|
@model_validator(mode="before")
|
|
125
136
|
@classmethod
|
|
126
137
|
def validate_no_reserved_fields(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
127
|
-
"""
|
|
138
|
+
"""Prevent subclasses from using names reserved by the platform.
|
|
128
139
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
values: Dictionary of field values being validated.
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
The validated values dictionary.
|
|
140
|
+
The validator inspects annotated field names to ensure they do not start
|
|
141
|
+
with ``_reserved_`` and do not collide with fields injected by the SOAR
|
|
142
|
+
service (see ``AppConfig``). The ``values`` argument is unused but kept
|
|
143
|
+
for Pydantic compatibility.
|
|
137
144
|
|
|
138
145
|
Raises:
|
|
139
|
-
ValueError: If a
|
|
140
|
-
with platform-reserved field names.
|
|
141
|
-
|
|
142
|
-
Note:
|
|
143
|
-
The SOAR platform injects fields like "_reserved_credential_management"
|
|
144
|
-
into asset configs, so this prevents the entire "_reserved_" namespace
|
|
145
|
-
from being used in user-defined assets.
|
|
146
|
+
ValueError: If a reserved or injected field name is used.
|
|
146
147
|
"""
|
|
147
148
|
for field_name in cls.__annotations__:
|
|
148
149
|
# The platform injects fields like "_reserved_credential_management" into asset configs,
|
|
@@ -183,33 +184,20 @@ class BaseAsset(BaseModel):
|
|
|
183
184
|
|
|
184
185
|
@classmethod
|
|
185
186
|
def to_json_schema(cls) -> dict[str, AssetFieldSpecification]:
|
|
186
|
-
"""Generate
|
|
187
|
+
"""Generate manifest-ready schema entries from the asset definition.
|
|
187
188
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
Each field is converted into a SOAR manifest dictionary that includes the
|
|
190
|
+
data type, requirement flag, default value, dropdown options, and an order
|
|
191
|
+
index. Alias names are honored when present. Sensitive fields are emitted
|
|
192
|
+
as ``password`` data types and must be annotated as ``str``. Defaults are
|
|
193
|
+
serialized directly, with ``ZoneInfo`` defaults represented by their key.
|
|
191
194
|
|
|
192
195
|
Returns:
|
|
193
|
-
|
|
194
|
-
including data types, descriptions, requirements, and other metadata.
|
|
196
|
+
Mapping of field (or alias) names to schema specifications.
|
|
195
197
|
|
|
196
198
|
Raises:
|
|
197
|
-
TypeError: If a field type cannot be serialized or
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Example:
|
|
201
|
-
>>> class MyAsset(BaseAsset):
|
|
202
|
-
... host: str = AssetField(description="Server hostname")
|
|
203
|
-
... port: int = AssetField(description="Server port", default=443)
|
|
204
|
-
>>> schema = MyAsset.to_json_schema()
|
|
205
|
-
>>> schema["host"]["data_type"]
|
|
206
|
-
'string'
|
|
207
|
-
>>> schema["host"]["required"]
|
|
208
|
-
True
|
|
209
|
-
|
|
210
|
-
Note:
|
|
211
|
-
Sensitive fields are automatically converted to "password" type
|
|
212
|
-
regardless of their Python type annotation, and must be str type.
|
|
199
|
+
TypeError: If a field type cannot be serialized or a sensitive field is
|
|
200
|
+
not declared as ``str``.
|
|
213
201
|
"""
|
|
214
202
|
params: dict[str, AssetFieldSpecification] = {}
|
|
215
203
|
|
|
@@ -242,6 +230,7 @@ class BaseAsset(BaseModel):
|
|
|
242
230
|
required=bool(json_schema_extra.get("required", True)),
|
|
243
231
|
description=description,
|
|
244
232
|
order=field_order,
|
|
233
|
+
category=json_schema_extra.get("category", FieldCategory.CONNECTIVITY),
|
|
245
234
|
)
|
|
246
235
|
|
|
247
236
|
if (default := field.default) not in (PydanticUndefined, None):
|
|
@@ -258,12 +247,7 @@ class BaseAsset(BaseModel):
|
|
|
258
247
|
|
|
259
248
|
@classmethod
|
|
260
249
|
def fields_requiring_decryption(cls) -> set[str]:
|
|
261
|
-
"""
|
|
262
|
-
|
|
263
|
-
Returns:
|
|
264
|
-
A set of field names that are marked as sensitive and need
|
|
265
|
-
decryption before use.
|
|
266
|
-
"""
|
|
250
|
+
"""Return attribute names marked as sensitive (aliases are ignored)."""
|
|
267
251
|
return {
|
|
268
252
|
field_name
|
|
269
253
|
for field_name, field in cls.model_fields.items()
|
|
@@ -273,11 +257,7 @@ class BaseAsset(BaseModel):
|
|
|
273
257
|
|
|
274
258
|
@classmethod
|
|
275
259
|
def timezone_fields(cls) -> set[str]:
|
|
276
|
-
"""
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
A set of field names that use the ZoneInfo type.
|
|
280
|
-
"""
|
|
260
|
+
"""Return attribute names typed as ``ZoneInfo`` (aliases are ignored)."""
|
|
281
261
|
return {
|
|
282
262
|
field_name
|
|
283
263
|
for field_name, field in cls.model_fields.items()
|
|
@@ -290,21 +270,21 @@ class BaseAsset(BaseModel):
|
|
|
290
270
|
|
|
291
271
|
@property
|
|
292
272
|
def auth_state(self) -> AssetState:
|
|
293
|
-
"""
|
|
273
|
+
"""Authentication state persisted by SOAR (encrypted at rest); raises if no app context."""
|
|
294
274
|
if self._auth_state is None:
|
|
295
275
|
raise AppContextRequired()
|
|
296
276
|
return self._auth_state
|
|
297
277
|
|
|
298
278
|
@property
|
|
299
279
|
def cache_state(self) -> AssetState:
|
|
300
|
-
"""
|
|
280
|
+
"""Cache for miscellaneous data persisted by SOAR (encrypted at rest); raises if no app context."""
|
|
301
281
|
if self._cache_state is None:
|
|
302
282
|
raise AppContextRequired()
|
|
303
283
|
return self._cache_state
|
|
304
284
|
|
|
305
285
|
@property
|
|
306
286
|
def ingest_state(self) -> AssetState:
|
|
307
|
-
"""
|
|
287
|
+
"""Ingestion checkpoints persisted by SOAR (encrypted at rest); raises if no app context."""
|
|
308
288
|
if self._ingest_state is None:
|
|
309
289
|
raise AppContextRequired()
|
|
310
290
|
return self._ingest_state
|
soar_sdk/asset_state.py
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from collections.abc import Iterator, MutableMapping
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
from soar_sdk.shims.phantom.base_connector import BaseConnector
|
|
5
6
|
from soar_sdk.shims.phantom.encryption_helper import encryption_helper
|
|
6
7
|
|
|
7
8
|
AssetStateKeyType = str
|
|
8
|
-
AssetStateValueType =
|
|
9
|
+
AssetStateValueType = Any
|
|
9
10
|
AssetStateType = dict[AssetStateKeyType, AssetStateValueType]
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class AssetState(MutableMapping[AssetStateKeyType, AssetStateValueType]):
|
|
13
14
|
"""An adapter to the asset state stored within SOAR. The state can be split into multiple partitions; this object represents a single partition. State is automatically encrypted at rest."""
|
|
14
15
|
|
|
15
|
-
def __init__(
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
backend: BaseConnector,
|
|
19
|
+
state_key: str,
|
|
20
|
+
asset_id: str,
|
|
21
|
+
app_id: str | None = None,
|
|
22
|
+
) -> None:
|
|
16
23
|
self.backend = backend
|
|
17
24
|
self.state_key = state_key
|
|
18
25
|
self.asset_id = asset_id
|
|
26
|
+
self.app_id = app_id
|
|
19
27
|
|
|
20
|
-
def get_all(self) -> AssetStateType:
|
|
28
|
+
def get_all(self, *, force_reload: bool = False) -> AssetStateType:
|
|
21
29
|
"""Get the entirety of this part of the asset state."""
|
|
30
|
+
if force_reload:
|
|
31
|
+
self.backend.reload_state_from_file(self.asset_id)
|
|
22
32
|
state = self.backend.load_state() or {}
|
|
23
33
|
if not (part_encrypted := state.get(self.state_key)):
|
|
24
34
|
return {}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from soar_sdk.auth.client import (
|
|
2
|
+
CertificateOAuthClient,
|
|
3
|
+
OAuthClientError,
|
|
4
|
+
SOARAssetOAuthClient,
|
|
5
|
+
)
|
|
6
|
+
from soar_sdk.auth.factories import (
|
|
7
|
+
create_oauth_auth,
|
|
8
|
+
create_oauth_callback_handler,
|
|
9
|
+
create_oauth_client,
|
|
10
|
+
)
|
|
11
|
+
from soar_sdk.auth.flows import (
|
|
12
|
+
AuthorizationCodeFlow,
|
|
13
|
+
ClientCredentialsFlow,
|
|
14
|
+
)
|
|
15
|
+
from soar_sdk.auth.httpx_auth import (
|
|
16
|
+
BasicAuth,
|
|
17
|
+
OAuthBearerAuth,
|
|
18
|
+
StaticTokenAuth,
|
|
19
|
+
)
|
|
20
|
+
from soar_sdk.auth.models import (
|
|
21
|
+
CertificateCredentials,
|
|
22
|
+
OAuthConfig,
|
|
23
|
+
OAuthToken,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AuthorizationCodeFlow",
|
|
28
|
+
"BasicAuth",
|
|
29
|
+
"CertificateCredentials",
|
|
30
|
+
"CertificateOAuthClient",
|
|
31
|
+
"ClientCredentialsFlow",
|
|
32
|
+
"OAuthBearerAuth",
|
|
33
|
+
"OAuthClientError",
|
|
34
|
+
"OAuthConfig",
|
|
35
|
+
"OAuthToken",
|
|
36
|
+
"SOARAssetOAuthClient",
|
|
37
|
+
"StaticTokenAuth",
|
|
38
|
+
"create_oauth_auth",
|
|
39
|
+
"create_oauth_callback_handler",
|
|
40
|
+
"create_oauth_client",
|
|
41
|
+
]
|