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/auth/flows.py ADDED
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from abc import ABC, abstractmethod
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from soar_sdk.auth.client import (
9
+ AuthorizationRequiredError,
10
+ OAuthClientError,
11
+ SOARAssetOAuthClient,
12
+ TokenExpiredError,
13
+ )
14
+ from soar_sdk.auth.models import OAuthConfig, OAuthGrantType, OAuthToken
15
+ from soar_sdk.logging import getLogger
16
+
17
+ if TYPE_CHECKING:
18
+ from soar_sdk.asset_state import AssetState
19
+
20
+ logger = getLogger()
21
+
22
+
23
+ class OAuthFlow(ABC):
24
+ """Abstract base class for OAuth authentication flows."""
25
+
26
+ @abstractmethod
27
+ def authenticate(self) -> OAuthToken:
28
+ """Execute the authentication flow and return a valid token."""
29
+
30
+ @abstractmethod
31
+ def get_token(self) -> OAuthToken:
32
+ """Get a valid token, refreshing if necessary."""
33
+
34
+
35
+ class ClientCredentialsFlow(OAuthFlow):
36
+ """OAuth 2.0 Client Credentials flow."""
37
+
38
+ def __init__(
39
+ self,
40
+ auth_state: AssetState,
41
+ *,
42
+ client_id: str,
43
+ client_secret: str,
44
+ token_endpoint: str,
45
+ scope: str | list[str] | None = None,
46
+ extra_params: dict[str, Any] | None = None,
47
+ ) -> None:
48
+ self._auth_state = auth_state
49
+ self._extra_params = extra_params
50
+
51
+ self._config = OAuthConfig(
52
+ client_id=client_id,
53
+ client_secret=client_secret,
54
+ token_endpoint=token_endpoint,
55
+ scope=scope,
56
+ grant_type=OAuthGrantType.CLIENT_CREDENTIALS,
57
+ )
58
+
59
+ self._client = SOARAssetOAuthClient(self._config, auth_state)
60
+
61
+ def authenticate(self) -> OAuthToken:
62
+ """Authenticate using client credentials."""
63
+ return self._client.fetch_token_with_client_credentials(
64
+ extra_params=self._extra_params,
65
+ )
66
+
67
+ def get_token(self) -> OAuthToken:
68
+ """Get a valid token, fetching a new one if expired."""
69
+ try:
70
+ return self._client.get_valid_token(auto_refresh=False)
71
+ except (AuthorizationRequiredError, TokenExpiredError):
72
+ return self.authenticate()
73
+
74
+
75
+ class AuthorizationCodeFlow(OAuthFlow):
76
+ """OAuth 2.0 Authorization Code flow."""
77
+
78
+ def __init__(
79
+ self,
80
+ auth_state: AssetState,
81
+ asset_id: str,
82
+ *,
83
+ client_id: str,
84
+ client_secret: str | None = None,
85
+ authorization_endpoint: str,
86
+ token_endpoint: str,
87
+ redirect_uri: str,
88
+ scope: str | list[str] | None = None,
89
+ use_pkce: bool = False,
90
+ extra_auth_params: dict[str, Any] | None = None,
91
+ extra_token_params: dict[str, Any] | None = None,
92
+ poll_timeout: int = 300,
93
+ poll_interval: int = 3,
94
+ ) -> None:
95
+ self._auth_state = auth_state
96
+ self._asset_id = asset_id
97
+ self._use_pkce = use_pkce
98
+ self._extra_auth_params = extra_auth_params
99
+ self._extra_token_params = extra_token_params
100
+ self._poll_timeout = poll_timeout
101
+ self._poll_interval = poll_interval
102
+
103
+ self._config = OAuthConfig(
104
+ client_id=client_id,
105
+ client_secret=client_secret,
106
+ authorization_endpoint=authorization_endpoint,
107
+ token_endpoint=token_endpoint,
108
+ redirect_uri=redirect_uri,
109
+ scope=scope,
110
+ grant_type=OAuthGrantType.AUTHORIZATION_CODE,
111
+ )
112
+
113
+ self._client = SOARAssetOAuthClient(self._config, auth_state)
114
+
115
+ @property
116
+ def client(self) -> SOARAssetOAuthClient:
117
+ """Return the OAuth client."""
118
+ return self._client
119
+
120
+ def get_authorization_url(self) -> str:
121
+ """Generate the authorization URL."""
122
+ auth_url, _ = self._client.create_authorization_url(
123
+ self._asset_id,
124
+ use_pkce=self._use_pkce,
125
+ extra_params=self._extra_auth_params,
126
+ )
127
+ return auth_url
128
+
129
+ def set_authorization_code(self, code: str) -> None:
130
+ """Store authorization code in state (called by webhook)."""
131
+ self._client.set_authorization_code(code)
132
+
133
+ def exchange_code_for_token(self, code: str) -> OAuthToken:
134
+ """Exchange an authorization code for tokens."""
135
+ return self._client.fetch_token_with_authorization_code(
136
+ code,
137
+ extra_params=self._extra_token_params,
138
+ )
139
+
140
+ def wait_for_authorization(
141
+ self,
142
+ on_progress: Callable[[int], None] | None = None,
143
+ ) -> OAuthToken:
144
+ """Wait for user authorization to complete by polling state."""
145
+ start_time = time.time()
146
+ iteration = 0
147
+
148
+ while time.time() - start_time < self._poll_timeout:
149
+ time.sleep(self._poll_interval)
150
+ iteration += 1
151
+
152
+ if on_progress:
153
+ on_progress(iteration)
154
+
155
+ code = self._client.get_authorization_code(force_reload=True)
156
+ if code:
157
+ return self.exchange_code_for_token(code)
158
+
159
+ raise OAuthClientError(
160
+ f"Authorization timed out after {self._poll_timeout} seconds"
161
+ )
162
+
163
+ def authenticate(self) -> OAuthToken:
164
+ """Execute the full authorization code flow."""
165
+ auth_url = self.get_authorization_url()
166
+ raise AuthorizationRequiredError(
167
+ f"User authorization required. Please visit: {auth_url}"
168
+ )
169
+
170
+ def get_token(self) -> OAuthToken:
171
+ """Get a valid token, refreshing if necessary."""
172
+ return self._client.get_valid_token(auto_refresh=True)
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from collections.abc import Generator
5
+
6
+ import httpx
7
+
8
+ from soar_sdk.auth.client import SOARAssetOAuthClient
9
+ from soar_sdk.auth.models import OAuthToken
10
+
11
+
12
+ class BasicAuth(httpx.Auth):
13
+ """HTTPX authentication using HTTP Basic Authentication."""
14
+
15
+ def __init__(self, username: str, password: str) -> None:
16
+ self._username = username
17
+ self._password = password
18
+
19
+ def auth_flow(
20
+ self,
21
+ request: httpx.Request,
22
+ ) -> Generator[httpx.Request, httpx.Response]:
23
+ """Add Basic authentication header to the request."""
24
+ credentials = f"{self._username}:{self._password}"
25
+ encoded = base64.b64encode(credentials.encode()).decode("ascii")
26
+ request.headers["Authorization"] = f"Basic {encoded}"
27
+ yield request
28
+
29
+
30
+ class StaticTokenAuth(httpx.Auth):
31
+ """HTTPX authentication using a static token."""
32
+
33
+ def __init__(
34
+ self,
35
+ token: OAuthToken | str,
36
+ *,
37
+ token_type: str = "Bearer", # noqa: S107
38
+ header_name: str = "Authorization",
39
+ ) -> None:
40
+ if isinstance(token, str):
41
+ self._access_token = token
42
+ else:
43
+ self._access_token = token.access_token
44
+ token_type = token.token_type or token_type
45
+ self._token_type = token_type
46
+ self._header_name = header_name
47
+
48
+ def auth_flow(
49
+ self,
50
+ request: httpx.Request,
51
+ ) -> Generator[httpx.Request, httpx.Response]:
52
+ """Add authentication header to the request."""
53
+ if self._token_type:
54
+ request.headers[self._header_name] = (
55
+ f"{self._token_type} {self._access_token}"
56
+ )
57
+ else:
58
+ request.headers[self._header_name] = self._access_token
59
+ yield request
60
+
61
+
62
+ class OAuthBearerAuth(httpx.Auth):
63
+ """HTTPX authentication using OAuth Bearer tokens."""
64
+
65
+ requires_response_body = True
66
+
67
+ def __init__(
68
+ self,
69
+ oauth_client: SOARAssetOAuthClient,
70
+ *,
71
+ auto_refresh: bool = True,
72
+ ) -> None:
73
+ self._oauth_client = oauth_client
74
+ self._auto_refresh = auto_refresh
75
+ self._token: OAuthToken | None = None
76
+
77
+ def auth_flow(
78
+ self,
79
+ request: httpx.Request,
80
+ ) -> Generator[httpx.Request, httpx.Response]:
81
+ """Handle authentication flow for a request."""
82
+ if self._token is None or self._token.is_expired():
83
+ self._token = self._oauth_client.get_valid_token(
84
+ auto_refresh=self._auto_refresh
85
+ )
86
+
87
+ request.headers["Authorization"] = f"Bearer {self._token.access_token}"
88
+ response = yield request
89
+
90
+ if (
91
+ response.status_code == 401
92
+ and self._auto_refresh
93
+ and self._token.refresh_token
94
+ ):
95
+ self._token = self._oauth_client.refresh_token(self._token.refresh_token)
96
+ request.headers["Authorization"] = f"Bearer {self._token.access_token}"
97
+ yield request
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from enum import StrEnum
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+
9
+ class OAuthGrantType(StrEnum):
10
+ """Supported OAuth 2.0 grant types."""
11
+
12
+ AUTHORIZATION_CODE = "authorization_code"
13
+ CLIENT_CREDENTIALS = "client_credentials"
14
+ REFRESH_TOKEN = "refresh_token" # noqa: S105
15
+
16
+
17
+ class OAuthToken(BaseModel):
18
+ """OAuth 2.0 token response."""
19
+
20
+ model_config = ConfigDict(extra="allow")
21
+
22
+ _DEFAULT_TOKEN_TYPE = "Bearer" # noqa: S105
23
+
24
+ access_token: str
25
+ token_type: str = _DEFAULT_TOKEN_TYPE
26
+ expires_in: int | None = None
27
+ refresh_token: str | None = None
28
+ scope: str | None = None
29
+ expires_at: float | None = None
30
+
31
+ def model_post_init(self, __context: object) -> None:
32
+ """Calculate expires_at if not provided but expires_in is available."""
33
+ if self.expires_at is None and self.expires_in is not None:
34
+ self.expires_at = time.time() + self.expires_in
35
+
36
+ def is_expired(self, leeway: int = 30) -> bool:
37
+ """Check if the token is expired."""
38
+ if self.expires_at is None:
39
+ return False
40
+ return time.time() >= (self.expires_at - leeway)
41
+
42
+
43
+ class OAuthConfig(BaseModel):
44
+ """Configuration for OAuth 2.0 authentication."""
45
+
46
+ model_config = ConfigDict(extra="forbid")
47
+
48
+ client_id: str
49
+ client_secret: str | None = None
50
+ authorization_endpoint: str | None = None
51
+ token_endpoint: str
52
+ redirect_uri: str | None = None
53
+ scope: str | list[str] | None = None
54
+ grant_type: OAuthGrantType = OAuthGrantType.AUTHORIZATION_CODE
55
+
56
+ def get_scope_string(self) -> str | None:
57
+ """Return scope as a space-separated string."""
58
+ if self.scope is None:
59
+ return None
60
+ if isinstance(self.scope, list):
61
+ return " ".join(self.scope)
62
+ return self.scope
63
+
64
+
65
+ class OAuthSession(BaseModel):
66
+ """Represents an active OAuth authentication session."""
67
+
68
+ model_config = ConfigDict(extra="forbid")
69
+
70
+ session_id: str
71
+ asset_id: str
72
+ auth_pending: bool = True
73
+ auth_complete: bool = False
74
+ auth_code: str | None = None
75
+ error: str | None = None
76
+ error_description: str | None = None
77
+ state: str | None = None
78
+ code_verifier: str | None = None
79
+
80
+
81
+ class OAuthState(BaseModel):
82
+ """State persisted in auth_state for OAuth authentication."""
83
+
84
+ model_config = ConfigDict(extra="forbid")
85
+
86
+ token: OAuthToken | None = None
87
+ session: OAuthSession | None = None
88
+ client_id: str | None = Field(
89
+ default=None,
90
+ description="Stored client_id to detect credential changes",
91
+ )
92
+
93
+
94
+ class CertificateCredentials(BaseModel):
95
+ """Certificate-based authentication credentials."""
96
+
97
+ model_config = ConfigDict(extra="forbid")
98
+
99
+ certificate_thumbprint: str
100
+ private_key: str
101
+ tenant_id: str | None = None
@@ -27,35 +27,53 @@ package = typer.Typer()
27
27
  console = Console() # For printing lots of pretty colors and stuff
28
28
 
29
29
 
30
- async def collect_all_wheels(wheels: set[DependencyWheel]) -> list[tuple[str, bytes]]:
31
- """Asynchronously collect all wheels from the given set of DependencyWheel objects."""
32
- # Create progress bar for tracking wheel collection
30
+ async def collect_all_wheels(wheels: list[DependencyWheel]) -> list[tuple[str, bytes]]:
31
+ """Asynchronously collect all wheels from the given list of DependencyWheel objects.
32
+
33
+ Downloads/builds each unique wheel once while updating every DependencyWheel instance
34
+ so the manifest records the final wheel filenames.
35
+ """
36
+ dedupe_map: dict[int, list[DependencyWheel]] = {}
37
+ for wheel in wheels:
38
+ key = hash(wheel)
39
+ dedupe_map.setdefault(key, []).append(wheel)
40
+
33
41
  progress = tqdm(
34
- total=len(wheels),
42
+ total=len(dedupe_map),
35
43
  desc="Downloading wheels",
36
44
  unit="wheel",
37
45
  colour="green",
38
46
  ncols=80,
39
47
  )
40
48
 
41
- async def collect_from_wheel(wheel: DependencyWheel) -> list[tuple[str, bytes]]:
42
- result = []
43
- # This actually is covered, but pytest-cov branch coverage
44
- # has a bug with the end of async for loops
49
+ async def collect_from_wheel(
50
+ cache_key: int, wheel: DependencyWheel
51
+ ) -> tuple[int, list[tuple[str, bytes]]]:
52
+ result: list[tuple[str, bytes]] = []
45
53
  async for path, data in wheel.collect_wheels(): # pragma: no cover
46
54
  result.append((path, data))
47
- # Update progress bar after each wheel is processed
48
55
  progress.update(1)
49
- return result
56
+ return cache_key, result
50
57
 
51
- # Use asyncio.gather to truly run all wheel collections concurrently
52
58
  with contextlib.closing(progress):
53
- wheel_data_lists = await asyncio.gather(
54
- *(collect_from_wheel(wheel) for wheel in wheels)
59
+ gathered_results = await asyncio.gather(
60
+ *(
61
+ collect_from_wheel(key, wheel_group[0])
62
+ for key, wheel_group in dedupe_map.items()
63
+ )
55
64
  )
56
65
 
57
- # Use itertools.chain to flatten the list of lists
58
- return list(chain.from_iterable(wheel_data_lists))
66
+ cache = dict(gathered_results)
67
+
68
+ for key, wheel_group in dedupe_map.items():
69
+ representative = wheel_group[0]
70
+ if representative.sdist is not None or representative.source_dir is not None:
71
+ for path, _ in cache[key]:
72
+ wheel_name = Path(path).name
73
+ for wheel in wheel_group:
74
+ wheel._record_built_wheel(wheel_name)
75
+
76
+ return list(chain.from_iterable(cache.values()))
59
77
 
60
78
 
61
79
  @package.command()
@@ -124,13 +142,13 @@ def build(
124
142
 
125
143
  with tarfile.open(output_file, "w:gz") as app_tarball:
126
144
  # Collect all wheels from both Python versions
127
- all_wheels = set(
145
+ all_wheels = (
128
146
  app_meta.pip313_dependencies.wheel + app_meta.pip314_dependencies.wheel
129
147
  )
130
148
 
131
149
  # Run the async collection function within an event loop
132
150
  console.print(
133
- f"[yellow]Collecting [bold]{len(all_wheels)}[/bold] wheel{'' if len(all_wheels) == 1 else 's'} for package[/]"
151
+ f"[yellow]Collecting [bold]{len(all_wheels)}[/bold] wheel{'' if len(set(all_wheels)) == 1 else 's'} for package[/]"
134
152
  )
135
153
  wheel_data = asyncio.run(collect_all_wheels(all_wheels))
136
154
 
@@ -219,18 +219,41 @@ class DependencyWheel(BaseModel):
219
219
  sdist: UvSourceDistribution | None = Field(exclude=True, default=None)
220
220
  source_dir: UvSourceDirectory | None = Field(exclude=True, default=None)
221
221
 
222
+ def _set_wheel_paths(self, wheel_name: str) -> str:
223
+ """Assign the final wheel path (with any existing prefix) to both arches."""
224
+ base_path = Path(self.input_file or "wheels/shared")
225
+ # If there's already a filename component, replace it instead of nesting it
226
+ if base_path.suffix == ".whl":
227
+ base_path = base_path.parent
228
+ wheel_path = (base_path / wheel_name).as_posix()
229
+ self.input_file = wheel_path
230
+ self.input_file_aarch64 = wheel_path
231
+ return wheel_path
232
+
233
+ def set_placeholder_wheel_name(self, version: str) -> None:
234
+ """Populate a clearly placeholder wheel path when we expect to build from source."""
235
+ # Use only a filename here; platform-specific prefixes are added later.
236
+ self.input_file = "<to_be_built>.whl"
237
+ self.input_file_aarch64 = "<to_be_built>.whl"
238
+
239
+ def _record_built_wheel(self, wheel_name: str) -> str:
240
+ """Fill in missing wheel paths once a wheel has been built from source."""
241
+ return self._set_wheel_paths(wheel_name)
242
+
222
243
  async def collect_wheels(self) -> AsyncGenerator[tuple[str, bytes]]:
223
244
  """Collect a list of wheel files to fetch for this dependency across all platforms."""
224
245
  if self.wheel is None and self.sdist is not None:
225
246
  logger.info(f"Building sdist for {self.input_file}")
226
247
  wheel_name, wheel_bytes = await self.sdist.fetch_and_build()
227
- yield (f"wheels/shared/{wheel_name}", wheel_bytes)
248
+ wheel_path = self._record_built_wheel(wheel_name)
249
+ yield (wheel_path, wheel_bytes)
228
250
  return
229
251
 
230
252
  if self.wheel is None and self.source_dir is not None:
231
253
  logger.info(f"Building local sources for {self.input_file}")
232
254
  wheel_name, wheel_bytes = self.source_dir.build()
233
- yield (f"wheels/shared/{wheel_name}", wheel_bytes)
255
+ wheel_path = self._record_built_wheel(wheel_name)
256
+ yield (wheel_path, wheel_bytes)
234
257
  return
235
258
 
236
259
  if self.wheel is None:
@@ -398,12 +421,14 @@ class UvPackage(BaseModel):
398
421
  and UvLock.normalize_package_name(self.name) in DEPENDENCIES_TO_BUILD
399
422
  ):
400
423
  wheel.sdist = self.sdist
424
+ wheel.set_placeholder_wheel_name(self.version)
401
425
 
402
426
  if (
403
427
  self.source.directory is not None
404
428
  and UvLock.normalize_package_name(self.name) in DEPENDENCIES_TO_BUILD
405
429
  ):
406
430
  wheel.source_dir = UvSourceDirectory(directory=self.source.directory)
431
+ wheel.set_placeholder_wheel_name(self.version)
407
432
 
408
433
  try:
409
434
  wheel_x86_64 = self._find_wheel(
@@ -429,7 +454,7 @@ class UvPackage(BaseModel):
429
454
  wheel.input_file_aarch64 = f"{wheel_aarch64.basename}.whl"
430
455
  wheel.wheel_aarch64 = wheel_aarch64
431
456
  except FileNotFoundError:
432
- if wheel.sdist is None:
457
+ if wheel.sdist is None and wheel.source_dir is None:
433
458
  logger.warning(
434
459
  f"Could not find a suitable aarch64 wheel for {self.name=}, {self.version=}, {abi_precedence=}, {python_precedence=} -- the built package might not work on ARM systems"
435
460
  )
@@ -121,6 +121,13 @@ if TYPE_CHECKING or not _soar_is_available:
121
121
  def get_config(self) -> dict:
122
122
  return self.config
123
123
 
124
+ def get_app_id(self) -> str:
125
+ return self.__app_json.get("appid", "")
126
+
127
+ def get_state_dir(self) -> str:
128
+ phantom_home = os.getenv("PHANTOM_HOME", "/opt/phantom")
129
+ return f"{phantom_home}/local_data/app_states/{self.get_app_id()}/"
130
+
124
131
  def save_state(self, state: dict) -> None:
125
132
  self.__state = state
126
133
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splunk-soar-sdk
3
- Version: 3.6.0
3
+ Version: 3.7.0
4
4
  Summary: The official framework for developing and testing Splunk SOAR Apps
5
5
  Project-URL: Homepage, https://github.com/phantomcyber/splunk-soar-sdk
6
6
  Project-URL: Documentation, https://github.com/phantomcyber/splunk-soar-sdk
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Classifier: Typing :: Typed
19
19
  Requires-Python: <3.15,>=3.13
20
+ Requires-Dist: authlib>=1.3.0
20
21
  Requires-Dist: beautifulsoup4>=4.10.0
21
22
  Requires-Dist: bleach>=6.2.0
22
23
  Requires-Dist: build>=1.3.0
@@ -29,6 +30,7 @@ Requires-Dist: humanize>=4.12.2
29
30
  Requires-Dist: jinja2>=3.1.0
30
31
  Requires-Dist: packaging>=25.0
31
32
  Requires-Dist: pydantic<3,>=2
33
+ Requires-Dist: pyjwt[crypto]>=2.8.0
32
34
  Requires-Dist: requests<3
33
35
  Requires-Dist: setuptools>=80.9.0
34
36
  Requires-Dist: toml<1,>=0.10.2
@@ -1,12 +1,12 @@
1
1
  soar_sdk/__init__.py,sha256=RzAng-ARqpK01SY82lNy4uYJFVG0yW6Q3CccEqbToJ4,726
2
2
  soar_sdk/abstract.py,sha256=GycJhTrSNDa7eDg8hOD7hJjIt5eHEykZhpza-jh_Veo,7787
3
3
  soar_sdk/action_results.py,sha256=eL4qBj2nXDKurzs733z_nnpNREc0SLLYJP2lPTpMKf0,11911
4
- soar_sdk/actions_manager.py,sha256=jfTTa8Rq06GXpJ_UHCA0MFli5bLgYFbcjpgbAW-ZSKo,5684
5
- soar_sdk/app.py,sha256=1tPd1bFe9abSzNJCqAmw7sexeuSHwSKGdggyrPjkVQA,35122
4
+ soar_sdk/actions_manager.py,sha256=8IYOi2k8i9LHXEhQVZ0Ig3IS1gD3iAmOJ9q0bi14g-o,7179
5
+ soar_sdk/app.py,sha256=2bUWx1BgWS9Kwkp0aUzVWM8LTdVUq1JI2eXR0LhwEMU,37092
6
6
  soar_sdk/app_cli_runner.py,sha256=K1ATWyGs0iNgPfIjMthsN72laOXqXCFZNEXfuzAMOM4,11645
7
7
  soar_sdk/app_client.py,sha256=hbe1R2QwXDmoS4959a-ay9oylD1Qk-oPJvJRnxvICz0,6281
8
- soar_sdk/asset.py,sha256=au_P8YS5SUJaZm98DJVpmHlX5BhBhAPnpoYnmj095cU,12236
9
- soar_sdk/asset_state.py,sha256=Qj9Ku9OyAr5rL7N_ifpaEdpA27eu5qggSIbUob2L_VI,2101
8
+ soar_sdk/asset.py,sha256=FI4HHtFdDXcOuad8sXB4ShkTzM8la42sZivCKjJK78o,11596
9
+ soar_sdk/asset_state.py,sha256=qh4n8IoabVObIZXRPyM0zznwC5LcJpbADcybmDdQABc,2318
10
10
  soar_sdk/async_utils.py,sha256=Dz7RagIRjyIagA9vivHWSb18S96J2WOuDB8B5Zy64AE,1428
11
11
  soar_sdk/colors.py,sha256=--i_iXqfyITUz4O95HMjfZQGbwFZ34bLmBhtfpXXqlQ,1095
12
12
  soar_sdk/compat.py,sha256=N4bG1wqISICV92K1jLx7v5JGrHC08Bdn3Gx3Cx1lEmE,3062
@@ -32,6 +32,12 @@ soar_sdk/app_templates/basic_app/logo.svg,sha256=_JTop6spn5oPWPk-w6Tzumx_FTSBanO
32
32
  soar_sdk/app_templates/basic_app/logo_dark.svg,sha256=PTxIs_1CKK9ZY3v-K1QoGwaUng9ZUL2MhUeO2jeHu_0,291
33
33
  soar_sdk/app_templates/basic_app/uv.lock,sha256=AfgaIBg88KH-0iyXpCXacXAwHYKm0c-on2gWXjV9L-Y,80216
34
34
  soar_sdk/app_templates/basic_app/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ soar_sdk/auth/__init__.py,sha256=Z6pSzWMd49aY4H-9IxdCeWxCEC8_K4kT7i4buyv03sw,896
36
+ soar_sdk/auth/client.py,sha256=QmQ2dcTpke7hI7aff4usgbM_LQ0Prw2PILEb56jQFII,17077
37
+ soar_sdk/auth/factories.py,sha256=lNDxhrJtQpEEVV_dGEarwHHdU64oNPCxcZTrv0L4GEY,3994
38
+ soar_sdk/auth/flows.py,sha256=ahSOdr129s-GQjGUK8e2kNaLmjLTRzkxEJG3IwUws78,5457
39
+ soar_sdk/auth/httpx_auth.py,sha256=b_ldXjRxqZZiCjCnR3eZvUiDWTqYZShZKhu9NBn1Vns,3009
40
+ soar_sdk/auth/models.py,sha256=vSgRqHxyga2W8FGQZLjqHtk0yEQHkbEREshDePfJXgo,2856
35
41
  soar_sdk/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
42
  soar_sdk/cli/cli.py,sha256=9OqjVFPoIBdsO-Zmijcf0yBnjk3ggBPEOaCxKfvvw6g,998
37
43
  soar_sdk/cli/path_utils.py,sha256=aJy3fzw2MIU-WHZn21H021B4sjGkKxW61l02QIFVGJ8,1087
@@ -43,7 +49,7 @@ soar_sdk/cli/manifests/cli.py,sha256=cly5xVdj4bBIdZVMQPIWTXRgUfd1ON3qKO-76Fwql18
43
49
  soar_sdk/cli/manifests/deserializers.py,sha256=kwgPAMgUEXtIn4AuQOh1nkLfWFqe4qnYPZ1czB-FQTU,16516
44
50
  soar_sdk/cli/manifests/processors.py,sha256=soiRTbfLQuetstt1Xk7vKmWzOUhgVON5JxjWMvnGN7w,5141
45
51
  soar_sdk/cli/manifests/serializers.py,sha256=ulpq3nS8g1YrIP371XoQC3_kpz-9v2Ln_mqPyMtpWn8,3632
46
- soar_sdk/cli/package/cli.py,sha256=Vb6vdDszWLEOuBrAe4BLoxzdmr8luwA0_FbsvjjYhhM,9578
52
+ soar_sdk/cli/package/cli.py,sha256=hdpVBRvVGWaYDYhpDILlA7LRhunfElLbNdh21jviKrM,10087
47
53
  soar_sdk/cli/package/utils.py,sha256=fl6PMcrdC2zA7A16byQuxxPyAI2Z-BqBLfLlF2ZNnQ4,1712
48
54
  soar_sdk/cli/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
55
  soar_sdk/cli/test/cli.py,sha256=iDrthN8L7B1RplLhq0EI69MndaOhvAXn7bqv3XzlfpM,7655
@@ -72,7 +78,7 @@ soar_sdk/meta/actions.py,sha256=qOXFChYxOVTt1d0gBffyw8ahJ3E4w7GsVoD4yzJN8Vc,2189
72
78
  soar_sdk/meta/adapters.py,sha256=KjSYIUtkCz2eesA_vhsNCjfi5C-Uz71tbSuDIjhuB8U,1112
73
79
  soar_sdk/meta/app.py,sha256=KbDiq1JgRiH4hudyKr95wMfcPN7YLyOkRjf4JIoFihs,2815
74
80
  soar_sdk/meta/datatypes.py,sha256=piR-oBVAATiRciXSdVE7XaqjUZTgSaOvTEqcOcNvCS0,795
75
- soar_sdk/meta/dependencies.py,sha256=1Jsdurc77zCMVSA_dePQfdNi2tpplnhuEnSnMs87Ibo,21949
81
+ soar_sdk/meta/dependencies.py,sha256=SXWOoUJRmyW9RE3CrgF2sQLQhfyPcXAg7KO0ONf3h_g,23244
76
82
  soar_sdk/meta/webhooks.py,sha256=ILKP9pNalKG9DLdNQNoDlu5KUnm0m7PyA2O0fbkqVrA,1217
77
83
  soar_sdk/models/__init__.py,sha256=YZVAcBguAlUsxAnBBL6jSguJEzf5PYCtdvbNyU1XfEU,380
78
84
  soar_sdk/models/artifact.py,sha256=G8hv9wPPoRgrAQzIf-YlCSjAlkHEcIPF389T1bo4yHw,1087
@@ -83,7 +89,7 @@ soar_sdk/models/vault_attachment.py,sha256=sdRnQdPiwgaZDojpap4ohH7u1Q5TYGP-drs8K
83
89
  soar_sdk/models/view.py,sha256=BUuz6VVVe78hg7irGgZCbvBcycOmuPqplkagdi3T4Dg,779
84
90
  soar_sdk/shims/phantom/action_result.py,sha256=Nddc9oswAfHU7I2q0pLm3HZ2YiLUQZUEIqqAjToZWnM,1606
85
91
  soar_sdk/shims/phantom/app.py,sha256=uvE7Hsz5KudARpwaye7cx9lEOTMmPsJlZdunmkV8_lY,303
86
- soar_sdk/shims/phantom/base_connector.py,sha256=59Eh9D6cFOfVNf6OfN6Q8ds-QnnUp8zO_S3HlgDmQo8,4872
92
+ soar_sdk/shims/phantom/base_connector.py,sha256=2Tbh5nHnP6euilRiQL9NaVtmrgQIiY0eIkkzu-ah8_Y,5152
87
93
  soar_sdk/shims/phantom/connector_result.py,sha256=b6yrR1uUXBhfwpUf8HzESItPgfaiHxNTXF8FaGdQNsk,640
88
94
  soar_sdk/shims/phantom/consts.py,sha256=eq6AIuDhb2Z-CJORwv98D3JbcIOW8CC673zx5dNPFKU,404
89
95
  soar_sdk/shims/phantom/encryption_helper.py,sha256=20VqqSFuftjB8bMriP6mjgvYWpYqYZyokYzq_aydkqU,1503
@@ -110,8 +116,8 @@ soar_sdk/views/components/pie_chart.py,sha256=LVTeHVJN6nf2vjUs9y7PDBhS0U1fKW750l
110
116
  soar_sdk/webhooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
117
  soar_sdk/webhooks/models.py,sha256=j3kbvYmcOlcj3gQYKtrv7iS-lDavMKYNLdCNMy_I2Hc,4542
112
118
  soar_sdk/webhooks/routing.py,sha256=OjezhuAb8wzW0MnbGSnIWeAH3uJcu-Sb7s3w9zoiPVM,6873
113
- splunk_soar_sdk-3.6.0.dist-info/METADATA,sha256=B68IqabYE-9dIw_KGpE_UETtUF-W0rmTWt-gjYbHM7M,7478
114
- splunk_soar_sdk-3.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
115
- splunk_soar_sdk-3.6.0.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
116
- splunk_soar_sdk-3.6.0.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
117
- splunk_soar_sdk-3.6.0.dist-info/RECORD,,
119
+ splunk_soar_sdk-3.7.0.dist-info/METADATA,sha256=etgn77ft4OQs4sPRMdUk88IMtxC7CPQoECTOPmrv9kk,7544
120
+ splunk_soar_sdk-3.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
121
+ splunk_soar_sdk-3.7.0.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
122
+ splunk_soar_sdk-3.7.0.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
123
+ splunk_soar_sdk-3.7.0.dist-info/RECORD,,