pangea-sdk 4.1.0__py3-none-any.whl → 4.3.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.
pangea/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
- __version__ = "4.1.0"
1
+ __version__ = "4.3.0"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
5
+ from pangea.file_uploader import FileUploader
5
6
  from pangea.request import PangeaRequest
6
7
  from pangea.response import PangeaResponse
@@ -0,0 +1 @@
1
+ from .file_uploader import FileUploaderAsync
@@ -0,0 +1,39 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ import io
4
+ import logging
5
+ from typing import Dict, Optional
6
+
7
+ from pangea.asyncio.request import PangeaRequestAsync
8
+ from pangea.request import PangeaConfig
9
+ from pangea.response import TransferMethod
10
+
11
+
12
+ class FileUploaderAsync:
13
+ def __init__(self) -> None:
14
+ self.logger = logging.getLogger("pangea")
15
+ self._request: PangeaRequestAsync = PangeaRequestAsync(
16
+ config=PangeaConfig(),
17
+ token="",
18
+ service="FileUploader",
19
+ logger=self.logger,
20
+ )
21
+
22
+ async def upload_file(
23
+ self,
24
+ url: str,
25
+ file: io.BufferedReader,
26
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
27
+ file_details: Optional[Dict] = None,
28
+ ) -> None:
29
+ if transfer_method == TransferMethod.PUT_URL:
30
+ files = [("file", ("filename", file, "application/octet-stream"))]
31
+ await self._request.put_presigned_url(url=url, files=files)
32
+ elif transfer_method == TransferMethod.POST_URL:
33
+ files = [("file", ("filename", file, "application/octet-stream"))]
34
+ await self._request.post_presigned_url(url=url, data=file_details, files=files) # type: ignore[arg-type]
35
+ else:
36
+ raise ValueError(f"Transfer method not supported: {transfer_method}")
37
+
38
+ async def close(self) -> None:
39
+ await self._request.session.close()
pangea/asyncio/request.py CHANGED
@@ -32,7 +32,7 @@ class PangeaRequestAsync(PangeaRequestBase):
32
32
  endpoint: str,
33
33
  result_class: Type[TResult],
34
34
  data: Union[str, Dict] = {},
35
- files: List[Tuple] = [],
35
+ files: Optional[List[Tuple]] = None,
36
36
  poll_result: bool = True,
37
37
  url: Optional[str] = None,
38
38
  ) -> PangeaResponse[TResult]:
@@ -250,7 +250,7 @@ class PangeaRequestAsync(PangeaRequestBase):
250
250
  url: str,
251
251
  headers: Dict = {},
252
252
  data: Union[str, Dict] = {},
253
- files: List[Tuple] = [],
253
+ files: Optional[List[Tuple]] = [],
254
254
  presigned_url_post: bool = False,
255
255
  ) -> aiohttp.ClientResponse:
256
256
  if files:
@@ -5,4 +5,5 @@ from .embargo import EmbargoAsync
5
5
  from .file_scan import FileScanAsync
6
6
  from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
7
7
  from .redact import RedactAsync
8
+ from .sanitize import SanitizeAsync
8
9
  from .vault import VaultAsync
@@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Union
7
7
  import pangea.services.authn.models as m
8
8
  from pangea.asyncio.services.base import ServiceBaseAsync
9
9
  from pangea.config import PangeaConfig
10
- from pangea.response import PangeaResponse
10
+ from pangea.response import PangeaResponse, PangeaResponseResult
11
11
 
12
12
  SERVICE_NAME = "authn"
13
13
 
@@ -399,6 +399,25 @@ class AuthNAsync(ServiceBaseAsync):
399
399
  "v2/client/password/change", m.ClientPasswordChangeResult, data=input.model_dump(exclude_none=True)
400
400
  )
401
401
 
402
+ async def expire(self, user_id: str) -> PangeaResponse[PangeaResponseResult]:
403
+ """
404
+ Expire a user's password
405
+
406
+ Expire a user's password.
407
+
408
+ OperationId: authn_post_v2_user_password_expire
409
+
410
+ Args:
411
+ user_id: The identity of a user or a service.
412
+
413
+ Returns:
414
+ A PangeaResponse with an empty object in the response.result field.
415
+
416
+ Examples:
417
+ await authn.client.password.expire("pui_[...]")
418
+ """
419
+ return await self.request.post("v2/user/password/expire", PangeaResponseResult, {"id": user_id})
420
+
402
421
  class TokenAsync(ServiceBaseAsync):
403
422
  service_name = SERVICE_NAME
404
423
 
@@ -1,7 +1,7 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
3
 
4
- from typing import Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional
5
5
 
6
6
  from pangea.asyncio.services.base import ServiceBaseAsync
7
7
  from pangea.response import PangeaResponse
@@ -162,7 +162,7 @@ class AuthZAsync(ServiceBaseAsync):
162
162
  action: str,
163
163
  subject: Subject,
164
164
  debug: Optional[bool] = None,
165
- attributes: Optional[Dict[str, Union[int, str]]] = None,
165
+ attributes: Optional[Dict[str, Any]] = None,
166
166
  ) -> PangeaResponse[CheckResult]:
167
167
  """Perform a check request.
168
168
 
@@ -173,7 +173,7 @@ class AuthZAsync(ServiceBaseAsync):
173
173
  action (str): The action to check.
174
174
  subject (Subject): The subject to check.
175
175
  debug (Optional[bool]): Setting this value to True will provide a detailed analysis of the check.
176
- attributes (Optional[Dict[str, Union[int, str]]]): Additional attributes for the check.
176
+ attributes (Optional[Dict[str, Any]]): Additional attributes for the check.
177
177
 
178
178
  Raises:
179
179
  PangeaAPIException: If an API Error happens.
@@ -195,8 +195,10 @@ class AuthZAsync(ServiceBaseAsync):
195
195
  input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
196
196
  return await self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
197
197
 
198
- async def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
199
- """List resources.
198
+ async def list_resources(
199
+ self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
200
+ ) -> PangeaResponse[ListResourcesResult]:
201
+ """List resources. (Beta)
200
202
 
201
203
  Given a type, action, and subject, list all the resources in the
202
204
  type that the subject has access to the action with.
@@ -205,6 +207,7 @@ class AuthZAsync(ServiceBaseAsync):
205
207
  type (str): The type to filter resources.
206
208
  action (str): The action to filter resources.
207
209
  subject (Subject): The subject to filter resources.
210
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
208
211
 
209
212
  Raises:
210
213
  PangeaAPIException: If an API Error happens.
@@ -222,13 +225,15 @@ class AuthZAsync(ServiceBaseAsync):
222
225
  )
223
226
  """
224
227
 
225
- input_data = ListResourcesRequest(type=type, action=action, subject=subject)
228
+ input_data = ListResourcesRequest(type=type, action=action, subject=subject, attributes=attributes)
226
229
  return await self.request.post(
227
230
  "v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
228
231
  )
229
232
 
230
- async def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
231
- """List subjects.
233
+ async def list_subjects(
234
+ self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
235
+ ) -> PangeaResponse[ListSubjectsResult]:
236
+ """List subjects. (Beta)
232
237
 
233
238
  Given a resource and an action, return the list of subjects who have
234
239
  access to the action for the given resource.
@@ -236,6 +241,7 @@ class AuthZAsync(ServiceBaseAsync):
236
241
  Args:
237
242
  resource (Resource): The resource to filter subjects.
238
243
  action (str): The action to filter subjects.
244
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
239
245
 
240
246
  Raises:
241
247
  PangeaAPIException: If an API Error happens.
@@ -252,7 +258,7 @@ class AuthZAsync(ServiceBaseAsync):
252
258
  )
253
259
  """
254
260
 
255
- input_data = ListSubjectsRequest(resource=resource, action=action)
261
+ input_data = ListSubjectsRequest(resource=resource, action=action, attributes=attributes)
256
262
  return await self.request.post(
257
263
  "v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True)
258
264
  )
@@ -59,12 +59,14 @@ class FileScanAsync(ServiceBaseAsync):
59
59
  OperationId: file_scan_post_v1_scan
60
60
 
61
61
  Args:
62
- file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
63
62
  file_path (str, optional): filepath to be opened and scanned
63
+ file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
64
64
  verbose (bool, optional): Echo the API parameters in the response
65
65
  raw (bool, optional): Include raw data from this provider
66
66
  provider (str, optional): Scan file using this provider
67
67
  sync_call (bool, optional): True to wait until server returns a result, False to return immediately and retrieve result asynchronously
68
+ transfer_method (TransferMethod, optional): Transfer method used to upload the file data.
69
+ source_url (str, optional): A URL where the Pangea APIs can fetch the contents of the input file.
68
70
 
69
71
  Raises:
70
72
  PangeaAPIException: If an API Error happens
@@ -77,7 +79,7 @@ class FileScanAsync(ServiceBaseAsync):
77
79
  Examples:
78
80
  try:
79
81
  with open("./path/to/file.pdf", "rb") as f:
80
- response = client.file_scan(file=f, verbose=True, provider="crowdstrike")
82
+ response = await client.file_scan(file=f, verbose=True, provider="crowdstrike")
81
83
  print(f"Response: {response.result}")
82
84
  except pe.PangeaAPIException as e:
83
85
  print(f"Request Error: {e.response.summary}")
@@ -85,6 +87,15 @@ class FileScanAsync(ServiceBaseAsync):
85
87
  print(f"\\t{err.detail} \\n")
86
88
  """
87
89
 
90
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
91
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
92
+
93
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
94
+ raise ValueError(
95
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
96
+ )
97
+
98
+ files: Optional[List[Tuple]] = None
88
99
  if file or file_path:
89
100
  if file_path:
90
101
  file = open(file_path, "rb")
@@ -95,9 +106,9 @@ class FileScanAsync(ServiceBaseAsync):
95
106
  size = params.size
96
107
  else:
97
108
  crc, sha, size = None, None, None
98
- files: List[Tuple] = [("upload", ("filename", file, "application/octet-stream"))]
99
- else:
100
- raise ValueError("Need to set file_path or file arguments")
109
+ files = [("upload", ("filename", file, "application/octet-stream"))]
110
+ elif source_url is None:
111
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
101
112
 
102
113
  input = m.FileScanRequest(
103
114
  verbose=verbose,
@@ -110,7 +121,11 @@ class FileScanAsync(ServiceBaseAsync):
110
121
  source_url=source_url,
111
122
  )
112
123
  data = input.model_dump(exclude_none=True)
113
- return await self.request.post("v1/scan", m.FileScanResult, data=data, files=files, poll_result=sync_call)
124
+ try:
125
+ return await self.request.post("v1/scan", m.FileScanResult, data=data, files=files, poll_result=sync_call)
126
+ finally:
127
+ if file_path and file:
128
+ file.close()
114
129
 
115
130
  async def request_upload_url(
116
131
  self,
@@ -0,0 +1,193 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ import io
4
+ from typing import List, Optional, Tuple
5
+
6
+ import pangea.services.sanitize as m
7
+ from pangea.asyncio.services.base import ServiceBaseAsync
8
+ from pangea.response import PangeaResponse, TransferMethod
9
+ from pangea.utils import FileUploadParams, get_file_upload_params
10
+
11
+
12
+ class SanitizeAsync(ServiceBaseAsync):
13
+ """Sanitize service client.
14
+
15
+ Examples:
16
+ import os
17
+
18
+ # Pangea SDK
19
+ from pangea.config import PangeaConfig
20
+ from pangea.asyncio.services import Sanitize
21
+
22
+ PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
23
+ config = PangeaConfig(domain="pangea.cloud")
24
+
25
+ sanitize = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
26
+ """
27
+
28
+ service_name = "sanitize"
29
+
30
+ async def sanitize(
31
+ self,
32
+ transfer_method: TransferMethod = TransferMethod.POST_URL,
33
+ file_path: Optional[str] = None,
34
+ file: Optional[io.BufferedReader] = None,
35
+ source_url: Optional[str] = None,
36
+ share_id: Optional[str] = None,
37
+ file_scan: Optional[m.SanitizeFile] = None,
38
+ content: Optional[m.SanitizeContent] = None,
39
+ share_output: Optional[m.SanitizeShareOutput] = None,
40
+ size: Optional[int] = None,
41
+ crc32c: Optional[str] = None,
42
+ sha256: Optional[str] = None,
43
+ uploaded_file_name: Optional[str] = None,
44
+ sync_call: bool = True,
45
+ ) -> PangeaResponse[m.SanitizeResult]:
46
+ """
47
+ Sanitize
48
+
49
+ Apply file sanitization actions according to specified rules.
50
+
51
+ OperationId: sanitize_post_v1_sanitize
52
+
53
+ Args:
54
+ transfer_method: The transfer method used to upload the file data.
55
+ file_path: Path to file to sanitize.
56
+ file: File to sanitize.
57
+ source_url: A URL where the file to be sanitized can be downloaded.
58
+ share_id: A Pangea Secure Share ID where the file to be sanitized is stored.
59
+ file_scan: Options for File Scan.
60
+ content: Options for how the file should be sanitized.
61
+ share_output: Integration with Secure Share.
62
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
63
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
64
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
65
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
66
+ sync_call: Whether or not to poll on HTTP/202.
67
+
68
+ Raises:
69
+ PangeaAPIException: If an API error happens.
70
+
71
+ Returns:
72
+ The sanitized file and information on the sanitization that was
73
+ performed.
74
+
75
+ Examples:
76
+ with open("/path/to/file.pdf", "rb") as f:
77
+ response = await sanitize.sanitize(
78
+ file=f,
79
+ transfer_method=TransferMethod.POST_URL,
80
+ uploaded_file_name="uploaded_file",
81
+ )
82
+ """
83
+
84
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
85
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
86
+
87
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
88
+ raise ValueError(
89
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
90
+ )
91
+
92
+ files: Optional[List[Tuple]] = None
93
+ if file or file_path:
94
+ if file_path:
95
+ file = open(file_path, "rb")
96
+ if transfer_method == TransferMethod.POST_URL and (sha256 is None or crc32c is None or size is None):
97
+ params = get_file_upload_params(file) # type: ignore[arg-type]
98
+ crc32c = params.crc_hex if crc32c is None else crc32c
99
+ sha256 = params.sha256_hex if sha256 is None else sha256
100
+ size = params.size if size is None else size
101
+ else:
102
+ crc32c, sha256, size = None, None, None
103
+ files = [("upload", ("filename", file, "application/octet-stream"))]
104
+ elif source_url is None:
105
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
106
+
107
+ input = m.SanitizeRequest(
108
+ transfer_method=transfer_method,
109
+ source_url=source_url,
110
+ share_id=share_id,
111
+ file=file_scan,
112
+ content=content,
113
+ share_output=share_output,
114
+ crc32c=crc32c,
115
+ sha256=sha256,
116
+ size=size,
117
+ uploaded_file_name=uploaded_file_name,
118
+ )
119
+ data = input.model_dump(exclude_none=True)
120
+ try:
121
+ return await self.request.post(
122
+ "v1/sanitize", m.SanitizeResult, data=data, files=files, poll_result=sync_call
123
+ )
124
+ finally:
125
+ if file_path and file is not None:
126
+ file.close()
127
+
128
+ async def request_upload_url(
129
+ self,
130
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
131
+ params: Optional[FileUploadParams] = None,
132
+ file_scan: Optional[m.SanitizeFile] = None,
133
+ content: Optional[m.SanitizeContent] = None,
134
+ share_output: Optional[m.SanitizeShareOutput] = None,
135
+ size: Optional[int] = None,
136
+ crc32c: Optional[str] = None,
137
+ sha256: Optional[str] = None,
138
+ uploaded_file_name: Optional[str] = None,
139
+ ) -> PangeaResponse[m.SanitizeResult]:
140
+ """
141
+ Sanitize via presigned URL
142
+
143
+ Apply file sanitization actions according to specified rules via a
144
+ [presigned URL](https://pangea.cloud/docs/api/transfer-methods).
145
+
146
+ OperationId: sanitize_post_v1_sanitize 2
147
+
148
+ Args:
149
+ transfer_method: The transfer method used to upload the file data.
150
+ params: File upload parameters.
151
+ file_scan: Options for File Scan.
152
+ content: Options for how the file should be sanitized.
153
+ share_output: Integration with Secure Share.
154
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
155
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
156
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
157
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
158
+
159
+ Raises:
160
+ PangeaAPIException: If an API error happens.
161
+
162
+ Returns:
163
+ A presigned URL.
164
+
165
+ Examples:
166
+ presignedUrl = await sanitize.request_upload_url(
167
+ transfer_method=TransferMethod.PUT_URL,
168
+ uploaded_file_name="uploaded_file",
169
+ )
170
+
171
+ # Upload file to `presignedUrl.accepted_result.put_url`.
172
+
173
+ # Poll for Sanitize's result.
174
+ response: PangeaResponse[SanitizeResult] = await sanitize.poll_result(response=presignedUrl)
175
+ """
176
+
177
+ input = m.SanitizeRequest(
178
+ transfer_method=transfer_method,
179
+ file=file_scan,
180
+ content=content,
181
+ share_output=share_output,
182
+ crc32c=crc32c,
183
+ sha256=sha256,
184
+ size=size,
185
+ uploaded_file_name=uploaded_file_name,
186
+ )
187
+ if params is not None and (transfer_method == TransferMethod.POST_URL):
188
+ input.crc32c = params.crc_hex
189
+ input.sha256 = params.sha256_hex
190
+ input.size = params.size
191
+
192
+ data = input.model_dump(exclude_none=True)
193
+ return await self.request.request_presigned_url("v1/sanitize", m.SanitizeResult, data=data)
@@ -0,0 +1,35 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ import io
4
+ import logging
5
+ from typing import Dict, Optional
6
+
7
+ from pangea.request import PangeaConfig, PangeaRequest
8
+ from pangea.response import TransferMethod
9
+
10
+
11
+ class FileUploader:
12
+ def __init__(self):
13
+ self.logger = logging.getLogger("pangea")
14
+ self._request = PangeaRequest(
15
+ config=PangeaConfig(),
16
+ token="",
17
+ service="FileUploader",
18
+ logger=self.logger,
19
+ )
20
+
21
+ def upload_file(
22
+ self,
23
+ url: str,
24
+ file: io.BufferedReader,
25
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
26
+ file_details: Optional[Dict] = None,
27
+ ):
28
+ if transfer_method == TransferMethod.PUT_URL:
29
+ files = [("file", ("filename", file, "application/octet-stream"))]
30
+ self._request.put_presigned_url(url=url, files=files)
31
+ elif transfer_method == TransferMethod.POST_URL:
32
+ files = [("file", ("filename", file, "application/octet-stream"))]
33
+ self._request.post_presigned_url(url=url, data=file_details, files=files)
34
+ else:
35
+ raise ValueError(f"Transfer method not supported: {transfer_method}")
pangea/request.py CHANGED
@@ -182,7 +182,7 @@ class PangeaRequestBase(object):
182
182
  elif status == ResponseStatus.VAULT_ITEM_NOT_FOUND.value:
183
183
  raise pe.VaultItemNotFound(summary, response)
184
184
  elif status == ResponseStatus.NOT_FOUND.value:
185
- raise pe.NotFound(str(response.raw_response.url) if response.raw_response is not None else "", response) # type: ignore[arg-type]
185
+ raise pe.NotFound(str(response.raw_response.url) if response.raw_response is not None else "", response)
186
186
  elif status == ResponseStatus.INTERNAL_SERVER_ERROR.value:
187
187
  raise pe.InternalServerError(response)
188
188
  elif status == ResponseStatus.ACCEPTED.value:
pangea/response.py CHANGED
@@ -37,10 +37,24 @@ class AttachedFile(object):
37
37
 
38
38
 
39
39
  class TransferMethod(str, enum.Enum):
40
+ """Transfer methods for uploading file data."""
41
+
40
42
  MULTIPART = "multipart"
41
43
  POST_URL = "post-url"
42
44
  PUT_URL = "put-url"
43
45
  SOURCE_URL = "source-url"
46
+ """
47
+ A `source-url` is a caller-specified URL where the Pangea APIs can fetch the
48
+ contents of the input file. When calling a Pangea API with a
49
+ `transfer_method` of `source-url`, you must also specify a `source_url`
50
+ input parameter that provides a URL to the input file. The source URL can be
51
+ a presigned URL created by the caller, and it will be used to download the
52
+ content of the input file. The `source-url` transfer method is useful when
53
+ you already have a file in your storage and can provide a URL from which
54
+ Pangea API can fetch the input file—there is no need to transfer it to
55
+ Pangea with a separate POST or PUT request.
56
+ """
57
+
44
58
  DEST_URL = "dest-url"
45
59
 
46
60
  def __str__(self):
@@ -222,4 +236,4 @@ class PangeaResponse(ResponseHeader, Generic[T]):
222
236
 
223
237
  @property
224
238
  def url(self) -> str:
225
- return str(self.raw_response.url) # type: ignore[arg-type,union-attr]
239
+ return str(self.raw_response.url) # type: ignore[union-attr]
@@ -5,4 +5,5 @@ from .embargo import Embargo
5
5
  from .file_scan import FileScan
6
6
  from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
7
7
  from .redact import Redact
8
+ from .sanitize import Sanitize
8
9
  from .vault.vault import Vault
@@ -96,7 +96,7 @@ class Signer:
96
96
 
97
97
  for func in (serialization.load_pem_private_key, serialization.load_ssh_private_key):
98
98
  try:
99
- return func(private_key, None) # type: ignore[operator]
99
+ return func(private_key, None)
100
100
  except exceptions.UnsupportedAlgorithm as e:
101
101
  raise e
102
102
  except ValueError:
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Union
6
6
 
7
7
  import pangea.services.authn.models as m
8
8
  from pangea.config import PangeaConfig
9
- from pangea.response import PangeaResponse
9
+ from pangea.response import PangeaResponse, PangeaResponseResult
10
10
  from pangea.services.base import ServiceBase
11
11
 
12
12
  SERVICE_NAME = "authn"
@@ -363,10 +363,10 @@ class AuthN(ServiceBase):
363
363
 
364
364
  def __init__(
365
365
  self,
366
- token,
367
- config=None,
368
- logger_name="pangea",
369
- ):
366
+ token: str,
367
+ config: PangeaConfig | None = None,
368
+ logger_name: str = "pangea",
369
+ ) -> None:
370
370
  super().__init__(token, config, logger_name=logger_name)
371
371
 
372
372
  def change(
@@ -399,6 +399,25 @@ class AuthN(ServiceBase):
399
399
  "v2/client/password/change", m.ClientPasswordChangeResult, data=input.model_dump(exclude_none=True)
400
400
  )
401
401
 
402
+ def expire(self, user_id: str) -> PangeaResponse[PangeaResponseResult]:
403
+ """
404
+ Expire a user's password
405
+
406
+ Expire a user's password.
407
+
408
+ OperationId: authn_post_v2_user_password_expire
409
+
410
+ Args:
411
+ user_id: The identity of a user or a service.
412
+
413
+ Returns:
414
+ A PangeaResponse with an empty object in the response.result field.
415
+
416
+ Examples:
417
+ authn.client.password.expire("pui_[...]")
418
+ """
419
+ return self.request.post("v2/user/password/expire", PangeaResponseResult, {"id": user_id})
420
+
402
421
  class Token(ServiceBase):
403
422
  service_name = SERVICE_NAME
404
423