-
Notifications
You must be signed in to change notification settings - Fork 19
[WIP] Add missing support for reports #37
base: master
Are you sure you want to change the base?
Changes from all commits
82edc83
beeb49d
2e79a37
4f6f6a4
27642c4
b5970d0
8abaa83
516b327
10d4f79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ | |
| from .nexpose_engine import EngineStatus, EnginePriority, EngineBase, EngineSummary, EngineConfiguration | ||
| from .nexpose_node import NodeScanStatus, NodeBase, Node | ||
| from .nexpose_privileges import AssetGroupPrivileges, GlobalPrivileges, SitePrivileges | ||
| from .nexpose_report import ReportStatus, ReportTemplate, ReportConfigurationSummary, ReportConfiguration, ReportSummary | ||
| from .nexpose_report import AdhocReportConfiguration, ReportStatus, ReportTemplate, ReportConfigurationSummary, ReportConfiguration, ReportSummary | ||
| from .nexpose_role import RoleScope, RoleSummary, RoleDetails | ||
| from .nexpose_scansummary import VulnerabilityStatus, ScanStatus, ScanSummary, ScanSummaryNodeCounts, ScanSummaryTaskCounts, ScanSummaryVulnerability | ||
| from .nexpose_site import Host, Range, SiteBase, SiteSummary, SiteConfiguration | ||
|
|
@@ -284,7 +284,7 @@ def GetSecurityConsoleStatus(self): | |
| try: | ||
| response = OpenWebRequest(self._URI_root, None, {}, self.timeout) | ||
| return NexposeStatus.GetStatusFromURL(response.geturl()) | ||
| except: | ||
| except Exception: | ||
| return NexposeStatus.UNKNOWN | ||
|
|
||
|
|
||
|
|
@@ -596,10 +596,15 @@ def RequestReportConfig(self, reportconfiguration_id): | |
| Retreive the detailed report configuration (definition) of the specified report configuration. | ||
| This function will return a single ReportConfigResponse XML object (API 1.1). | ||
| """ | ||
| return self.ExecuteBasicOnReportConfiguration("ReportConfigRequest", reportconfiguration_id) | ||
| response = self.ExecuteBasicOnReportConfiguration("ReportConfigRequest", reportconfiguration_id) | ||
| return get_element(response, 'ReportConfig') | ||
|
|
||
| def RequestReportSave(self): | ||
| raise NotImplementedError() # TODO | ||
| def RequestReportSave(self, report_configuration, generate_now=False): | ||
| """ | ||
| Save a report configuration and optionally generate a report immediately. | ||
| This function will return a single ReportSaveResponse XML object (API 1.1). | ||
| """ | ||
| return self.ExecuteBasicWithElement('ReportSaveRequest', {'generate-now': 1 if generate_now else 0}, report_configuration) | ||
|
|
||
| def RequestReportGenerate(self, reportconfiguration_id): | ||
| """ | ||
|
|
@@ -620,6 +625,13 @@ def RequestReportDelete(self, report_id, reportconfiguration_id=0): | |
| else: | ||
| return self.ExecuteBasicOnReport("ReportDeleteRequest", report_id) | ||
|
|
||
| def RequestAdhocReport(self, report_config): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite understand why this is a new function...? Couldn't we just re-use the one below? Or do you want to make breaking changes here that wouldn't work with the below function?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't really remember why I made new ones, but they don't need to be new. I am not terribly concerned about backwards compatibility either at this point since there is a lot of work needed to get this to be on par with the ruby gem for functionality. |
||
| """ | ||
| Generate a new report using the specified report configuration (definition). | ||
| This function will return a single ReportGenerateResponse XML object (API 1.1). | ||
| """ | ||
| return self.ExecuteBasicWithElement('ReportAdhocGenerateRequest', {}, as_xml(report_config)) | ||
|
|
||
| def RequestReportAdhocGenerate(self, id): | ||
| request = """ | ||
| <AdhocReportConfig format="raw-xml-v2" template-id="audit-report"> | ||
|
|
@@ -629,7 +641,6 @@ def RequestReportAdhocGenerate(self, id): | |
| </AdhocReportConfig> | ||
| """ | ||
| return self.ExecuteBasicWithElement("ReportAdhocGenerateRequest", {}, as_xml(request.format(id))) | ||
| raise NotImplementedError() # TODO | ||
|
|
||
| # | ||
| # The following functions implement the User Management API: | ||
|
|
@@ -1466,7 +1477,7 @@ def GetAssetGroupConfiguration(self, assetgroup_or_id): | |
| asset_group.description = json_dict.get['description'] | ||
| if asset_group.description is None: | ||
| asset_group.description = asset_group.short_description | ||
| except: | ||
| except Exception: | ||
| pass | ||
|
|
||
| return asset_group | ||
|
|
@@ -1969,7 +1980,7 @@ def GetReportConfigurationDetails(self, report_or_configuration_or_id): | |
| report_or_configuration_or_id = report_or_configuration_or_id.configuration_id | ||
| response = self.VerifySuccess(self.RequestReportConfig(report_or_configuration_or_id)) | ||
| element = get_element(response, 'ReportConfig') | ||
| return ReportConfigurationSummary.CreateFromXML(element) # TODO: THIS MUST BE A FULL CONFIGURATION | ||
| return ReportConfiguration.CreateFromXML(element) # TODO: THIS MUST BE A FULL CONFIGURATION | ||
|
|
||
| def GetReportHistory(self, reportconfiguration_or_id): | ||
| """ | ||
|
|
@@ -2048,6 +2059,36 @@ def GenerateScanReport(self, scan_or_id): | |
| body = ''.join(data[4:-1]) | ||
| return as_xml(base64.urlsafe_b64decode(body)) | ||
|
|
||
| def GenerateAdHocReport(self, adhoc_report_configuration): | ||
| """ | ||
| Generate adhoc report and return decoded contents. | ||
| """ | ||
| # TODO: add optional filename param to store the report to disk instead of memory | ||
| self._RequireInstanceOf(adhoc_report_configuration, AdhocReportConfiguration) | ||
| data = self.RequestAdhocReport(adhoc_report_configuration.AsXML()) | ||
| data = self.VerifySuccess(data) | ||
| # TODO: figure out a way to handle this safely/correctly for different formats | ||
| data = data.tail.replace('\r', '').strip().split('\n') | ||
| # assert data[1] == 'Content-Type: text/xml; name=report.xml' | ||
| assert data[2] == 'Content-Transfer-Encoding: base64' | ||
| assert data[3] == '' | ||
| assert data[0] == data[-1][:-2] | ||
| body = ''.join(data[4:-1]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had experience when requesting an XML report & within the response there were multiple report items. So in this case, there should be some looping trying to find the actual report. Also, it's really confusing to see splits & then calling each element without an example response.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is just copied from the existing adhoc report method (which only takes a scan id) and it seems pretty limited. I would like this to be able to work the way the ruby gem does where you can write to file instead of holding it all in memory. Being able to capture the extra attachments (like charts) would be nice, but the ruby gem doesn't support that either so I'm not super concerned about it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this. After experimenting a bit, it is possible to use Python's MIME parser to handle these cases but you need to modify the input slightly, i.e. prefix some headers. Here is an excerpt of how we implemented it: A similar approach could work here as well I assume. That way all the verification can be deferred to a library but the content needs to be prepared properly. |
||
| return base64.urlsafe_b64decode(body) | ||
|
|
||
| def SaveReportConfiguration(self, report_configuration, generate_now=False): | ||
| """ | ||
| Save the configuration of a report and return the id of the saved report config. | ||
| If successful, the id will also have been updated in the provided ReportConfiguration object. | ||
| To create a new report, specify -1 as id. | ||
| """ | ||
| self._RequireInstanceOf(report_configuration, ReportConfiguration) | ||
| response = self.RequestReportSave(report_configuration.AsXML(exclude_id=False), generate_now) | ||
| self.VerifySuccess(response) | ||
| report_cfg_id = int(get_attribute(response, 'reportcfg-id')) | ||
| report_configuration.id = report_cfg_id | ||
| return report_cfg_id | ||
|
|
||
| # | ||
| # The following functions implement the Role Management API: | ||
| # ========================================================= | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on that