1
- import asyncio , uuid , requests , urllib , datetime
1
+ import asyncio , uuid , requests , urllib , datetime , re
2
2
from requests .compat import urljoin
3
3
from pathlib import Path
4
4
@@ -8,8 +8,25 @@ class AuthException(BaseException):
8
8
pass
9
9
10
10
class CommunicationException (BaseException ):
11
- pass
12
11
12
+ @staticmethod
13
+ def __clean (content ):
14
+ if type (content ) is list :
15
+ return [CommunicationException .__clean (x ) for x in content ]
16
+ elif type (content ) is tuple :
17
+ return (CommunicationException .__clean (x ) for x in content )
18
+ elif type (content ) is dict :
19
+ return {k :CommunicationException .__clean (v ) for k ,v in content .items ()}
20
+ elif type (content ) is str :
21
+ if re .match ("^Bearer.*" , content ):
22
+ return "REDACTED"
23
+ else :
24
+ return content
25
+ else :
26
+ return content
27
+
28
+ def __init__ (self , op , * args , ** kwargs ):
29
+ BaseException .__init__ (self , f"Operation: { op .__name__ } args: [{ CommunicationException .__clean (args )} ] kwargs: [{ CommunicationException .__clean (kwargs )} ]" )
13
30
14
31
15
32
class CxOneAuthEndpoint :
@@ -172,37 +189,61 @@ async def paged_api(coro, array_element, offset_field='offset', **kwargs):
172
189
yield buf .pop ()
173
190
174
191
175
-
176
-
177
192
class CxOneClient :
178
193
__AGENT_NAME = 'CxOne PyClient'
179
194
180
- def __init__ (self , oauth_id , oauth_secret , agent_name , agent_version , tenant_auth_endpoint , api_endpoint , timeout = 60 , retries = 3 , proxy = None , ssl_verify = True ):
181
195
196
+ def __common__init (self , agent_name , agent_version , tenant_auth_endpoint , api_endpoint , timeout , retries , proxy , ssl_verify ):
182
197
with open (Path (__file__ ).parent / "version.txt" , "rt" ) as version :
183
198
self .__version = version .readline ().rstrip ()
184
199
185
200
self .__agent = f"{ agent_name } /{ agent_version } /({ CxOneClient .__AGENT_NAME } /{ self .__version } )"
186
201
self .__proxy = proxy
187
202
self .__ssl_verify = ssl_verify
188
-
189
203
self .__auth_lock = asyncio .Lock ()
190
204
self .__corelation_id = str (uuid .uuid4 ())
191
205
192
206
self .__auth_endpoint = tenant_auth_endpoint
193
207
self .__api_endpoint = api_endpoint
194
208
self .__timeout = timeout
195
209
self .__retries = retries
196
-
197
-
198
- self .__auth_content = urllib .parse .urlencode ( {
210
+
211
+ self .__auth_endpoint = tenant_auth_endpoint
212
+ self .__api_endpoint = api_endpoint
213
+ self .__timeout = timeout
214
+ self .__retries = retries
215
+
216
+ self .__auth_result = None
217
+
218
+
219
+
220
+
221
+
222
+ @staticmethod
223
+ def create_with_oauth (oauth_id , oauth_secret , agent_name , agent_version , tenant_auth_endpoint , api_endpoint , timeout = 60 , retries = 3 , proxy = None , ssl_verify = True ):
224
+ inst = CxOneClient ()
225
+ inst .__common__init (agent_name , agent_version , tenant_auth_endpoint , api_endpoint , timeout , retries , proxy , ssl_verify )
226
+
227
+ inst .__auth_content = urllib .parse .urlencode ( {
199
228
"grant_type" : "client_credentials" ,
200
229
"client_id" : oauth_id ,
201
230
"client_secret" : oauth_secret
202
231
})
203
-
204
- self .__auth_result = None
205
232
233
+ return inst
234
+
235
+ @staticmethod
236
+ def create_with_api_key (api_key , agent_name , agent_version , tenant_auth_endpoint , api_endpoint , timeout = 60 , retries = 3 , proxy = None , ssl_verify = True ):
237
+ inst = CxOneClient ()
238
+ inst .__common__init (agent_name , agent_version , tenant_auth_endpoint , api_endpoint , timeout , retries , proxy , ssl_verify )
239
+
240
+ inst .__auth_content = urllib .parse .urlencode ( {
241
+ "grant_type" : "refresh_token" ,
242
+ "client_id" : "ast-app" ,
243
+ "refresh_token" : api_key
244
+ })
245
+
246
+ return inst
206
247
207
248
@property
208
249
def auth_endpoint (self ):
@@ -259,14 +300,22 @@ async def __exec_request(self, op, *args, **kwargs):
259
300
kwargs ['verify' ] = self .__ssl_verify
260
301
261
302
for _ in range (0 , self .__retries ):
303
+ auth_headers = await self .__get_request_headers ()
304
+
305
+ if 'headers' in kwargs .keys ():
306
+ for h in auth_headers .keys ():
307
+ kwargs ['headers' ][h ] = auth_headers [h ]
308
+ else :
309
+ kwargs ['headers' ] = auth_headers
310
+
262
311
response = await asyncio .to_thread (op , * args , ** kwargs )
263
312
264
313
if response .status_code == 401 :
265
314
await self .__do_auth ()
266
315
else :
267
316
return response
268
317
269
- raise CommunicationException (f" { str ( op ) } { str ( args ) } { str ( kwargs ) } " )
318
+ raise CommunicationException (op , * args , ** kwargs )
270
319
271
320
272
321
@staticmethod
@@ -288,47 +337,91 @@ def __join_query_dict(url, querydict):
288
337
289
338
return urljoin (url , f"?{ '&' .join (query )} " if len (query ) > 0 else '' )
290
339
291
-
340
+ @dashargs ("tags-keys" , "tags-values" )
341
+ async def get_applications (self , ** kwargs ):
342
+ url = urljoin (self .api_endpoint , "applications" )
343
+ url = CxOneClient .__join_query_dict (url , kwargs )
344
+ return await self .__exec_request (requests .get , url )
345
+
346
+ async def get_application (self , id , ** kwargs ):
347
+ url = urljoin (self .api_endpoint , f"applications/{ id } " )
348
+ url = CxOneClient .__join_query_dict (url , kwargs )
349
+ return await self .__exec_request (requests .get , url )
350
+
292
351
@dashargs ("repo-url" , "name-regex" , "tags-keys" , "tags-values" )
293
352
async def get_projects (self , ** kwargs ):
294
353
url = urljoin (self .api_endpoint , "projects" )
295
-
296
354
url = CxOneClient .__join_query_dict (url , kwargs )
355
+ return await self .__exec_request (requests .get , url )
356
+
297
357
298
- return await self .__exec_request (requests .get , url , headers = await self .__get_request_headers () )
358
+ async def get_project (self , projectid ):
359
+ url = urljoin (self .api_endpoint , f"projects/{ projectid } " )
360
+ return await self .__exec_request (requests .get , url )
299
361
300
- async def get_project (self , id ):
301
- url = urljoin (self .api_endpoint , f"projects/ { id } " )
302
- return await self .__exec_request (requests .get , url , headers = await self . __get_request_headers () )
362
+ async def get_project_configuration (self , projectid ):
363
+ url = urljoin (self .api_endpoint , f"configuration/project?project-id= { projectid } " )
364
+ return await self .__exec_request (requests .get , url )
303
365
304
- async def get_project_configuration (self , id ):
305
- url = urljoin (self .api_endpoint , f"configuration/project?project-id= { id } " )
306
- return await self .__exec_request (requests .get , url , headers = await self . __get_request_headers () )
366
+ async def get_tenant_configuration (self ):
367
+ url = urljoin (self .api_endpoint , f"configuration/tenant " )
368
+ return await self .__exec_request (requests .get , url )
307
369
308
370
@dashargs ("from-date" , "project-id" , "project-ids" , "scan-ids" , "project-names" , "source-origin" , "source-type" , "tags-keys" , "tags-values" , "to-date" )
309
371
async def get_scans (self , ** kwargs ):
310
372
url = urljoin (self .api_endpoint , "scans" )
311
-
312
373
url = CxOneClient .__join_query_dict (url , kwargs )
313
-
314
- return await self .__exec_request (requests .get , url , headers = await self .__get_request_headers () )
374
+ return await self .__exec_request (requests .get , url )
315
375
376
+ @dashargs ("scan-ids" )
377
+ async def get_sast_scans_metadata (self , ** kwargs ):
378
+ url = urljoin (self .api_endpoint , "sast-metadata" )
379
+ url = CxOneClient .__join_query_dict (url , kwargs )
380
+ return await self .__exec_request (requests .get , url )
316
381
317
- async def execute_scan (self , payload , ** kwargs ):
382
+ async def get_sast_scan_metadata (self , scanid , ** kwargs ):
383
+ url = urljoin (self .api_endpoint , f"sast-metadata/{ scanid } " )
384
+ url = CxOneClient .__join_query_dict (url , kwargs )
385
+ return await self .__exec_request (requests .get , url )
386
+
387
+ @dashargs ("source-node-operation" , "source-node" , "source-line-operation" , "source-line" , "source-file-operation" , "source-file" , \
388
+ "sink-node-operation" , "sink-node" , "sink-line-operation" , "sink-line" , "sink-file-operation" , \
389
+ "sink-file" , "result-ids" , "preset-id" , "number-of-nodes-operation" , "number-of-nodes" , "notes-operation" , \
390
+ "first-found-at-operation" , "first-found-at" , "apply-predicates" )
391
+ async def get_sast_scan_aggregate_results (self , scanid , groupby_field = ['SEVERITY' ], ** kwargs ):
392
+ url = urljoin (self .api_endpoint , f"sast-scan-summary/aggregate" )
393
+ url = CxOneClient .__join_query_dict (url , kwargs | {
394
+ 'scan-id' : scanid ,
395
+ 'group-by-field' : groupby_field
396
+ })
397
+ return await self .__exec_request (requests .get , url )
318
398
399
+ async def execute_scan (self , payload , ** kwargs ):
319
400
url = urljoin (self .api_endpoint , "scans" )
320
401
url = CxOneClient .__join_query_dict (url , kwargs )
402
+ return await self .__exec_request (requests .post , url , json = payload )
321
403
322
- return await self .__exec_request (requests .post , url , json = payload , headers = await self .__get_request_headers () )
323
-
324
-
325
404
async def get_sast_scan_log (self , scanid , stream = False ):
326
405
url = urljoin (self .api_endpoint , f"logs/{ scanid } /sast" )
327
- return await self .__exec_request (requests .get , url , stream = stream , headers = await self .__get_request_headers () )
406
+ response = await self .__exec_request (requests .get , url )
407
+
408
+ if response .ok and response .status_code == 307 :
409
+ response = await self .__exec_request (requests .get , response .headers ['Location' ], stream = stream )
410
+
411
+ return response
412
+
413
+ async def get_groups (self , ** kwargs ):
414
+ url = CxOneClient .__join_query_dict (urljoin (self .admin_endpoint , "groups" ), kwargs )
415
+ return await self .__exec_request (requests .get , url )
328
416
329
417
async def get_groups (self , ** kwargs ):
330
418
url = CxOneClient .__join_query_dict (urljoin (self .admin_endpoint , "groups" ), kwargs )
331
- return await self .__exec_request (requests .get , url , headers = await self .__get_request_headers () )
419
+ return await self .__exec_request (requests .get , url )
420
+
421
+ async def get_scan_workflow (self , scanid , ** kwargs ):
422
+ url = urljoin (self .api_endpoint , f"scans/{ scanid } /workflow" )
423
+ url = CxOneClient .__join_query_dict (url , kwargs )
424
+ return await self .__exec_request (requests .get , url )
332
425
333
426
class ProjectRepoConfig :
334
427
@@ -360,4 +453,4 @@ async def primary_branch(self):
360
453
@property
361
454
async def repo_url (self ):
362
455
url = await self .__get_logical_repo_url ()
363
- return url if len (url ) > 0 else None
456
+ return url if len (url ) > 0 else None
0 commit comments