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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "5.2.0beta2"
1
+ __version__ = "5.2.1"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
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, cast
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: Optional[str] = None) -> AttachedFile:
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
@@ -174,14 +174,16 @@ class AuditAsync(ServiceBaseAsync, AuditBase):
174
174
  verbose: Optional[bool] = None,
175
175
  ) -> PangeaResponse[LogResult]:
176
176
  """
177
- Log an entry
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
- try:
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.fix_consistency_proofs(tree_sizes)
631
+ await self._fix_consistency_proofs(tree_sizes)
636
632
 
637
- async def fix_consistency_proofs(self, tree_sizes: Iterable[int]):
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:
@@ -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__(self, token: str, config=None, logger_name="pangea", config_id: Optional[str] = None):
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]:
@@ -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
- async def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile: # type: ignore[override]
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):
@@ -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 = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
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,
@@ -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\nTotal errors:")
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
- for row in reversed(search_res.result.events):
67
- if row.leaf_index != leaf_index:
68
- break
69
- dump_event(output, row, search_res)
70
- cnt += 1
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 # type: ignore[return-value]
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, cast
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: Optional[str] = None) -> AttachedFile:
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
  {
@@ -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
@@ -492,7 +492,7 @@ class Audit(ServiceBase, AuditBase):
492
492
  verbose: Optional[bool] = None,
493
493
  ) -> PangeaResponse[LogResult]:
494
494
  """
495
- Log an entry
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
- try:
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.fix_consistency_proofs(tree_sizes)
959
+ self._fix_consistency_proofs(tree_sizes)
965
960
 
966
- def fix_consistency_proofs(self, tree_sizes: Iterable[int]):
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__(self, token: str, config=None, logger_name="pangea", config_id: Optional[str] = None):
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: Optional[str] = None) -> AttachedFile:
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