From 2c56ce11bdf6fe8dc8d0d66c42e276910a0f157f Mon Sep 17 00:00:00 2001 From: whalenda Date: Wed, 28 Aug 2024 12:50:33 -0400 Subject: [PATCH] feat: USMC MCCAST formatted POAM export (#1345) Co-authored-by: David Whalen Co-authored-by: cd-rite <61710958+cd-rite@users.noreply.github.com> Co-authored-by: csmig --- CONTRIBUTORS.md | 1 + api/source/controllers/Collection.js | 40 ++- api/source/specification/stig-manager.yaml | 29 ++ api/source/utils/poam-template-mccast.xlsx | Bin 0 -> 9701 bytes api/source/utils/serializers.js | 46 +++- client/src/js/SM/FindingsPanel.js | 306 +++++++++++++-------- client/src/js/findingsSummary.js | 6 +- client/src/js/review.js | 1 - test/api/postman_collection.json | 174 +++++++++++- 9 files changed, 466 insertions(+), 137 deletions(-) create mode 100644 api/source/utils/poam-template-mccast.xlsx diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f96b51813..a455c279c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,6 +10,7 @@ - Copyright 2021 Russell Johnson, russell.d.johnson@saic.com - Copyright 2023-2024 Mathew Ferreira, mferreira@rite-solutions.com - Copyright 2024 Rajesh Shrestha, rshrestha@rite-solutions.com +- Copyright 2024 David Whalen, david.whalen@usmc.mil - _Add the copyright date, your name, and email address here. (PLEASE KEEP THIS LINE)_ ## Note for U.S. Federal Employees diff --git a/api/source/controllers/Collection.js b/api/source/controllers/Collection.js index 4836cdf0b..a5ec1ff1b 100644 --- a/api/source/controllers/Collection.js +++ b/api/source/controllers/Collection.js @@ -151,17 +151,27 @@ module.exports.getFindingsByCollection = async function getFindingsByCollection module.exports.getPoamByCollection = async function getFindingsByCollection (req, res, next) { try { - const aggregator = req.query.aggregator - const benchmarkId = req.query.benchmarkId - const assetId = req.query.assetId - const acceptedOnly = req.query.acceptedOnly + const { + aggregator, + benchmarkId, + assetId, + acceptedOnly, + date, + office, + status, + mccastPackageId, + mccastAuthName, + format + } = req.query const defaults = { - date: req.query.date, - office: req.query.office, - status: req.query.status + date, + office, + status, + mccastPackageId, + mccastAuthName } const { collectionId, collectionGrant } = getCollectionInfoAndCheckPermission(req, Security.ACCESS_LEVEL.Restricted) - const response = await CollectionService.getFindingsByCollection( collectionId, aggregator, benchmarkId, assetId, acceptedOnly, + const findings = await CollectionService.getFindingsByCollection( collectionId, aggregator, benchmarkId, assetId, acceptedOnly, [ 'rulesWithDiscussion', 'groups', @@ -169,18 +179,20 @@ module.exports.getPoamByCollection = async function getFindingsByCollection (req 'stigs', 'ccis' ], req.userObject ) - - const po = Serialize.poamObjectFromFindings(response, defaults) - const xlsx = await Serialize.xlsxFromPoamObject(po) - let collectionName = collectionGrant.collection.name - writer.writeInlineFile( res, xlsx, `POAM-${collectionName}_${escape.filenameComponentFromDate()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + + const poFns = { + EMASS: Serialize.poamObjectFromFindings, + MCCAST: Serialize.mccastPoamObjectFromFindings + } + const xlsx = await Serialize.xlsxFromPoamObject(poFns[format](findings, defaults), format) + writer.writeInlineFile( res, xlsx, `POAM-${format}-${collectionGrant.collection.name}_${escape.filenameComponentFromDate()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + } catch (err) { next(err) } } - module.exports.getStigAssetsByCollectionUser = async function getStigAssetsByCollectionUser (req, res, next) { try { const userId = req.params.userId diff --git a/api/source/specification/stig-manager.yaml b/api/source/specification/stig-manager.yaml index b630631a1..d525775d0 100644 --- a/api/source/specification/stig-manager.yaml +++ b/api/source/specification/stig-manager.yaml @@ -2418,17 +2418,36 @@ paths: in: query schema: type: string + pattern: '^(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])/\d{4}$' - name: office description: Value for column Office/Org in: query schema: type: string + maxLength: 255 allowReserved: true - name: status description: Value for column Status in: query schema: type: string + maxLength: 255 + allowReserved: true + - $ref: '#/components/parameters/PoamFormatQuery' + - name: mccastPackageId + description: Value for POAM MCCAST PackageId + in: query + schema: + type: string + maxLength: 255 + allowReserved: true + - name: mccastAuthName + description: Value for POAM MCCAST Authorization Name + in: query + schema: + type: string + maxLength: 255 + allowReserved: true responses: '200': description: CollectionFinding response @@ -7558,6 +7577,16 @@ components: enum: - ruleId - groupId + PoamFormatQuery: + name: format + in: query + description: Value for POAM format (ie. EMASS, MCCAST) + schema: + type: string + enum: + - EMASS + - MCCAST + default: EMASS RetentionDateQuery: name: retentionDate in: query diff --git a/api/source/utils/poam-template-mccast.xlsx b/api/source/utils/poam-template-mccast.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..de8eb5bfb57bdde27067a8c0f3ec700fc449764f GIT binary patch literal 9701 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 ({ + authPackage: defaults.mccastAuthName, + name: finding.rules[0].title, + dateId: finding.stigs[0].ruleCount === 0 ? finding.stigs[0].benchmarkDate : '', + stigInfo: 'STIG Finding', + status: defaults.status, + packageId: defaults.mccastPackageId, + date: defaults.date, + startDate: '', + endDate: '', + securityChecks: finding.rules[0].ruleId || finding.groupId, + control: finding.ccis.map( cci => `DoD RMF-${defaults.mccastPackageId}-${cci.apAcronym?.replace(/\./g,' ')}-CNSSI 1253`).join('\n'), + resultingRisk: finding.severity === 'medium' ? 'Moderate' : `${finding.severity.charAt(0).toUpperCase()}${finding.severity.slice(1)}`, + weakness: finding.rules[0].vulnDiscussion, + mitigations: '', + comments: finding.stigs[0].ruleCount === 0 ? '' : finding.ccis.map( cci => `CCI-${cci.cci}`).join('\n'), + assets: finding.assets.map( asset => asset.name ).join('\n'), + mav: '', + mac: '', + mpr: '', + mui: '', + ms: '', + mi: '', + ma: '' + })) + return {vuln} +} + +module.exports.poamObjectFromFindings = function (findings, defaults = {}) { const vuln = findings.map( finding => ({ desc: `Title:\n${finding.rules[0].title}\n\nDescription:\n${finding.rules[0].vulnDiscussion}`, control: finding.ccis.map( cci => cci.apAcronym).join('\n'), @@ -30,15 +59,18 @@ module.exports.poamObjectFromFindings = function ( findings, defaults = {} ) { recommendations: '', resultingRisk: finding.severity === 'medium' ? 'Moderate' : `${finding.severity.charAt(0).toUpperCase()}${finding.severity.slice(1)}`, })) - return { - vuln: vuln - } + return {vuln} } -module.exports.xlsxFromPoamObject = async function ( po ) { - const templateData = await fs.readFile(path.join(__dirname,'poam-template.xlsx')) + +module.exports.xlsxFromPoamObject = async function (substitutions, format) { + const templateFiles = { + EMASS: 'poam-template.xlsx', + MCCAST: 'poam-template-mccast.xlsx' + } + const templateData = await fs.readFile(path.join(__dirname, templateFiles[format])) const template = new XlsxTemplate() await template.loadTemplate(templateData) - await template.substitute( 1, po ) + await template.substitute(1, substitutions) return await template.generate({type: 'nodebuffer'}) } \ No newline at end of file diff --git a/client/src/js/SM/FindingsPanel.js b/client/src/js/SM/FindingsPanel.js index 16384eb1e..6e0c205eb 100644 --- a/client/src/js/SM/FindingsPanel.js +++ b/client/src/js/SM/FindingsPanel.js @@ -1,8 +1,7 @@ -Ext.ns('SM') +Ext.ns('SM.Findings') -SM.AggregatorCombo = Ext.extend(Ext.form.ComboBox, { +SM.Findings.AggregatorCombo = Ext.extend(Ext.form.ComboBox, { initComponent: function () { - let me = this let config = { width: 70, forceSelection: true, @@ -17,12 +16,12 @@ SM.AggregatorCombo = Ext.extend(Ext.form.ComboBox, { }) } Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.AggregatorCombo.superclass.initComponent.call(this) + this.superclass().initComponent.call(this) } }) -Ext.reg('sm-aggregator-combo', SM.AggregatorCombo) +Ext.reg('sm-aggregator-combo', SM.Findings.AggregatorCombo) -SM.FindingsParentGrid = Ext.extend(Ext.grid.GridPanel, { +SM.Findings.ParentGrid = Ext.extend(Ext.grid.GridPanel, { initComponent: function () { let me = this this.aggValue = this.aggValue || 'groupId' @@ -229,10 +228,11 @@ SM.FindingsParentGrid = Ext.extend(Ext.grid.GridPanel, { } ] }) - const generatePoamBtn = new SM.GeneratePoamButton({ + const generatePoamBtn = new Ext.Button({ parentGrid: me, iconCls: 'icon-excel', - text: 'Generate POA&M...' + text: 'Generate POA&M...', + handler: this.genPoamBtnHandler }) function getStatSprites (store) { const stats = store.data.items.reduce((accumulator, currentValue) => { @@ -256,7 +256,7 @@ SM.FindingsParentGrid = Ext.extend(Ext.grid.GridPanel, { tooltip: 'Reload this grid', width: 20, handler: function (btn) { - store.reload(); + store.reload() } }, '-', @@ -357,11 +357,11 @@ SM.FindingsParentGrid = Ext.extend(Ext.grid.GridPanel, { } Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.FindingsParentGrid.superclass.initComponent.call(this) + this.superclass().initComponent.call(this) } }) -SM.FindingsChildGrid = Ext.extend(Ext.grid.GridPanel, { +SM.Findings.ChildGrid = Ext.extend(Ext.grid.GridPanel, { initComponent: function () { const me = this function engineResultConverter (v,r) { @@ -564,7 +564,7 @@ SM.FindingsChildGrid = Ext.extend(Ext.grid.GridPanel, { tooltip: 'Reload this grid', width: 20, handler: function (btn) { - store.reload(); + store.reload() } }, '-', @@ -597,11 +597,11 @@ SM.FindingsChildGrid = Ext.extend(Ext.grid.GridPanel, { bbar } Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.FindingsChildGrid.superclass.initComponent.call(this) + this.superclass().initComponent.call(this) } }) -SM.PoamStatusComboBox = Ext.extend(Ext.form.ComboBox, { +SM.Findings.PoamStatusComboBox = Ext.extend(Ext.form.ComboBox, { initComponent: function () { let config = { displayField: 'display', @@ -623,15 +623,41 @@ SM.PoamStatusComboBox = Ext.extend(Ext.form.ComboBox, { }) Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.PoamStatusComboBox.superclass.initComponent.call(this) + this.superclass().initComponent.call(this) this.store.loadData(data) } }) -Ext.reg('sm-poam-status-combo', SM.PoamStatusComboBox); +SM.Findings.PoamFormatComboBox = Ext.extend(Ext.form.ComboBox, { + initComponent: function () { + let config = { + displayField: 'display', + valueField: 'value', + triggerAction: 'all', + mode: 'local', + editable: false + } + let me = this + let data = [ + ['EMASS', 'EMASS'], + ['MCCAST', 'MCCAST'] + ] + this.store = new Ext.data.SimpleStore({ + fields: ['value', 'display'] + }) + this.store.on('load', function (store) { + me.setValue(store.getAt(0).get('value')) + }) + + Ext.apply(this, Ext.apply(this.initialConfig, config)) + this.superclass().initComponent.call(this) + + this.store.loadData(data) + } +}) -SM.PoamOptionsPanel = Ext.extend(Ext.FormPanel, { +SM.Findings.PoamOptionsPanel = Ext.extend(Ext.FormPanel, { initComponent: function () { const me = this // Set default date 30 days from now @@ -644,43 +670,119 @@ SM.PoamOptionsPanel = Ext.extend(Ext.FormPanel, { hideLabel: true, value: defaultDate }) + + const statusCombo = new SM.Findings.PoamStatusComboBox({ + anchor: '100%', + hideLabel: true, + name: 'status', + ref: 'statusCombo' + }) + + const setFormatDisplay = function (format) { + // Toggle visibility of FieldSets based on selection + const generatePoamWindow = me.ownerCt + formatCombo.setValue(format) + + if (format === 'MCCAST') { + generatePoamWindow.setHeight(460) + statusCombo.store.loadData([ + ['Started','Started'], + ['Not Started','Not Started'], + ['Request Risk Acceptance','Request Risk Acceptance'] + ]) + officeFieldSet.setVisible(false) + mccastPackageIdFieldSet.setVisible(true) + mccastAuthNameFieldSet.setVisible(true) + } + //EMASS or OTHER MODE + else { + generatePoamWindow.setHeight(390) + statusCombo.store.loadData([ + ['Ongoing','Ongoing'], + ['Completed','Completed'] + ]) + officeFieldSet.setVisible(true) + mccastPackageIdFieldSet.setVisible(false) + mccastAuthNameFieldSet.setVisible(false) + } + } + + const formatCombo = new SM.Findings.PoamFormatComboBox( { + anchor: '100%', + hideLabel: true, + name: 'format', + value: 'EMASS', + listeners: { + select: (combo, record) => { + setFormatDisplay(record.data.value) + } + } + }) + + const officeFieldSet = new Ext.form.FieldSet({ + title: 'Office/Org', + items: [{ + xtype: 'textfield', + anchor: '100%', + hideLabel: true, + name: 'office', + value: 'My Office Info' + }] + }) + + const mccastPackageIdFieldSet = new Ext.form.FieldSet({ + title: 'Package ID', + hidden:true, + items: [{ + name: 'mccastPackageId', + xtype: 'textfield', + anchor: '100%', + hideLabel: true, + value: 'Package ID', + }] + }) + + const mccastAuthNameFieldSet = new Ext.form.FieldSet({ + title: 'Authorization Package Name', + hidden:true, + items: [{ + name: 'mccastAuthName', + xtype: 'textfield', + anchor: '100%', + hideLabel: true, + value: 'Authorization Package Name' + }] + }) + const items = [ { xtype: 'fieldset', - title: 'Scheduled Completion Date', - items: [dateField] + title: 'Format', + items: [formatCombo] }, { xtype: 'fieldset', - title: 'Office/Org', - items: [{ - xtype: 'textfield', - anchor: '100%', - hideLabel: true, - name: 'office', - value: 'My office info' - }] + title: 'Scheduled Completion Date', + items: [dateField] }, + officeFieldSet, { xtype: 'fieldset', title: 'Status', - items: [{ - xtype: 'sm-poam-status-combo', - anchor: '100%', - hideLabel: true, - name: 'status', - value: 'Ongoing' - }] - } - + items: [statusCombo] + }, + mccastPackageIdFieldSet, + mccastAuthNameFieldSet ] + const config = { baseCls: 'x-plain', labelWidth: 70, monitorValid: true, trackResetOnLoad: true, - items: items, + items, buttons: [{ + anchor:'100%', text: this.btnText || 'Generate', iconCls: 'icon-excel', height: 30, @@ -688,17 +790,26 @@ SM.PoamOptionsPanel = Ext.extend(Ext.FormPanel, { parentPanel: me, formBind: true, handler: this.btnHandler || function () { } - }] + }], + setFormatDisplay } Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.PoamOptionsPanel.superclass.initComponent.call(this) + this.superclass().initComponent.call(this) } }) -SM.RequestAndServePoam = async function (collectionId, params) { +SM.Findings.RequestAndServePoam = async function (collectionId, params) { let mb try { mb = Ext.MessageBox.wait('Generating POA&M') + Object.keys(params).forEach((k) => params[k] == "" && delete params[k]) + if (params.format === "EMASS") { + delete params.mccastPackageId + delete params.mccastAuthName + } + if (params.format === "MCCAST") { + delete params.office + } const search = new URLSearchParams(params).toString() let url = `${STIGMAN.Env.apiBase}/collections/${collectionId}/poam?${search}` @@ -725,9 +836,9 @@ SM.RequestAndServePoam = async function (collectionId, params) { let url = window.URL.createObjectURL(blob) a.href = url a.download = filename - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + document.body.appendChild(a) + a.click() + document.body.removeChild(a) window.URL.revokeObjectURL(url) } } @@ -737,71 +848,20 @@ SM.RequestAndServePoam = async function (collectionId, params) { } } -SM.GeneratePoamButton = Ext.extend(Ext.Button, { +SM.Findings.FindingsPanel = Ext.extend(Ext.Panel, { initComponent: function () { const me = this - const onClick = function (btn, e) { - const poamOptionsPanel = new SM.PoamOptionsPanel({ - btnText: 'Generate POA&M', - padding: 10, - btnHandler: (btn, e) => { - const params = poamOptionsPanel.getForm().getFieldValues() - if (params.date && params.date instanceof Date) { - params.date = Ext.util.Format.date(params.date, 'm/d/Y') - } - params.aggregator = me.parentGrid.aggValue - if (me.parentGrid.stigValue && me.parentGrid.stigValue !== me.parentGrid.stigAllValue) { - params.benchmarkId = me.parentGrid.stigValue - } - appwindow.close() - SM.RequestAndServePoam(me.parentGrid.panel.collectionId, params) - } - }) - /******************************************************/ - // Form window - /******************************************************/ - const appwindow = new Ext.Window({ - title: 'POA&M Defaults', - cls: 'sm-dialog-window sm-round-panel', - modal: true, - hidden: true, - width: 230, - height: 310, - layout: 'fit', - plain: true, - bodyStyle: 'padding:5px;', - buttonAlign: 'right', - items: poamOptionsPanel - }) - appwindow.show(document.body); - } - - const config = { - listeners: { - click: onClick - } - } - - Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.GeneratePoamButton.superclass.initComponent.call(this) - } -}) -Ext.reg('sm-generate-poam-button', SM.GeneratePoamButton); - -// config: {collectionId} -SM.FindingsPanel = Ext.extend(Ext.Panel, { - initComponent: function () { - const me = this - const parent = new SM.FindingsParentGrid({ + const parent = new SM.Findings.ParentGrid({ cls: 'sm-round-panel', margins: { top: SM.Margin.top, right: SM.Margin.adjacent, bottom: SM.Margin.bottom, left: SM.Margin.edge }, border: false, region: 'center', panel: this, aggValue: me.aggregator || 'groupId', - title: 'Aggregated Findings' + title: 'Aggregated Findings', + genPoamBtnHandler }) - const child = new SM.FindingsChildGrid({ + const child = new SM.Findings.ChildGrid({ cls: 'sm-round-panel', margins: { top: SM.Margin.top, right: SM.Margin.edge, bottom: SM.Margin.bottom, left: SM.Margin.adjacent }, border: false, @@ -819,7 +879,7 @@ SM.FindingsPanel = Ext.extend(Ext.Panel, { this.parent = parent this.child = child - onParentRowSelect = (sm, index, record) => { + const onParentRowSelect = (sm, index, record) => { const params = {} params[parent.aggValue] = record.data[parent.aggValue] if (parent.stigValue !== parent.stigAllValue) { @@ -830,7 +890,7 @@ SM.FindingsPanel = Ext.extend(Ext.Panel, { }) } function onChildRowDblClick (grid, rowIndex) { - const r = grid.getStore().getAt(rowIndex); + const r = grid.getStore().getAt(rowIndex) const leaf = { collectionId: grid.panel.collectionId, assetId: r.data.assetId, @@ -846,6 +906,42 @@ SM.FindingsPanel = Ext.extend(Ext.Panel, { }) } + function genPoamBtnHandler() { + const poamOptionsPanel = new SM.Findings.PoamOptionsPanel({ + btnText: 'Generate POA&M', + padding: 10, + btnHandler: function () { + const params = poamOptionsPanel.getForm().getFieldValues() + if (params.date && params.date instanceof Date) { + params.date = Ext.util.Format.date(params.date, 'm/d/Y') + } + params.aggregator = parent.aggValue + if (parent.stigValue && parent.stigValue !== parent.stigAllValue) { + params.benchmarkId = parent.stigValue + } + appwindow.close() + localStorage.setItem('poam-format', params.format ?? 'EMASS') + SM.Findings.RequestAndServePoam(parent.panel.collectionId, params) + } + }) + + const appwindow = new Ext.Window({ + title: 'POA&M Defaults', + cls: 'sm-dialog-window sm-round-panel', + modal: true, + hidden: true, + width: 230, + height: 390, + layout: 'fit', + plain: true, + bodyStyle: 'padding:5px;', + buttonAlign: 'right', + items: poamOptionsPanel + }) + appwindow.show(document.body) + poamOptionsPanel.setFormatDisplay(localStorage.getItem('poam-format') ?? 'EMASS') + } + const config = { layout: 'border', border: false, @@ -859,13 +955,7 @@ SM.FindingsPanel = Ext.extend(Ext.Panel, { } Ext.apply(this, Ext.apply(this.initialConfig, config)) - SM.FindingsPanel.superclass.initComponent.call(this) - - // parent.store.load({ - // params: { - // aggregator: parent.aggValue - // } - // }) + this.superclass().initComponent.call(this) } }) diff --git a/client/src/js/findingsSummary.js b/client/src/js/findingsSummary.js index 4ce3d2faf..ef39a57b7 100644 --- a/client/src/js/findingsSummary.js +++ b/client/src/js/findingsSummary.js @@ -13,7 +13,7 @@ function addFindingsSummary( params ) { const aggregator = 'groupId' - const findingsPanel = new SM.FindingsPanel({ + const findingsPanel = new SM.Findings.FindingsPanel({ collectionId: collectionId, aggregator: aggregator }) @@ -36,7 +36,7 @@ function addFindingsSummary( params ) { } findingsTab.makePermanent = function () { findingsTab.sm_tabMode = 'permanent' - findingsTab.updateTitle.call(findingsTab) + findingsTab.updateTitle(findingsTab) } let tp = Ext.getCmp('main-tab-panel') @@ -49,7 +49,7 @@ function addFindingsSummary( params ) { } else { thisTab = tp.add( findingsTab ) } - thisTab.updateTitle.call(thisTab) + thisTab.updateTitle(thisTab) thisTab.show(); findingsPanel.parent.getStore().load({ diff --git a/client/src/js/review.js b/client/src/js/review.js index f51b17d6e..da0bd2a59 100644 --- a/client/src/js/review.js +++ b/client/src/js/review.js @@ -1111,7 +1111,6 @@ async function addReview( params ) { canAccept, fieldSettings: apiFieldSettings, btnHandler: function (btn) { - console.log(btn) saveReview({ source: 'form', type: btn.actionType diff --git a/test/api/postman_collection.json b/test/api/postman_collection.json index 0ed92f049..4f266b6f5 100644 --- a/test/api/postman_collection.json +++ b/test/api/postman_collection.json @@ -3481,7 +3481,7 @@ "name": "poam", "item": [ { - "name": "Return a POAM-like spreadsheet aggregated by groupId", + "name": "Return an EMASS formatted POAM-like spreadsheet aggregated by groupId", "event": [ { "listen": "test", @@ -3497,7 +3497,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{baseUrl}}/collections/:collectionId/poam?aggregator=groupId&date=01%2F01%2F1970&office=MyOffice&status=Ongoing&acceptedOnly=true", + "raw": "{{baseUrl}}/collections/:collectionId/poam?format=EMASS&aggregator=groupId&date=01%2F01%2F1970&office=MyOffice&status=Ongoing&acceptedOnly=true&mccastPackageId=PackageID&mccastAuthName=AuthPackageName", "host": [ "{{baseUrl}}" ], @@ -3507,6 +3507,10 @@ "poam" ], "query": [ + { + "key": "format", + "value": "EMASS" + }, { "key": "aggregator", "value": "groupId", @@ -3532,6 +3536,14 @@ "key": "status", "value": "", "disabled": true + }, + { + "key": "mccastPackageId", + "value": "PackageID" + }, + { + "key": "mccastAuthName", + "value": "AuthPackageName" } ], "variable": [ @@ -3545,7 +3557,7 @@ "response": [] }, { - "name": "Return a POAM-like spreadsheet aggregated by ruleId", + "name": "Return an EMASS formatted POAM-like spreadsheet aggregated by ruleId", "event": [ { "listen": "test", @@ -3561,7 +3573,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{baseUrl}}/collections/:collectionId/poam?aggregator=ruleId&date=01%2F01%2F1970&office=MyOffice&status=Ongoing", + "raw": "{{baseUrl}}/collections/:collectionId/poam?format=EMASS&aggregator=ruleId&date=01%2F01%2F1970&office=MyOffice&status=Ongoing&mccastPackageId=PackageID&mccastAuthName=AuthPackageName", "host": [ "{{baseUrl}}" ], @@ -3571,6 +3583,10 @@ "poam" ], "query": [ + { + "key": "format", + "value": "EMASS" + }, { "key": "aggregator", "value": "ruleId" @@ -3586,6 +3602,156 @@ { "key": "status", "value": "Ongoing" + }, + { + "key": "mccastPackageId", + "value": "PackageID" + }, + { + "key": "mccastAuthName", + "value": "AuthPackageName" + } + ], + "variable": [ + { + "key": "collectionId", + "value": "{{testCollection}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Return an EMASS formatted POAM-like spreadsheet aggregated by groupId", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Tests are at poam folder level" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/collections/:collectionId/poam?format=MCCAST&aggregator=groupId&date=01%2F01%2F1970&office=MyOffice&status=Started&acceptedOnly=true&mccastPackageId=PackageID&mccastAuthName=AuthPackageName", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "collections", + ":collectionId", + "poam" + ], + "query": [ + { + "key": "format", + "value": "MCCAST" + }, + { + "key": "aggregator", + "value": "groupId", + "description": "(Required) Aggregate the findings on this property" + }, + { + "key": "date", + "value": "01%2F01%2F1970" + }, + { + "key": "office", + "value": "MyOffice" + }, + { + "key": "status", + "value": "Started" + }, + { + "key": "acceptedOnly", + "value": "true" + }, + { + "key": "status", + "value": "", + "disabled": true + }, + { + "key": "mccastPackageId", + "value": "PackageID" + }, + { + "key": "mccastAuthName", + "value": "AuthPackageName" + } + ], + "variable": [ + { + "key": "collectionId", + "value": "{{testCollection}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Return a MCCAST formatted POAM-like spreadsheet aggregated by ruleId", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Tests are at poam folder level" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/collections/:collectionId/poam?format=MCCAST&aggregator=groupId&date=01%2F01%2F1970&office=MyOffice&status=Ongoing&acceptedOnly=true&mccastPackageId=PackageID&mccastAuthName=AuthPackageName", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "collections", + ":collectionId", + "poam" + ], + "query": [ + { + "key": "format", + "value": "MCCAST" + }, + { + "key": "aggregator", + "value": "ruleId" + }, + { + "key": "date", + "value": "01%2F01%2F1970" + }, + { + "key": "office", + "value": "MyOffice" + }, + { + "key": "status", + "value": "Started" + }, + { + "key": "mccastPackageId", + "value": "PackageID" + }, + { + "key": "mccastAuthName", + "value": "AuthPackageName" } ], "variable": [