pangea-sdk 5.2.0b2__py3-none-any.whl → 5.2.1__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.
- pangea/__init__.py +1 -1
- pangea/asyncio/request.py +18 -9
- pangea/asyncio/services/__init__.py +0 -2
- pangea/asyncio/services/audit.py +6 -10
- pangea/asyncio/services/authz.py +23 -2
- pangea/asyncio/services/base.py +15 -1
- pangea/asyncio/services/intel.py +3 -0
- pangea/asyncio/services/redact.py +12 -0
- pangea/asyncio/services/sanitize.py +26 -2
- pangea/asyncio/services/share.py +23 -1
- pangea/deep_verify.py +7 -1
- pangea/dump_audit.py +8 -7
- pangea/request.py +16 -7
- pangea/services/__init__.py +0 -2
- pangea/services/audit/audit.py +5 -10
- pangea/services/authz.py +21 -1
- pangea/services/base.py +14 -1
- pangea/services/intel.py +18 -0
- pangea/services/redact.py +28 -0
- pangea/services/sanitize.py +22 -0
- pangea/services/share/share.py +23 -1
- {pangea_sdk-5.2.0b2.dist-info → pangea_sdk-5.2.1.dist-info}/METADATA +4 -4
- {pangea_sdk-5.2.0b2.dist-info → pangea_sdk-5.2.1.dist-info}/RECORD +24 -28
- pangea/asyncio/services/ai_guard.py +0 -75
- pangea/asyncio/services/prompt_guard.py +0 -73
- pangea/services/ai_guard.py +0 -157
- pangea/services/prompt_guard.py +0 -83
- {pangea_sdk-5.2.0b2.dist-info → pangea_sdk-5.2.1.dist-info}/WHEEL +0 -0
pangea/__init__.py
CHANGED
pangea/asyncio/request.py
CHANGED
@@ -5,12 +5,11 @@ from __future__ import annotations
|
|
5
5
|
import asyncio
|
6
6
|
import json
|
7
7
|
import time
|
8
|
-
from typing import Dict, List, Optional, Sequence, Tuple, Type, Union
|
8
|
+
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
|
9
9
|
|
10
10
|
import aiohttp
|
11
11
|
from aiohttp import FormData
|
12
12
|
from pydantic import BaseModel
|
13
|
-
from pydantic_core import to_jsonable_python
|
14
13
|
from typing_extensions import Any, TypeVar
|
15
14
|
|
16
15
|
import pangea.exceptions as pe
|
@@ -56,20 +55,17 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
56
55
|
if data is None:
|
57
56
|
data = {}
|
58
57
|
|
59
|
-
# Normalize.
|
60
|
-
data = cast(dict[str, Any], to_jsonable_python(data))
|
61
|
-
|
62
58
|
if url is None:
|
63
59
|
url = self._url(endpoint)
|
64
60
|
|
65
61
|
# Set config ID if available
|
66
|
-
if self.config_id and data.get("config_id", None) is None:
|
67
|
-
data["config_id"] = self.config_id
|
62
|
+
if self.config_id and data.get("config_id", None) is None: # type: ignore[union-attr]
|
63
|
+
data["config_id"] = self.config_id # type: ignore[index]
|
68
64
|
|
69
65
|
self.logger.debug(
|
70
66
|
json.dumps({"service": self.service, "action": "post", "url": url, "data": data}, default=default_encoder)
|
71
67
|
)
|
72
|
-
transfer_method = data.get("transfer_method", None)
|
68
|
+
transfer_method = data.get("transfer_method", None) # type: ignore[union-attr]
|
73
69
|
|
74
70
|
if files and type(data) is dict and (transfer_method == TransferMethod.POST_URL.value):
|
75
71
|
requests_response = await self._full_post_presigned_url(
|
@@ -186,7 +182,20 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
186
182
|
if resp.status < 200 or resp.status >= 300:
|
187
183
|
raise pe.PresignedUploadError(f"presigned PUT failure: {resp.status}", await resp.text())
|
188
184
|
|
189
|
-
async def download_file(self, url: str, filename:
|
185
|
+
async def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
186
|
+
"""
|
187
|
+
Download file
|
188
|
+
|
189
|
+
Download a file from the specified URL and save it with the given
|
190
|
+
filename.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
url: URL of the file to download
|
194
|
+
filename: Name to save the downloaded file as. If not provided, the
|
195
|
+
filename will be determined from the Content-Disposition header or
|
196
|
+
the URL.
|
197
|
+
"""
|
198
|
+
|
190
199
|
self.logger.debug(
|
191
200
|
json.dumps(
|
192
201
|
{
|
@@ -1,11 +1,9 @@
|
|
1
|
-
from .ai_guard import AIGuardAsync
|
2
1
|
from .audit import AuditAsync
|
3
2
|
from .authn import AuthNAsync
|
4
3
|
from .authz import AuthZAsync
|
5
4
|
from .embargo import EmbargoAsync
|
6
5
|
from .file_scan import FileScanAsync
|
7
6
|
from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
|
8
|
-
from .prompt_guard import PromptGuardAsync
|
9
7
|
from .redact import RedactAsync
|
10
8
|
from .sanitize import SanitizeAsync
|
11
9
|
from .share import ShareAsync
|
pangea/asyncio/services/audit.py
CHANGED
@@ -174,14 +174,16 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
|
|
174
174
|
verbose: Optional[bool] = None,
|
175
175
|
) -> PangeaResponse[LogResult]:
|
176
176
|
"""
|
177
|
-
Log an
|
177
|
+
Log an event
|
178
178
|
|
179
179
|
Create a log entry in the Secure Audit Log.
|
180
|
+
|
180
181
|
Args:
|
181
182
|
event (dict[str, Any]): event to be logged
|
182
183
|
verify (bool, optional): True to verify logs consistency after response.
|
183
184
|
sign_local (bool, optional): True to sign event with local key.
|
184
185
|
verbose (bool, optional): True to get a more verbose response.
|
186
|
+
|
185
187
|
Raises:
|
186
188
|
AuditException: If an audit based api exception happens
|
187
189
|
PangeaAPIException: If an API Error happens
|
@@ -192,13 +194,7 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
|
|
192
194
|
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#log-an-entry).
|
193
195
|
|
194
196
|
Examples:
|
195
|
-
|
196
|
-
log_response = audit.log({"message"="Hello world"}, verbose=False)
|
197
|
-
print(f"Response. Hash: {log_response.result.hash}")
|
198
|
-
except pe.PangeaAPIException as e:
|
199
|
-
print(f"Request Error: {e.response.summary}")
|
200
|
-
for err in e.errors:
|
201
|
-
print(f"\\t{err.detail} \\n")
|
197
|
+
response = await audit.log_event({"message": "hello world"}, verbose=True)
|
202
198
|
"""
|
203
199
|
|
204
200
|
input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
|
@@ -632,9 +628,9 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
|
|
632
628
|
if pub_root is not None:
|
633
629
|
self.pub_roots[tree_size] = pub_root
|
634
630
|
|
635
|
-
await self.
|
631
|
+
await self._fix_consistency_proofs(tree_sizes)
|
636
632
|
|
637
|
-
async def
|
633
|
+
async def _fix_consistency_proofs(self, tree_sizes: Iterable[int]) -> None:
|
638
634
|
# on very rare occasions, the consistency proof in Arweave may be wrong
|
639
635
|
# override it with the proof from pangea (not the root hash, just the proof)
|
640
636
|
for tree_size in tree_sizes:
|
pangea/asyncio/services/authz.py
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
3
|
|
4
|
+
from __future__ import annotations
|
5
|
+
|
4
6
|
from typing import Any, Dict, List, Optional
|
5
7
|
|
6
8
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
9
|
+
from pangea.config import PangeaConfig
|
7
10
|
from pangea.response import PangeaResponse
|
8
11
|
from pangea.services.authz import (
|
9
12
|
CheckRequest,
|
@@ -36,8 +39,8 @@ class AuthZAsync(ServiceBaseAsync):
|
|
36
39
|
|
37
40
|
Examples:
|
38
41
|
import os
|
42
|
+
from pangea.asyncio.services import AuthZAsync
|
39
43
|
from pangea.config import PangeaConfig
|
40
|
-
from pangea.services import AuthZ
|
41
44
|
|
42
45
|
PANGEA_TOKEN = os.getenv("PANGEA_AUTHZ_TOKEN")
|
43
46
|
|
@@ -49,7 +52,25 @@ class AuthZAsync(ServiceBaseAsync):
|
|
49
52
|
|
50
53
|
service_name = "authz"
|
51
54
|
|
52
|
-
def __init__(
|
55
|
+
def __init__(
|
56
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
57
|
+
) -> None:
|
58
|
+
"""
|
59
|
+
AuthZ client
|
60
|
+
|
61
|
+
Initializes a new AuthZ client.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
token: Pangea API token.
|
65
|
+
config: Configuration.
|
66
|
+
logger_name: Logger name.
|
67
|
+
config_id: Configuration ID.
|
68
|
+
|
69
|
+
Examples:
|
70
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
71
|
+
authz = AuthZAsync(token="pangea_token", config=config)
|
72
|
+
"""
|
73
|
+
|
53
74
|
super().__init__(token, config, logger_name, config_id=config_id)
|
54
75
|
|
55
76
|
async def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
|
pangea/asyncio/services/base.py
CHANGED
@@ -63,7 +63,21 @@ class ServiceBaseAsync(ServiceBase):
|
|
63
63
|
else:
|
64
64
|
raise AttributeError("Need to set exception, response or request_id")
|
65
65
|
|
66
|
-
|
66
|
+
@override
|
67
|
+
async def download_file(self, url: str, filename: str | None = None) -> AttachedFile: # type: ignore[override]
|
68
|
+
"""
|
69
|
+
Download file
|
70
|
+
|
71
|
+
Download a file from the specified URL and save it with the given
|
72
|
+
filename.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
url: URL of the file to download
|
76
|
+
filename: Name to save the downloaded file as. If not provided, the
|
77
|
+
filename will be determined from the Content-Disposition header or
|
78
|
+
the URL.
|
79
|
+
"""
|
80
|
+
|
67
81
|
return await self.request.download_file(url=url, filename=filename)
|
68
82
|
|
69
83
|
async def close(self):
|
pangea/asyncio/services/intel.py
CHANGED
@@ -790,6 +790,7 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
790
790
|
verbose: Optional[bool] = None,
|
791
791
|
raw: Optional[bool] = None,
|
792
792
|
provider: Optional[str] = None,
|
793
|
+
cursor: Optional[str] = None,
|
793
794
|
) -> PangeaResponse[m.UserBreachedResult]:
|
794
795
|
"""
|
795
796
|
Look up breached users
|
@@ -808,6 +809,7 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
808
809
|
verbose (bool, optional): Echo the API parameters in the response
|
809
810
|
raw (bool, optional): Include raw data from this provider
|
810
811
|
provider (str, optional): Use reputation data from this provider: "crowdstrike"
|
812
|
+
cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
|
811
813
|
|
812
814
|
Raises:
|
813
815
|
PangeaAPIException: If an API Error happens
|
@@ -835,6 +837,7 @@ class UserIntelAsync(ServiceBaseAsync):
|
|
835
837
|
end=end,
|
836
838
|
verbose=verbose,
|
837
839
|
raw=raw,
|
840
|
+
cursor=cursor,
|
838
841
|
)
|
839
842
|
return await self.request.post(
|
840
843
|
"v1/user/breached", m.UserBreachedResult, data=input.model_dump(exclude_none=True)
|
@@ -65,6 +65,8 @@ class RedactAsync(ServiceBaseAsync):
|
|
65
65
|
rulesets: Optional[List[str]] = None,
|
66
66
|
return_result: Optional[bool] = None,
|
67
67
|
redaction_method_overrides: Optional[m.RedactionMethodOverrides] = None,
|
68
|
+
llm_request: Optional[bool] = None,
|
69
|
+
vault_parameters: Optional[m.VaultParameters] = None,
|
68
70
|
) -> PangeaResponse[m.RedactResult]:
|
69
71
|
"""
|
70
72
|
Redact
|
@@ -81,6 +83,8 @@ class RedactAsync(ServiceBaseAsync):
|
|
81
83
|
rulesets (list[str], optional): An array of redact rulesets short names
|
82
84
|
return_result(bool, optional): Setting this value to false will omit the redacted result only returning count
|
83
85
|
redaction_method_overrides: A set of redaction method overrides for any enabled rule. These methods override the config declared methods
|
86
|
+
llm_request: Boolean flag to enable FPE redaction for LLM requests
|
87
|
+
vault_parameters: A set of vault parameters to use for redaction
|
84
88
|
|
85
89
|
Raises:
|
86
90
|
PangeaAPIException: If an API Error happens
|
@@ -101,6 +105,8 @@ class RedactAsync(ServiceBaseAsync):
|
|
101
105
|
rulesets=rulesets,
|
102
106
|
return_result=return_result,
|
103
107
|
redaction_method_overrides=redaction_method_overrides,
|
108
|
+
llm_request=llm_request,
|
109
|
+
vault_parameters=vault_parameters,
|
104
110
|
)
|
105
111
|
return await self.request.post("v1/redact", m.RedactResult, data=input.model_dump(exclude_none=True))
|
106
112
|
|
@@ -114,6 +120,8 @@ class RedactAsync(ServiceBaseAsync):
|
|
114
120
|
rulesets: Optional[List[str]] = None,
|
115
121
|
return_result: Optional[bool] = None,
|
116
122
|
redaction_method_overrides: Optional[m.RedactionMethodOverrides] = None,
|
123
|
+
llm_request: bool | None = None,
|
124
|
+
vault_parameters: m.VaultParameters | None = None,
|
117
125
|
) -> PangeaResponse[m.StructuredResult]:
|
118
126
|
"""
|
119
127
|
Redact structured
|
@@ -134,6 +142,8 @@ class RedactAsync(ServiceBaseAsync):
|
|
134
142
|
rulesets (list[str], optional): An array of redact rulesets short names
|
135
143
|
return_result(bool, optional): Setting this value to false will omit the redacted result only returning count
|
136
144
|
redaction_method_overrides: A set of redaction method overrides for any enabled rule. These methods override the config declared methods
|
145
|
+
llm_request: Boolean flag to enable FPE redaction for LLM requests
|
146
|
+
vault_parameters: A set of vault parameters to use for redaction
|
137
147
|
|
138
148
|
Raises:
|
139
149
|
PangeaAPIException: If an API Error happens
|
@@ -161,6 +171,8 @@ class RedactAsync(ServiceBaseAsync):
|
|
161
171
|
rulesets=rulesets,
|
162
172
|
return_result=return_result,
|
163
173
|
redaction_method_overrides=redaction_method_overrides,
|
174
|
+
llm_request=llm_request,
|
175
|
+
vault_parameters=vault_parameters,
|
164
176
|
)
|
165
177
|
return await self.request.post(
|
166
178
|
"v1/redact_structured", m.StructuredResult, data=input.model_dump(exclude_none=True)
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import io
|
4
6
|
from typing import List, Optional, Tuple
|
5
7
|
|
6
8
|
import pangea.services.sanitize as m
|
7
9
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
10
|
+
from pangea.config import PangeaConfig
|
8
11
|
from pangea.response import PangeaResponse, TransferMethod
|
9
12
|
from pangea.utils import FileUploadParams, get_file_upload_params
|
10
13
|
|
@@ -16,17 +19,38 @@ class SanitizeAsync(ServiceBaseAsync):
|
|
16
19
|
import os
|
17
20
|
|
18
21
|
# Pangea SDK
|
22
|
+
from pangea.asyncio.services import SanitizeAsync
|
19
23
|
from pangea.config import PangeaConfig
|
20
|
-
from pangea.asyncio.services import Sanitize
|
21
24
|
|
22
25
|
PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
|
23
26
|
config = PangeaConfig(domain="pangea.cloud")
|
24
27
|
|
25
|
-
sanitize =
|
28
|
+
sanitize = SanitizeAsync(token=PANGEA_SANITIZE_TOKEN, config=config)
|
26
29
|
"""
|
27
30
|
|
28
31
|
service_name = "sanitize"
|
29
32
|
|
33
|
+
def __init__(
|
34
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
35
|
+
) -> None:
|
36
|
+
"""
|
37
|
+
Sanitize client
|
38
|
+
|
39
|
+
Initializes a new Sanitize client.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
token: Pangea API token.
|
43
|
+
config: Configuration.
|
44
|
+
logger_name: Logger name.
|
45
|
+
config_id: Configuration ID.
|
46
|
+
|
47
|
+
Examples:
|
48
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
49
|
+
authz = SanitizeAsync(token="pangea_token", config=config)
|
50
|
+
"""
|
51
|
+
|
52
|
+
super().__init__(token, config, logger_name, config_id=config_id)
|
53
|
+
|
30
54
|
async def sanitize(
|
31
55
|
self,
|
32
56
|
transfer_method: TransferMethod = TransferMethod.POST_URL,
|
pangea/asyncio/services/share.py
CHANGED
@@ -7,16 +7,38 @@ from typing import Dict, List, Optional, Tuple, Union
|
|
7
7
|
|
8
8
|
import pangea.services.share.share as m
|
9
9
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
10
|
+
from pangea.config import PangeaConfig
|
10
11
|
from pangea.response import PangeaResponse, TransferMethod
|
11
12
|
from pangea.services.share.file_format import FileFormat
|
12
13
|
from pangea.utils import get_file_size, get_file_upload_params
|
13
14
|
|
14
15
|
|
15
16
|
class ShareAsync(ServiceBaseAsync):
|
16
|
-
"""Share service client."""
|
17
|
+
"""Secure Share service client."""
|
17
18
|
|
18
19
|
service_name = "share"
|
19
20
|
|
21
|
+
def __init__(
|
22
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
23
|
+
) -> None:
|
24
|
+
"""
|
25
|
+
Secure Share client
|
26
|
+
|
27
|
+
Initializes a new Secure Share client.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
token: Pangea API token.
|
31
|
+
config: Configuration.
|
32
|
+
logger_name: Logger name.
|
33
|
+
config_id: Configuration ID.
|
34
|
+
|
35
|
+
Examples:
|
36
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
37
|
+
authz = ShareAsync(token="pangea_token", config=config)
|
38
|
+
"""
|
39
|
+
|
40
|
+
super().__init__(token, config, logger_name, config_id=config_id)
|
41
|
+
|
20
42
|
async def buckets(self) -> PangeaResponse[m.BucketsResult]:
|
21
43
|
"""
|
22
44
|
Buckets
|
pangea/deep_verify.py
CHANGED
@@ -263,8 +263,14 @@ def main():
|
|
263
263
|
audit = init_audit(args.token, args.domain)
|
264
264
|
errors = deep_verify(audit, args.file)
|
265
265
|
|
266
|
-
print("\n\
|
266
|
+
print("\n\nWarnings:")
|
267
|
+
val = errors["not_persisted"]
|
268
|
+
print(f"\tnot_persisted: {val}")
|
269
|
+
|
270
|
+
print("\nTotal errors:")
|
267
271
|
for key, val in errors.items():
|
272
|
+
if key == "not_persisted":
|
273
|
+
continue
|
268
274
|
print(f"\t{key.title()}: {val}")
|
269
275
|
print()
|
270
276
|
|
pangea/dump_audit.py
CHANGED
@@ -63,11 +63,12 @@ def dump_before(audit: Audit, output: io.TextIOWrapper, start: datetime) -> int:
|
|
63
63
|
cnt = 0
|
64
64
|
if search_res.result and search_res.result.count > 0:
|
65
65
|
leaf_index = search_res.result.events[0].leaf_index
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
66
|
+
if leaf_index is not None:
|
67
|
+
for row in reversed(search_res.result.events):
|
68
|
+
if row.leaf_index != leaf_index:
|
69
|
+
break
|
70
|
+
dump_event(output, row, search_res)
|
71
|
+
cnt += 1
|
71
72
|
print(f"Dumping before... {cnt} events")
|
72
73
|
return cnt
|
73
74
|
|
@@ -89,7 +90,7 @@ def dump_after(audit: Audit, output: io.TextIOWrapper, start: datetime, last_eve
|
|
89
90
|
cnt = 0
|
90
91
|
if search_res.result and search_res.result.count > 0:
|
91
92
|
leaf_index = search_res.result.events[0].leaf_index
|
92
|
-
if leaf_index == last_leaf_index:
|
93
|
+
if leaf_index is not None and leaf_index == last_leaf_index:
|
93
94
|
start_idx: int = 1 if last_event_hash == search_res.result.events[0].hash else 0
|
94
95
|
for row in search_res.result.events[start_idx:]:
|
95
96
|
if row.leaf_index != leaf_index:
|
@@ -124,7 +125,7 @@ def dump_page(
|
|
124
125
|
msg = f"Dumping... {search_res.result.count} events"
|
125
126
|
|
126
127
|
if search_res.result.count <= 1:
|
127
|
-
return end, 0
|
128
|
+
return end, 0, True, "", 0
|
128
129
|
|
129
130
|
offset = 0
|
130
131
|
result_id = search_res.result.id
|
pangea/request.py
CHANGED
@@ -6,13 +6,12 @@ import copy
|
|
6
6
|
import json
|
7
7
|
import logging
|
8
8
|
import time
|
9
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Type, Union
|
9
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Type, Union
|
10
10
|
|
11
11
|
import requests
|
12
12
|
from pydantic import BaseModel
|
13
|
-
from pydantic_core import to_jsonable_python
|
14
13
|
from requests.adapters import HTTPAdapter, Retry
|
15
|
-
from requests_toolbelt import MultipartDecoder # type: ignore
|
14
|
+
from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
|
16
15
|
from typing_extensions import TypeVar
|
17
16
|
|
18
17
|
import pangea
|
@@ -233,9 +232,6 @@ class PangeaRequest(PangeaRequestBase):
|
|
233
232
|
if data is None:
|
234
233
|
data = {}
|
235
234
|
|
236
|
-
# Normalize.
|
237
|
-
data = cast(dict[str, Any], to_jsonable_python(data))
|
238
|
-
|
239
235
|
if url is None:
|
240
236
|
url = self._url(endpoint)
|
241
237
|
|
@@ -406,7 +402,20 @@ class PangeaRequest(PangeaRequestBase):
|
|
406
402
|
|
407
403
|
return self._check_response(pangea_response)
|
408
404
|
|
409
|
-
def download_file(self, url: str, filename:
|
405
|
+
def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
406
|
+
"""
|
407
|
+
Download file
|
408
|
+
|
409
|
+
Download a file from the specified URL and save it with the given
|
410
|
+
filename.
|
411
|
+
|
412
|
+
Args:
|
413
|
+
url: URL of the file to download
|
414
|
+
filename: Name to save the downloaded file as. If not provided, the
|
415
|
+
filename will be determined from the Content-Disposition header or
|
416
|
+
the URL.
|
417
|
+
"""
|
418
|
+
|
410
419
|
self.logger.debug(
|
411
420
|
json.dumps(
|
412
421
|
{
|
pangea/services/__init__.py
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
from .ai_guard import AIGuard
|
2
1
|
from .audit.audit import Audit
|
3
2
|
from .authn.authn import AuthN
|
4
3
|
from .authz import AuthZ
|
5
4
|
from .embargo import Embargo
|
6
5
|
from .file_scan import FileScan
|
7
6
|
from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
|
8
|
-
from .prompt_guard import PromptGuard
|
9
7
|
from .redact import Redact
|
10
8
|
from .sanitize import Sanitize
|
11
9
|
from .share.share import Share
|
pangea/services/audit/audit.py
CHANGED
@@ -492,7 +492,7 @@ class Audit(ServiceBase, AuditBase):
|
|
492
492
|
verbose: Optional[bool] = None,
|
493
493
|
) -> PangeaResponse[LogResult]:
|
494
494
|
"""
|
495
|
-
Log an
|
495
|
+
Log an event
|
496
496
|
|
497
497
|
Create a log entry in the Secure Audit Log.
|
498
498
|
|
@@ -501,6 +501,7 @@ class Audit(ServiceBase, AuditBase):
|
|
501
501
|
verify (bool, optional): True to verify logs consistency after response.
|
502
502
|
sign_local (bool, optional): True to sign event with local key.
|
503
503
|
verbose (bool, optional): True to get a more verbose response.
|
504
|
+
|
504
505
|
Raises:
|
505
506
|
AuditException: If an audit based api exception happens
|
506
507
|
PangeaAPIException: If an API Error happens
|
@@ -511,13 +512,7 @@ class Audit(ServiceBase, AuditBase):
|
|
511
512
|
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v1/log).
|
512
513
|
|
513
514
|
Examples:
|
514
|
-
|
515
|
-
log_response = audit.log({"message": "hello world"}, verbose=True)
|
516
|
-
print(f"Response. Hash: {log_response.result.hash}")
|
517
|
-
except pe.PangeaAPIException as e:
|
518
|
-
print(f"Request Error: {e.response.summary}")
|
519
|
-
for err in e.errors:
|
520
|
-
print(f"\\t{err.detail} \\n")
|
515
|
+
response = audit.log_event({"message": "hello world"}, verbose=True)
|
521
516
|
"""
|
522
517
|
|
523
518
|
input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
|
@@ -961,9 +956,9 @@ class Audit(ServiceBase, AuditBase):
|
|
961
956
|
if pub_root is not None:
|
962
957
|
self.pub_roots[tree_size] = pub_root
|
963
958
|
|
964
|
-
self.
|
959
|
+
self._fix_consistency_proofs(tree_sizes)
|
965
960
|
|
966
|
-
def
|
961
|
+
def _fix_consistency_proofs(self, tree_sizes: Iterable[int]) -> None:
|
967
962
|
# on very rare occasions, the consistency proof in Arweave may be wrong
|
968
963
|
# override it with the proof from pangea (not the root hash, just the proof)
|
969
964
|
for tree_size in tree_sizes:
|
pangea/services/authz.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
5
|
import enum
|
5
6
|
from typing import Any, Dict, List, Optional, Union
|
6
7
|
|
8
|
+
from pangea.config import PangeaConfig
|
7
9
|
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponse, PangeaResponseResult
|
8
10
|
from pangea.services.base import ServiceBase
|
9
11
|
|
@@ -171,7 +173,25 @@ class AuthZ(ServiceBase):
|
|
171
173
|
|
172
174
|
service_name = "authz"
|
173
175
|
|
174
|
-
def __init__(
|
176
|
+
def __init__(
|
177
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
178
|
+
) -> None:
|
179
|
+
"""
|
180
|
+
AuthZ client
|
181
|
+
|
182
|
+
Initializes a new AuthZ client.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
token: Pangea API token.
|
186
|
+
config: Configuration.
|
187
|
+
logger_name: Logger name.
|
188
|
+
config_id: Configuration ID.
|
189
|
+
|
190
|
+
Examples:
|
191
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
192
|
+
authz = AuthZ(token="pangea_token", config=config)
|
193
|
+
"""
|
194
|
+
|
175
195
|
super().__init__(token, config, logger_name, config_id=config_id)
|
176
196
|
|
177
197
|
def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
|
pangea/services/base.py
CHANGED
@@ -101,5 +101,18 @@ class ServiceBase(object):
|
|
101
101
|
else:
|
102
102
|
raise AttributeError("Need to set exception, response or request_id")
|
103
103
|
|
104
|
-
def download_file(self, url: str, filename:
|
104
|
+
def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
105
|
+
"""
|
106
|
+
Download file
|
107
|
+
|
108
|
+
Download a file from the specified URL and save it with the given
|
109
|
+
filename.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
url: URL of the file to download
|
113
|
+
filename: Name to save the downloaded file as. If not provided, the
|
114
|
+
filename will be determined from the Content-Disposition header or
|
115
|
+
the URL.
|
116
|
+
"""
|
117
|
+
|
105
118
|
return self.request.download_file(url=url, filename=filename)
|
pangea/services/intel.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import enum
|
4
6
|
import hashlib
|
5
7
|
from typing import Dict, List, Optional
|
@@ -1237,6 +1239,7 @@ class UserBreachedRequest(IntelCommonRequest):
|
|
1237
1239
|
phone_number (str): A phone number to search for. minLength: 7, maxLength: 15.
|
1238
1240
|
start (str): Earliest date for search
|
1239
1241
|
end (str): Latest date for search
|
1242
|
+
cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
|
1240
1243
|
"""
|
1241
1244
|
|
1242
1245
|
email: Optional[str] = None
|
@@ -1245,6 +1248,7 @@ class UserBreachedRequest(IntelCommonRequest):
|
|
1245
1248
|
phone_number: Optional[str] = None
|
1246
1249
|
start: Optional[str] = None
|
1247
1250
|
end: Optional[str] = None
|
1251
|
+
cursor: Optional[str] = None
|
1248
1252
|
|
1249
1253
|
|
1250
1254
|
class UserBreachedBulkRequest(IntelCommonRequest):
|
@@ -1387,6 +1391,7 @@ class UserIntel(ServiceBase):
|
|
1387
1391
|
verbose: Optional[bool] = None,
|
1388
1392
|
raw: Optional[bool] = None,
|
1389
1393
|
provider: Optional[str] = None,
|
1394
|
+
cursor: Optional[str] = None,
|
1390
1395
|
) -> PangeaResponse[UserBreachedResult]:
|
1391
1396
|
"""
|
1392
1397
|
Look up breached users
|
@@ -1405,6 +1410,7 @@ class UserIntel(ServiceBase):
|
|
1405
1410
|
verbose (bool, optional): Echo the API parameters in the response
|
1406
1411
|
raw (bool, optional): Include raw data from this provider
|
1407
1412
|
provider (str, optional): Use reputation data from this provider: "spycloud"
|
1413
|
+
cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
|
1408
1414
|
|
1409
1415
|
Raises:
|
1410
1416
|
PangeaAPIException: If an API Error happens
|
@@ -1432,6 +1438,7 @@ class UserIntel(ServiceBase):
|
|
1432
1438
|
end=end,
|
1433
1439
|
verbose=verbose,
|
1434
1440
|
raw=raw,
|
1441
|
+
cursor=cursor,
|
1435
1442
|
)
|
1436
1443
|
return self.request.post("v1/user/breached", UserBreachedResult, data=input.model_dump(exclude_none=True))
|
1437
1444
|
|
@@ -1592,6 +1599,17 @@ class UserIntel(ServiceBase):
|
|
1592
1599
|
|
1593
1600
|
@staticmethod
|
1594
1601
|
def is_password_breached(response: PangeaResponse[UserBreachedResult], hash: str) -> PasswordStatus:
|
1602
|
+
"""
|
1603
|
+
Check if a password was breached
|
1604
|
+
|
1605
|
+
Helper function that simplifies searching the response's raw data for
|
1606
|
+
the full hash.
|
1607
|
+
|
1608
|
+
Args:
|
1609
|
+
response: API response from an earlier request
|
1610
|
+
hash: Password hash
|
1611
|
+
"""
|
1612
|
+
|
1595
1613
|
if response.result.raw_data is None: # type: ignore[union-attr]
|
1596
1614
|
raise PangeaException("Need raw data to check if hash is breached. Send request with raw=true")
|
1597
1615
|
|