diff --git a/docs/analytics.md b/docs/analytics.md index 5a0d643..f333a33 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -6,16 +6,18 @@ The following methods call Veracode REST APIs and return JSON. 1. The Reporting API is available to Veracode customers by request. More information about the API is available in the [Veracode Docs](https://docs.veracode.com/r/Reporting_REST_API). -- `Analytics().create_report(report_type ('findings'),last_updated_start_date, last_updated_end_date (opt), scan_type (opt), finding_status(opt), passed_policy(opt), policy_sandbox(opt), application_id(opt), rawjson(opt))`: set up a request for a report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: - - `report_type`: required, currently supports `findings` and `scans`. - - `last_updated_start_date`: required, beginning of date range for new or changed findings - - `last_updated_end_date`: optional, end of date range for new or changed findings - - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual', 'Software Composition Analysis', 'SCA' +- `Analytics().create_report(report_type ('findings'),last_updated_start_date(opt), last_updated_end_date (opt), scan_type (opt), finding_status(opt), passed_policy(opt), policy_sandbox(opt), application_id(opt), rawjson(opt), deletion_start_date(opt), deletion_end_date(opt))`: set up a request for a report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: + - `report_type`: required, currently supports `findings`, `scans`, and `deletedscans`. + - `last_updated_start_date`: required for `findings` report type, beginning of date range for new or changed findings or scans + - `last_updated_end_date`: optional, end of date range for new or changed findings or scans + - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual', 'Software Composition Analysis', 'SCA'. `SCA` is only supported for the `findings` report type. - `finding_status`: optional, 'Open' or 'Closed'. Applies only to the `findings` report. - `passed_policy`: optional, boolean. Applies only to the `findings` report. - `policy_sandbox`: optional, 'Policy' or 'Sandbox' - `application_id`: optional, application ID for which to return results - `rawjson`: optional, defaults to False. Returns full response if True, the GUID of the request if false + - `deletion_start_date`: required for `deletedscans` report type, beginning of date range for deleted scans. + - `deletion_end_date`: optional, end of date range for deleted scans. - `Analytics().get(guid, report_type(findings))`: check the status of the report request and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. Also, you need to specify the type of data expected by the GUID with `report_type`; this defaults to `findings`. @@ -23,4 +25,6 @@ The following methods call Veracode REST APIs and return JSON. - `Analytics().get_scans(guid)`: check the status of a scans report request specified by `guid` and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. +- `Analytics().get_deletedscans(guid)`: check the status of a deleted scans report request specified by `guid` and return the report contents when ready. Note that this method returns a tuple of `status` (string) and `results` (list); when `status` is `COMPLETED`, the `results` list will populate with results. + [All docs](docs.md) diff --git a/docs/api.md b/docs/api.md index cd89ac6..ba9134e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -26,15 +26,14 @@ As an alternative to importing individual objects into your library, you can acc ### Analytics Reporting -1. *Accessing*: The Reporting API is available to Veracode customers by request. 1. *More information*: See the [Veracode Docs](https://docs.veracode.com/r/Reporting_REST_API). 1. *See Also*: You can also access these methods from the [Analytics class](analytics.md). - `create_analytics_report(report_type ('findings'),last_updated_start_date, last_updated_end_date (opt), scan_type (opt), finding_status(opt), passed_policy(opt), policy_sandbox(opt), application_id(opt), rawjson(opt))`: set up a request for a report. By default this command returns the GUID of the report request; specify `rawjson=True` to get the full response. Dates should be specified as `YYYY-MM-DD HH:MM:SS` with the timestamp optional. Options include: - - `report_type`: required, currently only supports `findings` + - `report_type`: required, currently only supports `findings`, `scans` and `deletedscans` - `last_updated_start_date`: required, beginning of date range for new or changed findings - `last_updated_end_date`: optional, end of date range for new or changed findings - - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual', 'Software Composition Analysis', 'SCA' + - `scan_type`: optional, one or more of 'Static Analysis', 'Dynamic Analysis', 'Manual', 'Software Composition Analysis', 'SCA'. `SCA` is only supported for the `findings` type - `finding_status`: optional, 'Open' or 'Closed' - `passed_policy`: optional, boolean - `policy_sandbox`: optional, 'Policy' or 'Sandbox' diff --git a/docs/findings.md b/docs/findings.md index 6a7637e..d1c79fe 100644 --- a/docs/findings.md +++ b/docs/findings.md @@ -5,7 +5,7 @@ The following methods call Veracode REST APIs and return JSON. ## Findings and Annotations - `Findings().get_findings(app,scantype(opt),annot(opt),request_params(opt),sandbox(opt))`: get the findings for `app` (guid). - - `scantype`: Defaults to STATIC findings, but can be STATIC, DYNAMIC, MANUAL, SCA, or ALL (static, dynamic, manual). + - `scantype`: Defaults to STATIC findings, but can be STATIC, DYNAMIC, MANUAL, SCA, or ALL (static, dynamic, manual). You can also pass a comma-delimited string of valid scantype options. - `annot`: Defaults to TRUE but can be FALSE - `sandbox`: The guid of the sandbox in `app` for which you want findings. (Use the Sandboxes APIs to get the sandbox guid.) - `request_params`: Dictionary of additional query parameters. See the full [Findings API specification](https://help.veracode.com/r/c_findings_v2_intro) for some of the other options available. diff --git a/requirements.txt b/requirements.txt index 0827702..befdf39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ requests>=2.32.0 -veracode-api-signing>=22.3.0 -urllib3>= 2.2.2 +veracode-api-signing>=24.11.0 Pygments>= 2.9.0 idna>=3.7 certifi>=2024.7.4 \ No newline at end of file diff --git a/samples/reportingapi_deleted_sample.py b/samples/reportingapi_deleted_sample.py new file mode 100644 index 0000000..749fec3 --- /dev/null +++ b/samples/reportingapi_deleted_sample.py @@ -0,0 +1,32 @@ +import time +import sys +import json +import datetime +from veracode_api_py import Analytics + +wait_seconds = 15 + +print('Generating deleted scans report...') +theguid = Analytics().create_report(report_type="deletedscans",deletion_start_date='2024-07-01',deletion_end_date='2024-12-31') + +print('Checking status for report {}...'.format(theguid)) +thestatus,thescans=Analytics().get_deleted_scans(theguid) + +while thestatus != 'COMPLETED': + print('Waiting {} seconds before we try again...'.format(wait_seconds)) + time.sleep(wait_seconds) + print('Checking status for report {}...'.format(theguid)) + thestatus,thescans=Analytics().get_deleted_scans(theguid) + +recordcount = len(thescans) + +print('Retrieved {} scans'.format(recordcount)) + +if recordcount > 0: + now = datetime.datetime.now().astimezone() + filename = 'report-{}'.format(now) + with open('{}.json'.format(filename), 'w') as outfile: + json.dump(thescans,outfile) + outfile.close() + + print('Wrote {} findings to {}.json'.format(recordcount,filename)) \ No newline at end of file diff --git a/setup.py b/setup.py index d4fc5a3..39f06d0 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ long_description_content_type="text/markdown", author = 'Tim Jarrett', author_email = 'tjarrett@veracode.com', - url = 'https://github.com/tjarrettveracode', + url = 'https://github.com/tjarrettveracode', download_url = 'https://github.com/veracode/veracode-api-py/archive/v_0957.tar.gz', keywords = ['veracode', 'veracode-api'], install_requires=[ diff --git a/veracode_api_py/analytics.py b/veracode_api_py/analytics.py index 30bc1f1..036a416 100644 --- a/veracode_api_py/analytics.py +++ b/veracode_api_py/analytics.py @@ -6,7 +6,7 @@ from .apihelper import APIHelper class Analytics(): - report_types = [ "findings", "scans" ] + report_types = [ "findings", "scans", "deletedscans" ] findings_scan_types = ["Static Analysis", "Dynamic Analysis", "Manual", "SCA", "Software Composition Analysis" ] scan_scan_types = ["Static Analysis", "Dynamic Analysis", "Manual" ] @@ -14,22 +14,38 @@ class Analytics(): base_url = 'appsec/v1/analytics/report' #public methods - def create_report(self,report_type,last_updated_start_date,last_updated_end_date=None, + def create_report(self,report_type,last_updated_start_date=None,last_updated_end_date=None, scan_type:list = [], finding_status=None,passed_policy=None, - policy_sandbox=None,application_id=None,rawjson=False): + policy_sandbox=None,application_id=None,rawjson=False, deletion_start_date=None, + deletion_end_date=None): if report_type not in self.report_types: raise ValueError("{} is not in the list of valid report types ({})".format(report_type,self.report_types)) - report_def = { "report_type": report_type,"last_updated_start_date": last_updated_start_date } + report_def = { 'report_type': report_type } + + if last_updated_start_date: + report_def['last_updated_start_date'] = last_updated_start_date + else: + if report_type in ['findings','scans']: + raise ValueError("{} report type requires a last updated start date.").format(report_type) if last_updated_end_date: report_def['last_updated_end_date'] = last_updated_end_date + if deletion_end_date: + report_def['deletion_end_date'] = deletion_end_date + + if deletion_start_date: + report_def['deletion_start_date'] = deletion_start_date + else: + if report_type == 'deletedscans': + raise ValueError("{} report type requires a deleteion start date.").format(report_type) + if len(scan_type) > 0: if report_type == 'findings': valid_scan_types = self.findings_scan_types - elif report_type == 'scans': + elif report_type in [ 'scans', 'deletedscans' ]: valid_scan_types = self.scan_scan_types if not(self._case_insensitive_list_compare(scan_type,valid_scan_types)): raise ValueError("{} is not in the list of valid scan types ({})".format(report_type,valid_scan_types)) @@ -62,6 +78,10 @@ def get_findings(self, guid: UUID): def get_scans(self, guid: UUID): thestatus, thescans = self.get(guid=guid,report_type='scans') return thestatus, thescans + + def get_deleted_scans(self, guid: UUID): + thestatus, thescans = self.get(guid=guid,report_type='deletedscans') + return thestatus, thescans def get(self,guid: UUID,report_type='findings'): # handle multiple scan types diff --git a/veracode_api_py/api.py b/veracode_api_py/api.py index e90d2ce..2cee12c 100644 --- a/veracode_api_py/api.py +++ b/veracode_api_py/api.py @@ -642,15 +642,29 @@ def dyn_start_scan(self, length, unit): return DynUtils().start_scan(length,unit) # analytics apis - def create_analytics_report(self,report_type,last_updated_start_date,last_updated_end_date=None, + def create_analytics_report(self,report_type,last_updated_start_date=None,last_updated_end_date=None, scan_type:list = [], finding_status=None,passed_policy=None, - policy_sandbox=None,application_id=None,rawjson=False): + policy_sandbox=None,application_id=None,rawjson=False,deletion_start_date=None, + deletion_end_date=None): return Analytics().create_report(report_type=report_type,last_updated_start_date=last_updated_start_date, last_updated_end_date=last_updated_end_date,scan_type=scan_type, finding_status=finding_status,passed_policy=passed_policy, policy_sandbox=policy_sandbox,application_id=application_id, - rawjson=rawjson) + rawjson=rawjson, deletion_start_date=deletion_start_date, + deletion_end_date=deletion_end_date) def get_analytics_report(self,guid: UUID): status, findings = Analytics().get(guid=guid) + return status, findings + + def get_analytics_findings_report(self,guid:UUID): + status, findings = Analytics().get_findings(guid=guid) + return status, findings + + def get_analytics_scans(self,guid:UUID): + status, findings = Analytics().get_scans(guid=guid) + return status, findings + + def get_analytics_deleted_scans(self,guid:UUID): + status, findings = Analytics().get_deleted_scans(guid=guid) return status, findings \ No newline at end of file diff --git a/veracode_api_py/findings.py b/veracode_api_py/findings.py index afeb076..c48f0ad 100644 --- a/veracode_api_py/findings.py +++ b/veracode_api_py/findings.py @@ -14,8 +14,15 @@ def get_findings(self,app: UUID,scantype='STATIC',annot='TRUE',request_params=No if request_params == None: request_params = {} - if scantype in ['STATIC', 'DYNAMIC', 'MANUAL','SCA']: - request_params['scan_type'] = scantype + scantypes = "" + scantype = scantype.split(',') + for st in scantype: + if st in ['STATIC', 'DYNAMIC', 'MANUAL','SCA']: + if len(scantypes) > 0: + scantypes += "," + scantypes += st + if len(scantypes) > 0: + request_params['scan_type'] = scantypes #note that scantype='ALL' will result in no scan_type parameter as in API request_params['include_annot'] = annot diff --git a/veracode_api_py/identity.py b/veracode_api_py/identity.py index 2f9224e..dd1bc9d 100644 --- a/veracode_api_py/identity.py +++ b/veracode_api_py/identity.py @@ -298,7 +298,7 @@ def _create_or_update(self, method, role_name, role_description, role_guid: UUID if len(child_roles) > 0: role_def['child_roles'] = child_roles - payload = json.dumps({"profile": role_def}) + payload = json.dumps(role_def) return APIHelper()._rest_request(uri,httpmethod,body=payload) class Permissions():