Skip to content

Commit

Permalink
Merge pull request #86 from veracode/reportingapi-scans
Browse files Browse the repository at this point in the history
Add support for Reporting API Scans report
  • Loading branch information
tjarrettveracode authored Oct 3, 2024
2 parents 9b15427 + 22e0de2 commit 3dbd809
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 22 deletions.
12 changes: 8 additions & 4 deletions docs/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ 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 only supports `findings`
- `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'
- `finding_status`: optional, 'Open' or 'Closed'
- `passed_policy`: optional, boolean
- `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

- `Analytics().get(guid)`: 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.
- `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`.

- `Analytics().get_findings(guid)`: check the status of a findings 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_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.

[All docs](docs.md)
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = 'veracode_api_py'
version = '0.9.49'
version = '0.9.50'
authors = [ {name = "Tim Jarrett", email="[email protected]"} ]
description = 'Python helper library for working with the Veracode APIs. Handles retries, pagination, and other features of the modern Veracode REST APIs.'
readme = 'README.md'
Expand All @@ -22,4 +22,4 @@ dependencies = {file = ["requirements.txt"]}
[project.urls]
"Homepage" = "https://github.com/veracode/veracode-api-py"
"Bug Tracker" = "https://github.com/veracode/veracode-api-py/issues"
"Download" = "https://github.com/veracode/veracode-api-py/archive/v_0949.tar.gz"
"Download" = "https://github.com/veracode/veracode-api-py/archive/v_0950.tar.gz"
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
setup(
name = 'veracode_api_py',
packages = ['veracode_api_py'],
version = '0.9.49',
version = '0.9.50',
license='MIT',
description = 'Python helper library for working with the Veracode APIs. Handles retries, pagination, and other features of the modern Veracode REST APIs.',
long_description = long_description,
long_description_content_type="text/markdown",
author = 'Tim Jarrett',
author_email = '[email protected]',
url = 'https://github.com/tjarrettveracode',
download_url = 'https://github.com/veracode/veracode-api-py/archive/v_0949.tar.gz',
download_url = 'https://github.com/veracode/veracode-api-py/archive/v_0950.tar.gz',
keywords = ['veracode', 'veracode-api'],
install_requires=[
'veracode-api-signing'
Expand Down
40 changes: 26 additions & 14 deletions veracode_api_py/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,34 @@
from .apihelper import APIHelper

class Analytics():
report_types = [ "findings" ]
report_types = [ "findings", "scans" ]

scan_types = ["Static Analysis", "Dynamic Analysis", "Manual", "SCA", "Software Composition Analysis" ]
findings_scan_types = ["Static Analysis", "Dynamic Analysis", "Manual", "SCA", "Software Composition Analysis" ]
scan_scan_types = ["Static Analysis", "Dynamic Analysis", "Manual" ]

base_url = 'appsec/v1/analytics/report'

#public methods
def create_report(self,report_type,last_updated_start_date,last_updated_end_date=None,
scan_type:list = [], finding_status=None,passed_policy=None,
policy_sandbox=None,application_id=None,rawjson=False):
#TODO validate report_type, add scan_type

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 }

if last_updated_end_date:
report_def['last_updated_end_date'] = last_updated_end_date

if len(scan_type) > 0:
# validate against self.scan_types
if self._case_insensitive_list_compare(scan_type,self.scan_types):
report_def['scan_type'] = scan_type
else:
print("Invalid scan_type provided. Valid types are {}".format(self.scan_types))
return
if report_type == 'findings':
valid_scan_types = self.findings_scan_types
elif report_type == 'scans':
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))
report_def['scan_type'] = scan_type

if finding_status:
report_def['finding_status'] = finding_status
Expand All @@ -51,19 +55,27 @@ def create_report(self,report_type,last_updated_start_date,last_updated_end_date
else:
return response['_embedded']['id'] #we will usually just need the guid so we can come back and fetch the report

def get(self,guid: UUID):
def get_findings(self, guid: UUID):
thestatus, thefindings = self.get(guid=guid,report_type='findings')
return thestatus, thefindings

def get_scans(self, guid: UUID):
thestatus, thescans = self.get(guid=guid,report_type='scans')
return thestatus, thescans

def get(self,guid: UUID,report_type='findings'):
# handle multiple scan types
uri = "{}/{}".format(self.base_url,guid)
theresponse = APIHelper()._rest_paged_request(uri,"GET","findings",{},fullresponse=True)
theresponse = APIHelper()._rest_paged_request(uri,"GET",report_type,{},fullresponse=True)
thestatus = theresponse.get('_embedded',{}).get('status','')
thefindings = theresponse.get('_embedded',{}).get('findings',{})
return thestatus, thefindings
thebody = theresponse.get('_embedded',{}).get(report_type,{})
return thestatus, thebody

#helper methods
def _case_insensitive_list_compare(self,input_list:list, target_list:list):
input_set = self._lowercase_set_from_list(input_list)
target_set = self._lowercase_set_from_list(target_list)
return target_set.issuperset(input_set)


def _lowercase_set_from_list(self,thelist:list):
return set([x.lower() for x in thelist])

0 comments on commit 3dbd809

Please sign in to comment.