pangea-sdk 3.2.0__tar.gz → 3.4.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/PKG-INFO +13 -13
  2. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/README.md +10 -10
  3. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/__init__.py +1 -1
  4. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/request.py +127 -96
  5. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/base.py +20 -3
  6. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/file_scan.py +75 -5
  7. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/redact.py +2 -7
  8. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/config.py +2 -2
  9. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/request.py +135 -96
  10. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/response.py +13 -2
  11. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/audit/audit.py +7 -7
  12. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/authn/authn.py +4 -1
  13. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/authn/models.py +2 -1
  14. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/base.py +17 -4
  15. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/file_scan.py +68 -11
  16. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/redact.py +2 -7
  17. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/utils.py +9 -2
  18. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pyproject.toml +3 -3
  19. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/__init__.py +0 -0
  20. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/audit.py +0 -0
  21. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/authn.py +0 -0
  22. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/embargo.py +0 -0
  23. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/intel.py +0 -0
  24. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/asyncio/services/vault.py +0 -0
  25. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/audit_logger.py +0 -0
  26. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/deep_verify.py +0 -0
  27. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/deprecated.py +0 -0
  28. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/dump_audit.py +0 -0
  29. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/exceptions.py +0 -0
  30. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/__init__.py +0 -0
  31. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/audit/exceptions.py +0 -0
  32. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/audit/models.py +0 -0
  33. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/audit/signing.py +0 -0
  34. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/audit/util.py +0 -0
  35. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/embargo.py +0 -0
  36. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/intel.py +0 -0
  37. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/vault/models/asymmetric.py +0 -0
  38. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/vault/models/common.py +0 -0
  39. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/vault/models/secret.py +0 -0
  40. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/vault/models/symmetric.py +0 -0
  41. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/services/vault/vault.py +2 -2
  42. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/tools.py +0 -0
  43. {pangea_sdk-3.2.0 → pangea_sdk-3.4.0}/pangea/verify_audit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pangea-sdk
3
- Version: 3.2.0
3
+ Version: 3.4.0
4
4
  Summary: Pangea API SDK
5
5
  License: MIT
6
6
  Keywords: Pangea,SDK,Audit
@@ -15,10 +15,10 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Topic :: Software Development
17
17
  Classifier: Topic :: Software Development :: Libraries
18
- Requires-Dist: aiohttp (>=3.8.5,<4.0.0)
18
+ Requires-Dist: aiohttp (>=3.8.6,<4.0.0)
19
19
  Requires-Dist: alive-progress (>=2.4.1,<3.0.0)
20
20
  Requires-Dist: asyncio (>=3.4.3,<4.0.0)
21
- Requires-Dist: cryptography (==41.0.3)
21
+ Requires-Dist: cryptography (==41.0.5)
22
22
  Requires-Dist: deprecated (>=1.2.13,<2.0.0)
23
23
  Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
24
24
  Requires-Dist: pydantic (>=1.10.2,<2.0.0)
@@ -59,18 +59,18 @@ poetry add pangea-sdk
59
59
 
60
60
  ## Usage
61
61
 
62
- For samples apps look at [/examples](https://github.com/pangeacyber/pangea-python/tree/main/examples) folder in this repository. There you will find basic samples apps for each services supported on this SDK. Each service folder has a README.md with intructions to install, setup and run.
62
+ For sample apps, look at the [/examples](https://github.com/pangeacyber/pangea-python/tree/main/examples) folder in this repository. There you will find basic sample apps for each of the services supported on this SDK. Each service folder has a README.md with instructions to install, setup, and run the sample app.
63
63
 
64
64
 
65
65
  ## Asyncio support
66
66
 
67
- We have added support to asyncio library using aiohttp in order to support async/await calls to all our services.
67
+ We have added support to the asyncio library using aiohttp in order to support async/await calls to all our services.
68
68
  Async services classes are inside [pangea/asyncio](https://github.com/pangeacyber/pangea-python/tree/main/packages/pangea-sdk/pangea/asyncio) folder, and examples about how to use them are in [/examples/asyncio](https://github.com/pangeacyber/pangea-python/tree/main/examples/asyncio).
69
69
 
70
70
 
71
71
  ### Secure Audit Service - Integrity Tools
72
72
 
73
- Python Pangea SDK include also some extra features to validate Audit Service log's integrity. Here we explain how to run them.
73
+ The Python Pangea SDK also includes some extra features to validate Audit Service log's integrity. Here we explain how to run them.
74
74
 
75
75
  #### Verify audit data
76
76
 
@@ -101,7 +101,7 @@ curl -H "Authorization: Bearer ${PANGEA_TOKEN}" -X POST -H 'Content-Type: applic
101
101
 
102
102
  Download all audit logs for a given time range. Start and end date should be provided,
103
103
  a variety of formats is supported, including ISO-8601. The result is stored in a
104
- jsonl file (one json per line)
104
+ json file (one json per line).
105
105
 
106
106
  ```
107
107
  usage: python -m pangea.dump_audit [-h] [--token TOKEN] [--domain DOMAIN] [--output OUTPUT] start end
@@ -126,8 +126,8 @@ options:
126
126
 
127
127
  #### Perform Exhaustive Verification of Audit Data
128
128
 
129
- This script performs extensive verification on a range of events of the log stream. Appart from verifying the hash
130
- and the membership proof, it checks that there is no omissions in the stream, i.e. all the events are present and properly located.
129
+ This script performs extensive verification on a range of events of the log stream. Apart from verifying the hash
130
+ and the membership proof, it checks that there are no omissions in the stream, i.e. all the events are present and properly located.
131
131
 
132
132
  ```
133
133
  usage: python -m pangea.deep_verify [-h] [--token TOKEN] [--domain DOMAIN] --file FILE
@@ -153,9 +153,9 @@ It accepts multiple file formats:
153
153
 
154
154
  ## Reporting issues and new features
155
155
 
156
- If faced some issue using or testing this SDK or a new feature request feel free to open an issue [clicking here](https://github.com/pangeacyber/pangea-python/issues).
157
- We would need you to provide some basic information like what SDK's version you are using, stack trace if you got it, framework used, and steps to reproduce the issue.
158
- Also feel free to contact [Pangea support](mailto:support@pangea.cloud) by email or send us a message on [Slack](https://pangea.cloud/join-slack/)
156
+ If you run into an issue using or testing this SDK or if you have a new feature request, feel free to open an issue by [clicking here](https://github.com/pangeacyber/pangea-python/issues).
157
+ We would need you to provide some basic information, such as what SDK version you are using, the stack trace if you got it, the framework used, and steps to reproduce the issue.
158
+ Also, feel free to contact [Pangea support](mailto:support@pangea.cloud) by email or send us a message on [Slack](https://pangea.cloud/join-slack/).
159
159
 
160
160
 
161
161
  ## Contributing
@@ -168,5 +168,5 @@ These linters will run on every `git commit` operation.
168
168
 
169
169
  ### Send a PR
170
170
 
171
- If you would like to [send a PR](https://github.com/pangeacyber/pangea-python/pulls) including a new feature or fixing a bug in code or an error in documents we will really appreciate it and after review and approval you will be included in our [contributors list](https://github.com/pangeacyber/pangea-python/blob/main/packages/pangea-sdk/CONTRIBUTING.md)
171
+ If you would like to [send a PR](https://github.com/pangeacyber/pangea-python/pulls) including a new feature or fixing a bug in the code or an error in documents, we really appreciate it and after review and approval, you will be included in our [contributors list](https://github.com/pangeacyber/pangea-python/blob/main/packages/pangea-sdk/CONTRIBUTING.md).
172
172
 
@@ -29,18 +29,18 @@ poetry add pangea-sdk
29
29
 
30
30
  ## Usage
31
31
 
32
- For samples apps look at [/examples](https://github.com/pangeacyber/pangea-python/tree/main/examples) folder in this repository. There you will find basic samples apps for each services supported on this SDK. Each service folder has a README.md with intructions to install, setup and run.
32
+ For sample apps, look at the [/examples](https://github.com/pangeacyber/pangea-python/tree/main/examples) folder in this repository. There you will find basic sample apps for each of the services supported on this SDK. Each service folder has a README.md with instructions to install, setup, and run the sample app.
33
33
 
34
34
 
35
35
  ## Asyncio support
36
36
 
37
- We have added support to asyncio library using aiohttp in order to support async/await calls to all our services.
37
+ We have added support to the asyncio library using aiohttp in order to support async/await calls to all our services.
38
38
  Async services classes are inside [pangea/asyncio](https://github.com/pangeacyber/pangea-python/tree/main/packages/pangea-sdk/pangea/asyncio) folder, and examples about how to use them are in [/examples/asyncio](https://github.com/pangeacyber/pangea-python/tree/main/examples/asyncio).
39
39
 
40
40
 
41
41
  ### Secure Audit Service - Integrity Tools
42
42
 
43
- Python Pangea SDK include also some extra features to validate Audit Service log's integrity. Here we explain how to run them.
43
+ The Python Pangea SDK also includes some extra features to validate Audit Service log's integrity. Here we explain how to run them.
44
44
 
45
45
  #### Verify audit data
46
46
 
@@ -71,7 +71,7 @@ curl -H "Authorization: Bearer ${PANGEA_TOKEN}" -X POST -H 'Content-Type: applic
71
71
 
72
72
  Download all audit logs for a given time range. Start and end date should be provided,
73
73
  a variety of formats is supported, including ISO-8601. The result is stored in a
74
- jsonl file (one json per line)
74
+ json file (one json per line).
75
75
 
76
76
  ```
77
77
  usage: python -m pangea.dump_audit [-h] [--token TOKEN] [--domain DOMAIN] [--output OUTPUT] start end
@@ -96,8 +96,8 @@ options:
96
96
 
97
97
  #### Perform Exhaustive Verification of Audit Data
98
98
 
99
- This script performs extensive verification on a range of events of the log stream. Appart from verifying the hash
100
- and the membership proof, it checks that there is no omissions in the stream, i.e. all the events are present and properly located.
99
+ This script performs extensive verification on a range of events of the log stream. Apart from verifying the hash
100
+ and the membership proof, it checks that there are no omissions in the stream, i.e. all the events are present and properly located.
101
101
 
102
102
  ```
103
103
  usage: python -m pangea.deep_verify [-h] [--token TOKEN] [--domain DOMAIN] --file FILE
@@ -123,9 +123,9 @@ It accepts multiple file formats:
123
123
 
124
124
  ## Reporting issues and new features
125
125
 
126
- If faced some issue using or testing this SDK or a new feature request feel free to open an issue [clicking here](https://github.com/pangeacyber/pangea-python/issues).
127
- We would need you to provide some basic information like what SDK's version you are using, stack trace if you got it, framework used, and steps to reproduce the issue.
128
- Also feel free to contact [Pangea support](mailto:support@pangea.cloud) by email or send us a message on [Slack](https://pangea.cloud/join-slack/)
126
+ If you run into an issue using or testing this SDK or if you have a new feature request, feel free to open an issue by [clicking here](https://github.com/pangeacyber/pangea-python/issues).
127
+ We would need you to provide some basic information, such as what SDK version you are using, the stack trace if you got it, the framework used, and steps to reproduce the issue.
128
+ Also, feel free to contact [Pangea support](mailto:support@pangea.cloud) by email or send us a message on [Slack](https://pangea.cloud/join-slack/).
129
129
 
130
130
 
131
131
  ## Contributing
@@ -138,4 +138,4 @@ These linters will run on every `git commit` operation.
138
138
 
139
139
  ### Send a PR
140
140
 
141
- If you would like to [send a PR](https://github.com/pangeacyber/pangea-python/pulls) including a new feature or fixing a bug in code or an error in documents we will really appreciate it and after review and approval you will be included in our [contributors list](https://github.com/pangeacyber/pangea-python/blob/main/packages/pangea-sdk/CONTRIBUTING.md)
141
+ If you would like to [send a PR](https://github.com/pangeacyber/pangea-python/pulls) including a new feature or fixing a bug in the code or an error in documents, we really appreciate it and after review and approval, you will be included in our [contributors list](https://github.com/pangeacyber/pangea-python/blob/main/packages/pangea-sdk/CONTRIBUTING.md).
@@ -1,4 +1,4 @@
1
- __version__ = "3.2.0"
1
+ __version__ = "3.4.0"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
@@ -51,12 +51,10 @@ class PangeaRequestAsync(PangeaRequestBase):
51
51
  if self.config_id and data.get("config_id", None) is None:
52
52
  data["config_id"] = self.config_id
53
53
 
54
- if (
55
- files is not None
56
- and type(data) is dict
57
- and data.get("transfer_method", None) == TransferMethod.DIRECT.value
58
- ):
59
- requests_response = await self._post_presigned_url(
54
+ transfer_method = data.get("transfer_method", None)
55
+
56
+ if files is not None and type(data) is dict and (transfer_method == TransferMethod.POST_URL.value):
57
+ requests_response = await self._full_post_presigned_url(
60
58
  endpoint, result_class=result_class, data=data, files=files
61
59
  )
62
60
  else:
@@ -64,14 +62,92 @@ class PangeaRequestAsync(PangeaRequestBase):
64
62
  url, headers=self._headers(), data=data, files=files, presigned_url_post=False
65
63
  )
66
64
 
67
- pangea_response = PangeaResponse(
68
- requests_response, result_class=result_class, json=await requests_response.json()
69
- )
65
+ json_resp = await requests_response.json()
66
+ self.logger.debug(json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp}))
67
+
68
+ pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
70
69
  if poll_result:
71
70
  pangea_response = await self._handle_queued_result(pangea_response)
72
71
 
73
72
  return self._check_response(pangea_response)
74
73
 
74
+ async def get(
75
+ self, path: str, result_class: Type[PangeaResponseResult], check_response: bool = True
76
+ ) -> PangeaResponse:
77
+ """Makes the GET call to a Pangea Service endpoint.
78
+
79
+ Args:
80
+ endpoint(str): The Pangea Service API endpoint.
81
+ path(str): Additional URL path
82
+
83
+ Returns:
84
+ PangeaResponse which contains the response in its entirety and
85
+ various properties to retrieve individual fields
86
+ """
87
+
88
+ url = self._url(path)
89
+ self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
90
+
91
+ async with self.session.get(url, headers=self._headers()) as requests_response:
92
+ pangea_response = PangeaResponse(
93
+ requests_response, result_class=result_class, json=await requests_response.json()
94
+ )
95
+
96
+ self.logger.debug(
97
+ json.dumps(
98
+ {"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
99
+ default=default_encoder,
100
+ )
101
+ )
102
+
103
+ if check_response is False:
104
+ return pangea_response
105
+
106
+ return self._check_response(pangea_response)
107
+
108
+ async def poll_result_by_id(
109
+ self, request_id: str, result_class: Union[Type[PangeaResponseResult], dict], check_response: bool = True
110
+ ):
111
+ path = self._get_poll_path(request_id)
112
+ self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_once", "url": path}))
113
+ return await self.get(path, result_class, check_response=check_response)
114
+
115
+ async def poll_result_once(self, response: PangeaResponse, check_response: bool = True):
116
+ request_id = response.request_id
117
+ if not request_id:
118
+ raise pe.PangeaException("Poll result error error: response did not include a 'request_id'")
119
+
120
+ if response.status != ResponseStatus.ACCEPTED.value:
121
+ raise pe.PangeaException("Response already proccesed")
122
+
123
+ return await self.poll_result_by_id(request_id, response.result_class, check_response=check_response)
124
+
125
+ async def post_presigned_url(self, url: str, data: Dict, files: List[Tuple]):
126
+ # Send form request with file and upload_details as body
127
+ resp = await self._http_post(url=url, data=data, files=files, presigned_url_post=True)
128
+ self.logger.debug(
129
+ json.dumps(
130
+ {"service": self.service, "action": "post presigned", "url": url, "response": resp.text},
131
+ default=default_encoder,
132
+ )
133
+ )
134
+
135
+ if resp.status < 200 or resp.status >= 300:
136
+ raise pe.PresignedUploadError(f"presigned POST failure: {resp.status}", resp.text)
137
+
138
+ async def put_presigned_url(self, url: str, files: List[Tuple]):
139
+ # Send put request with file as body
140
+ resp = await self._http_put(url=url, files=files)
141
+ self.logger.debug(
142
+ json.dumps(
143
+ {"service": self.service, "action": "put presigned", "url": url, "response": await resp.text()},
144
+ default=default_encoder,
145
+ )
146
+ )
147
+
148
+ if resp.status < 200 or resp.status >= 300:
149
+ raise pe.PresignedUploadError(f"presigned PUT failure: {resp.status}", await resp.text())
150
+
75
151
  async def _http_post(
76
152
  self,
77
153
  url: str,
@@ -105,7 +181,21 @@ class PangeaRequestAsync(PangeaRequestBase):
105
181
 
106
182
  return await self.session.post(url, headers=headers, data=data_send)
107
183
 
108
- async def _post_presigned_url(
184
+ async def _http_put(
185
+ self,
186
+ url: str,
187
+ files: List[Tuple],
188
+ headers: Dict = {},
189
+ ) -> aiohttp.ClientResponse:
190
+ self.logger.debug(
191
+ json.dumps({"service": self.service, "action": "http_put", "url": url}, default=default_encoder)
192
+ )
193
+ form = FormData()
194
+ name, value = files[0]
195
+ form.add_field(name, value[1], filename=value[0], content_type=value[2])
196
+ return await self.session.put(url, headers=headers, data=form)
197
+
198
+ async def _full_post_presigned_url(
109
199
  self,
110
200
  endpoint: str,
111
201
  result_class: Type[PangeaResponseResult],
@@ -115,61 +205,52 @@ class PangeaRequestAsync(PangeaRequestBase):
115
205
  if len(files) == 0:
116
206
  raise AttributeError("files attribute should have at least 1 file")
117
207
 
208
+ response = await self.request_presigned_url(endpoint=endpoint, result_class=result_class, data=data)
209
+ data_to_presigned = response.accepted_result.post_form_data
210
+ presigned_url = response.accepted_result.post_url
211
+
212
+ await self.post_presigned_url(url=presigned_url, data=data_to_presigned, files=files)
213
+ return response.raw_response
214
+
215
+ async def request_presigned_url(
216
+ self,
217
+ endpoint: str,
218
+ result_class: Type[PangeaResponseResult],
219
+ data: Union[str, Dict] = {},
220
+ ) -> PangeaResponse:
118
221
  # Send request
119
222
  try:
120
223
  # This should return 202 (AcceptedRequestException)
121
224
  resp = await self.post(endpoint=endpoint, result_class=result_class, data=data, poll_result=False)
122
225
  raise pe.PresignedURLException("Should return 202", resp)
123
-
124
226
  except pe.AcceptedRequestException as e:
125
227
  accepted_exception = e
126
228
  except Exception as e:
127
229
  raise e
128
230
 
129
- # Receive 202 with accepted_status
130
- result = await self._poll_presigned_url(accepted_exception)
131
- data_to_presigned = result.accepted_status.upload_details
132
- presigned_url = result.accepted_status.upload_url
231
+ # Receive 202
232
+ return await self._poll_presigned_url(accepted_exception.response)
133
233
 
134
- # Send multipart request with file and upload_details as body
135
- resp = await self._http_post(url=presigned_url, data=data_to_presigned, files=files, presigned_url_post=True)
136
- self.logger.debug(
137
- json.dumps(
138
- {
139
- "service": self.service,
140
- "action": "post presigned",
141
- "url": presigned_url,
142
- "response": await resp.text(),
143
- },
144
- default=default_encoder,
145
- )
146
- )
234
+ async def _poll_presigned_url(self, response: PangeaResponse) -> AcceptedResult:
235
+ if response.http_status != 202:
236
+ raise AttributeError("Response should be 202")
147
237
 
148
- if resp.status < 200 or resp.status >= 300:
149
- raise pe.PresignedUploadError(f"presigned POST failure: {resp.status}", await resp.text())
150
-
151
- return accepted_exception.response.raw_response
152
-
153
- async def _poll_presigned_url(self, initial_exc: pe.AcceptedRequestException) -> AcceptedResult:
154
- if type(initial_exc) is not pe.AcceptedRequestException:
155
- raise AttributeError("Exception should be of type AcceptedRequestException")
156
-
157
- if initial_exc.accepted_result.accepted_status.upload_url:
158
- return initial_exc.accepted_result
238
+ if response.accepted_result is not None and response.accepted_result.has_upload_url:
239
+ return response
159
240
 
160
241
  self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "start"}))
161
242
  retry_count = 1
162
243
  start = time.time()
163
- loop_exc = initial_exc
244
+ loop_resp = response
164
245
 
165
246
  while (
166
- loop_exc.accepted_result is not None
167
- and not loop_exc.accepted_result.accepted_status.upload_url
247
+ loop_resp.accepted_result is not None
248
+ and not loop_resp.accepted_result.has_upload_url
168
249
  and not self._reach_timeout(start)
169
250
  ):
170
251
  await asyncio.sleep(self._get_delay(retry_count, start))
171
252
  try:
172
- await self.poll_result_once(initial_exc.response, check_response=False)
253
+ await self.poll_result_once(response, check_response=False)
173
254
  msg = "Polling presigned url return 200 instead of 202"
174
255
  self.logger.debug(
175
256
  json.dumps(
@@ -179,6 +260,7 @@ class PangeaRequestAsync(PangeaRequestBase):
179
260
  raise pe.PangeaException(msg)
180
261
  except pe.AcceptedRequestException as e:
181
262
  retry_count += 1
263
+ loop_resp = e.response
182
264
  loop_exc = e
183
265
  except Exception as e:
184
266
  self.logger.debug(
@@ -190,8 +272,8 @@ class PangeaRequestAsync(PangeaRequestBase):
190
272
 
191
273
  self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "exit"}))
192
274
 
193
- if loop_exc.accepted_result is not None and not loop_exc.accepted_result.accepted_status.upload_url:
194
- return loop_exc.accepted_result
275
+ if loop_resp.accepted_result is not None and not loop_resp.accepted_result.has_upload_url:
276
+ return loop_resp
195
277
  else:
196
278
  raise loop_exc
197
279
 
@@ -207,57 +289,6 @@ class PangeaRequestAsync(PangeaRequestBase):
207
289
 
208
290
  return response
209
291
 
210
- async def get(
211
- self, path: str, result_class: Type[PangeaResponseResult], check_response: bool = True
212
- ) -> PangeaResponse:
213
- """Makes the GET call to a Pangea Service endpoint.
214
-
215
- Args:
216
- endpoint(str): The Pangea Service API endpoint.
217
- path(str): Additional URL path
218
-
219
- Returns:
220
- PangeaResponse which contains the response in its entirety and
221
- various properties to retrieve individual fields
222
- """
223
-
224
- url = self._url(path)
225
- self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
226
-
227
- async with self.session.get(url, headers=self._headers()) as requests_response:
228
- pangea_response = PangeaResponse(
229
- requests_response, result_class=result_class, json=await requests_response.json()
230
- )
231
-
232
- self.logger.debug(
233
- json.dumps(
234
- {"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
235
- default=default_encoder,
236
- )
237
- )
238
-
239
- if check_response is False:
240
- return pangea_response
241
-
242
- return self._check_response(pangea_response)
243
-
244
- async def poll_result_by_id(
245
- self, request_id: str, result_class: Union[Type[PangeaResponseResult], dict], check_response: bool = True
246
- ):
247
- path = self._get_poll_path(request_id)
248
- self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_once", "url": path}))
249
- return await self.get(path, result_class, check_response=check_response)
250
-
251
- async def poll_result_once(self, response: PangeaResponse, check_response: bool = True):
252
- request_id = response.request_id
253
- if not request_id:
254
- raise pe.PangeaException("Poll result error error: response did not include a 'request_id'")
255
-
256
- if response.status != ResponseStatus.ACCEPTED.value:
257
- raise pe.PangeaException("Response already proccesed")
258
-
259
- return await self.poll_result_by_id(request_id, response.result_class, check_response=check_response)
260
-
261
292
  async def _poll_result_retry(self, response: PangeaResponse) -> PangeaResponse:
262
293
  retry_count = 1
263
294
  start = time.time()
@@ -1,9 +1,11 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
3
 
4
+ from typing import Optional, Type, Union
5
+
4
6
  from pangea.asyncio.request import PangeaRequestAsync
5
7
  from pangea.exceptions import AcceptedRequestException
6
- from pangea.response import PangeaResponse
8
+ from pangea.response import PangeaResponse, PangeaResponseResult
7
9
  from pangea.services.base import ServiceBase
8
10
 
9
11
 
@@ -21,7 +23,13 @@ class ServiceBaseAsync(ServiceBase):
21
23
 
22
24
  return self._request
23
25
 
24
- async def poll_result(self, exception: AcceptedRequestException) -> PangeaResponse:
26
+ async def poll_result(
27
+ self,
28
+ exception: Optional[AcceptedRequestException] = None,
29
+ response: Optional[PangeaResponse] = None,
30
+ request_id: Optional[str] = None,
31
+ result_class: Union[Type[PangeaResponseResult], dict] = dict,
32
+ ) -> PangeaResponse:
25
33
  """
26
34
  Poll result
27
35
 
@@ -39,7 +47,16 @@ class ServiceBaseAsync(ServiceBase):
39
47
  Examples:
40
48
  response = service.poll_result(exception)
41
49
  """
42
- return await self.request.poll_result_once(exception.response, check_response=True)
50
+ if exception is not None:
51
+ return await self.request.poll_result_once(exception.response, check_response=True)
52
+ elif response is not None:
53
+ return await self.request.poll_result_once(response, check_response=True)
54
+ elif request_id is not None:
55
+ return await self.request.poll_result_by_id(
56
+ request_id=request_id, result_class=result_class, check_response=True
57
+ )
58
+ else:
59
+ raise AttributeError("Need to set exception, response or request_id")
43
60
 
44
61
  async def close(self):
45
62
  await self.request.session.close()
@@ -1,11 +1,14 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
3
  import io
4
- from typing import Optional
4
+ import logging
5
+ from typing import Dict, Optional
5
6
 
6
7
  import pangea.services.file_scan as m
7
- from pangea.response import PangeaResponse
8
- from pangea.utils import get_presigned_url_upload_params
8
+ from pangea.asyncio.request import PangeaRequestAsync
9
+ from pangea.request import PangeaConfig
10
+ from pangea.response import PangeaResponse, TransferMethod
11
+ from pangea.utils import FileUploadParams, get_file_upload_params
9
12
 
10
13
  from .base import ServiceBaseAsync
11
14
 
@@ -46,6 +49,8 @@ class FileScanAsync(ServiceBaseAsync):
46
49
  raw: Optional[bool] = None,
47
50
  provider: Optional[str] = None,
48
51
  sync_call: bool = True,
52
+ transfer_method: TransferMethod = TransferMethod.POST_URL,
53
+ source_url: Optional[str] = None,
49
54
  ) -> PangeaResponse[m.FileScanResult]:
50
55
  """
51
56
  Scan
@@ -84,13 +89,78 @@ class FileScanAsync(ServiceBaseAsync):
84
89
  if file or file_path:
85
90
  if file_path:
86
91
  file = open(file_path, "rb")
87
- crc, sha, size, _ = get_presigned_url_upload_params(file)
92
+ if transfer_method == TransferMethod.POST_URL:
93
+ params = get_file_upload_params(file)
94
+ crc = params.crc_hex
95
+ sha = params.sha256_hex
96
+ size = params.size
97
+ else:
98
+ crc, sha, size = None, None, None
88
99
  files = [("upload", ("filename", file, "application/octet-stream"))]
89
100
  else:
90
101
  raise ValueError("Need to set file_path or file arguments")
91
102
 
92
103
  input = m.FileScanRequest(
93
- verbose=verbose, raw=raw, provider=provider, transfer_crc32c=crc, transfer_sha256=sha, transfer_size=size
104
+ verbose=verbose,
105
+ raw=raw,
106
+ provider=provider,
107
+ crc32c=crc,
108
+ sha256=sha,
109
+ size=size,
110
+ transfer_method=transfer_method,
111
+ source_url=source_url,
94
112
  )
95
113
  data = input.dict(exclude_none=True)
96
114
  return await self.request.post("v1/scan", m.FileScanResult, data=data, files=files, poll_result=sync_call)
115
+
116
+ async def request_upload_url(
117
+ self,
118
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
119
+ params: Optional[FileUploadParams] = None,
120
+ verbose: Optional[bool] = None,
121
+ raw: Optional[bool] = None,
122
+ provider: Optional[str] = None,
123
+ ) -> PangeaResponse[m.FileScanResult]:
124
+ input = m.FileScanRequest(
125
+ verbose=verbose,
126
+ raw=raw,
127
+ provider=provider,
128
+ transfer_method=transfer_method,
129
+ )
130
+ if params is not None and (transfer_method == TransferMethod.POST_URL):
131
+ input.crc32c = params.crc_hex
132
+ input.sha256 = params.sha256_hex
133
+ input.size = params.size
134
+
135
+ data = input.dict(exclude_none=True)
136
+ return await self.request.request_presigned_url("v1/scan", m.FileScanResult, data=data)
137
+
138
+
139
+ class FileUploaderAsync:
140
+ def __init__(self):
141
+ self.logger = logging.getLogger("pangea")
142
+ self._request: PangeaRequestAsync = PangeaRequestAsync(
143
+ config=PangeaConfig(),
144
+ token="",
145
+ service="FileScanUploader",
146
+ logger=self.logger,
147
+ )
148
+
149
+ async def upload_file(
150
+ self,
151
+ url: str,
152
+ file: io.BufferedReader,
153
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
154
+ file_details: Optional[Dict] = None,
155
+ ):
156
+ if transfer_method == TransferMethod.PUT_URL:
157
+ files = [("file", ("filename", file, "application/octet-stream"))]
158
+ await self._request.put_presigned_url(url=url, files=files)
159
+ elif transfer_method == TransferMethod.POST_URL:
160
+ files = [("file", ("filename", file, "application/octet-stream"))]
161
+ await self._request.post_presigned_url(url=url, data=file_details, files=files)
162
+ else:
163
+ raise ValueError(f"Transfer method not supported: {transfer_method}")
164
+
165
+ async def close(self):
166
+ await self._request.session.close()
@@ -36,13 +36,8 @@ class RedactAsync(ServiceBaseAsync):
36
36
 
37
37
  service_name = "redact"
38
38
 
39
- def __init__(
40
- self,
41
- token,
42
- config=None,
43
- logger_name="pangea",
44
- ):
45
- super().__init__(token, config, logger_name)
39
+ def __init__(self, token, config=None, logger_name="pangea", config_id: Optional[str] = None):
40
+ super().__init__(token, config, logger_name, config_id=config_id)
46
41
 
47
42
  async def redact(
48
43
  self,
@@ -10,7 +10,7 @@ class PangeaConfig:
10
10
  """Holds run time configuration information used by SDK components."""
11
11
 
12
12
  """
13
- Used to set pangea domain (and port if needed), it should not include service subdomain
13
+ Used to set Pangea domain (and port if needed), it should not include service subdomain
14
14
  just for particular use cases when environment = "local", domain could be set to an url including:
15
15
  scheme (http:// or https://), subdomain, domain and port.
16
16
 
@@ -19,7 +19,7 @@ class PangeaConfig:
19
19
 
20
20
  """
21
21
  Used to generate service url.
22
- It should be only 'production' or 'local' in case of particular services that can run locally as Redact
22
+ It should be only 'production' or 'local' in cases of particular services that can run locally as Redact.
23
23
 
24
24
  """
25
25
  environment: str = "production"