diff --git a/CHANGELOG.md b/CHANGELOG.md index 130a3e9ecc..44a4ed474c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [9.0.0] - unreleased ### Added +- List NVT of the found CVEs at the report details page [#1673](https://github.com/greenbone/gsa/pull/1673) - Added links for GOS 6 manual for audits, policies and TLS certificates [#1657](https://github.com/greenbone/gsa/pull/1657) - Added OSP Sensor type to GSA [#1646](https://github.com/greenbone/gsa/pull/1646) - Added TLS certificate filter type [#1630](https://github.com/greenbone/gsa/pull/1630) @@ -67,6 +68,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). requests in gsad [#1355](https://github.com/greenbone/gsa/pull/1355) ### Fixed +- Fixed parsing report details data [#1673](https://github.com/greenbone/gsa/pull/1673) - Fixed scanconfig clone icon tooltip does not show if permission is denied [#1664](https://github.com/greenbone/gsa/pull/1664) - Fixed feed status page does not render [#1628](https://github.com/greenbone/gsa/pull/1628) - fixed secinfo severitybars not displaying severity.[#1530](https://github.com/greenbone/gsa/pull/1530) @@ -98,7 +100,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Removed Clone and Verify functionalities for report formats [#1650](https://github.com/greenbone/gsa/pull/1650) - Use new [React context API](https://reactjs.org/docs/context.html#api) [#1637](https://github.com/greenbone/gsa/pull/1637) -- Update response data parsing in Model classes [#1633](https://github.com/greenbone/gsa/pull/1633) +- Update response data parsing in Model classes + [#1633](https://github.com/greenbone/gsa/pull/1633), + [#1668](https://github.com/greenbone/gsa/pull/1668) - Fix statusbar content can be more than 100% and add progressbar colors to theme [1621](https://github.com/greenbone/gsa/pull/1621) - Allow to overwrite details=1 for command results.get() [#1618](https://github.com/greenbone/gsa/pull/1618) - Ensure not to request the report details when loading a list of reports [#1617](https://github.com/greenbone/gsa/pull/1617) diff --git a/gsa/CMakeLists.txt b/gsa/CMakeLists.txt index 427f7e7318..2a00a9b9db 100644 --- a/gsa/CMakeLists.txt +++ b/gsa/CMakeLists.txt @@ -156,7 +156,6 @@ set (GSA_JS_SRC_FILES ${GSA_SRC_DIR}/src/gmp/models/report/report.js ${GSA_SRC_DIR}/src/gmp/models/report/task.js ${GSA_SRC_DIR}/src/gmp/models/report/tlscertificate.js - ${GSA_SRC_DIR}/src/gmp/models/report/vulnerability.js ${GSA_SRC_DIR}/src/gmp/models/result.js ${GSA_SRC_DIR}/src/gmp/models/role.js ${GSA_SRC_DIR}/src/gmp/models/scanconfig.js diff --git a/gsa/src/gmp/__tests__/parser.js b/gsa/src/gmp/__tests__/parser.js index 0541f0ce85..933d2bbcd9 100644 --- a/gsa/src/gmp/__tests__/parser.js +++ b/gsa/src/gmp/__tests__/parser.js @@ -330,6 +330,13 @@ describe('setProperties tests', () => { expect(obj.lorem).toEqual('ipsum'); expect(Object.keys(obj)).toEqual(expect.arrayContaining(['foo', 'lorem'])); + }); + + test('should not allow to override set properties', () => { + const obj = setProperties({ + foo: 'bar', + lorem: 'ipsum', + }); expect(() => { obj.foo = 'a'; @@ -339,6 +346,26 @@ describe('setProperties tests', () => { }).toThrow(); }); + test('should allow to override set properties if requested', () => { + const obj = setProperties( + { + foo: 'bar', + lorem: 'ipsum', + }, + {}, + {writable: true}, + ); + + expect(obj.foo).toEqual('bar'); + expect(obj.lorem).toEqual('ipsum'); + + obj.foo = 'a'; + obj.lorem = 'b'; + + expect(obj.foo).toEqual('a'); + expect(obj.lorem).toEqual('b'); + }); + test('should skip properties starting with underscore', () => { const obj = setProperties({ foo: 'bar', diff --git a/gsa/src/gmp/models/__tests__/nvt.js b/gsa/src/gmp/models/__tests__/nvt.js index 6783a6f564..3adb8cf489 100644 --- a/gsa/src/gmp/models/__tests__/nvt.js +++ b/gsa/src/gmp/models/__tests__/nvt.js @@ -19,7 +19,7 @@ /* eslint-disable max-len */ -import Nvt from 'gmp/models/nvt'; +import Nvt, {getRefs, hasRefType, getFilteredRefIds} from 'gmp/models/nvt'; import Info from 'gmp/models/info'; import {testModelFromElement, testModelMethods} from 'gmp/models/testing'; @@ -30,11 +30,14 @@ describe('nvt Model tests', () => { test('should parse NVT oid as id', () => { const nvt1 = Nvt.fromElement({_oid: '42.1337'}); const nvt2 = Nvt.fromElement({}); + const nvt3 = Nvt.fromElement({nvt: {_oid: '1.2.3'}}); expect(nvt1.id).toEqual('42.1337'); expect(nvt1.oid).toEqual('42.1337'); expect(nvt2.id).toBeUndefined(); expect(nvt2.oid).toBeUndefined(); + expect(nvt3.oid).toEqual('1.2.3'); + expect(nvt3.id).toEqual('1.2.3'); }); test('should not allow to overwrite id', () => { @@ -52,14 +55,17 @@ describe('nvt Model tests', () => { }); test('should parse nvt_type', () => { - const nvt = Nvt.fromElement({_type: 'foo'}); + const nvt1 = Nvt.fromElement({_type: 'foo'}); + const nvt2 = Nvt.fromElement({nvt: {_type: 'foo'}}); - expect(nvt.nvtType).toEqual('foo'); + expect(nvt1.nvtType).toEqual('foo'); + expect(nvt2.nvtType).toEqual('foo'); }); test('should parse tags', () => { const nvt1 = Nvt.fromElement({tags: 'bv=/A:P|st=vf'}); const nvt2 = Nvt.fromElement({}); + const nvt3 = Nvt.fromElement({nvt: {tags: 'bv=/A:P|st=vf'}}); const res = { bv: '/A:P', st: 'vf', @@ -67,6 +73,7 @@ describe('nvt Model tests', () => { expect(nvt1.tags).toEqual(res); expect(nvt2.tags).toEqual({}); + expect(nvt3.tags).toEqual(res); }); test('should parse refs', () => { @@ -110,6 +117,7 @@ describe('nvt Model tests', () => { }; const nvt1 = Nvt.fromElement(elem); const nvt2 = Nvt.fromElement({}); + const nvt3 = Nvt.fromElement({nvt: elem}); expect(nvt1.cves).toEqual(['cveId', 'cve_idId']); expect(nvt2.cves).toEqual([]); @@ -123,15 +131,28 @@ describe('nvt Model tests', () => { expect(nvt2.certs).toEqual([]); expect(nvt1.xrefs).toEqual([{ref: 'customId', type: 'custom-type'}]); expect(nvt2.xrefs).toEqual([]); + + expect(nvt3.cves).toEqual(['cveId', 'cve_idId']); + expect(nvt3.bids).toEqual(['bidId', 'bugtraq_idId']); + expect(nvt3.certs).toEqual([ + {id: 'dfn-certId', type: 'dfn-cert'}, + {id: 'DFN-certId', type: 'dfn-cert'}, + {id: 'cert-bundId', type: 'cert-bund'}, + ]); + expect(nvt3.xrefs).toEqual([{ref: 'customId', type: 'custom-type'}]); }); test('should parse severity', () => { const nvt1 = Nvt.fromElement({cvss_base: '8.5'}); const nvt2 = Nvt.fromElement({cvss_base: ''}); + const nvt3 = Nvt.fromElement({nvt: {cvss_base: '9.5'}}); expect(nvt1.severity).toEqual(8.5); expect(nvt1.cvss_base).toBeUndefined(); expect(nvt2.severity).toBeUndefined(); + expect(nvt2.cvss_base).toBeUndefined(); + expect(nvt3.cvss_base).toBeUndefined(); + expect(nvt3.severity).toEqual(9.5); }); test('should parse preferences', () => { @@ -154,9 +175,11 @@ describe('nvt Model tests', () => { ]; const nvt1 = Nvt.fromElement({}); const nvt2 = Nvt.fromElement(elem); + const nvt3 = Nvt.fromElement({nvt: elem}); expect(nvt1.preferences).toEqual([]); expect(nvt2.preferences).toEqual(res); + expect(nvt3.preferences).toEqual(res); }); test('should parse xrefs with correct protocol', () => { @@ -175,6 +198,11 @@ describe('nvt Model tests', () => { refs: {ref: [{_type: 'URL', _id: 'ftps://42'}]}, }); const nvt7 = Nvt.fromElement({refs: {ref: [{_id: 'ftps://42'}]}}); + const nvt8 = Nvt.fromElement({ + nvt: { + refs: {ref: [{_type: 'URL', _id: 'https://42'}]}, + }, + }); expect(nvt1.xrefs).toEqual([{ref: '42', type: 'other'}]); expect(nvt2.xrefs).toEqual([{ref: 'http://42', type: 'url'}]); @@ -184,6 +212,7 @@ describe('nvt Model tests', () => { expect(nvt6.xrefs).toEqual([{ref: 'ftps://42', type: 'url'}]); expect(nvt7.xrefs).toEqual([{ref: 'ftps://42', type: 'other'}]); expect(nvt7.xref).toBeUndefined(); + expect(nvt8.xrefs).toEqual([{ref: 'https://42', type: 'url'}]); }); test('should parse qod', () => { @@ -193,6 +222,7 @@ describe('nvt Model tests', () => { const nvt4 = Nvt.fromElement({qod: {type: ''}}); const nvt5 = Nvt.fromElement({qod: {type: 'foo'}}); const nvt6 = Nvt.fromElement({qod: {value: '75.5', type: 'foo'}}); + const nvt7 = Nvt.fromElement({nvt: {qod: {value: '75.5', type: 'foo'}}}); expect(nvt1.qod).toBeUndefined(); expect(nvt2.qod.value).toBeUndefined(); @@ -200,26 +230,183 @@ describe('nvt Model tests', () => { expect(nvt4.qod.type).toBeUndefined(); expect(nvt5.qod.type).toEqual('foo'); expect(nvt6.qod).toEqual({value: 75.5, type: 'foo'}); + expect(nvt7.qod).toEqual({value: 75.5, type: 'foo'}); }); test('should parse default_timeout', () => { const nvt1 = Nvt.fromElement({}); const nvt2 = Nvt.fromElement({default_timeout: ''}); const nvt3 = Nvt.fromElement({default_timeout: '123'}); + const nvt4 = Nvt.fromElement({nvt: {default_timeout: '123'}}); expect(nvt1.defaultTimeout).toBeUndefined(); expect(nvt2.defaultTimeout).toBeUndefined(); expect(nvt3.defaultTimeout).toEqual(123); expect(nvt3.default_timeout).toBeUndefined(); + expect(nvt4.defaultTimeout).toEqual(123); + expect(nvt4.default_timeout).toBeUndefined(); }); test('should parse timeout', () => { const nvt1 = Nvt.fromElement({}); const nvt2 = Nvt.fromElement({timeout: ''}); const nvt3 = Nvt.fromElement({timeout: '123'}); + const nvt4 = Nvt.fromElement({nvt: {timeout: '123'}}); expect(nvt1.timeout).toBeUndefined(); expect(nvt2.timeout).toBeUndefined(); expect(nvt3.timeout).toEqual(123); + expect(nvt4.timeout).toEqual(123); + }); +}); + +describe('getRefs tests', () => { + test('should return empty array for undefined element', () => { + const refs = getRefs(); + + expect(refs).toEqual([]); + }); + + test('should return empty array for empty object', () => { + const refs = getRefs({}); + + expect(refs).toEqual([]); + }); + + test('should return empty array for empty refs', () => { + const refs = getRefs({refs: {}}); + + expect(refs).toEqual([]); + }); + + test('should return refs ref', () => { + const refs = getRefs({ + refs: { + ref: [], + }, + }); + + expect(refs).toEqual([]); + }); + + test('should return array for single ref', () => { + const refs = getRefs({ + refs: { + ref: [ + { + foo: 'bar', + }, + ], + }, + }); + + expect(refs.length).toEqual(1); + expect(refs[0]).toEqual({foo: 'bar'}); + }); + + test('should return all refs', () => { + const refs = getRefs({ + refs: { + ref: [ + { + foo: 'bar', + }, + { + lorem: 'ipsum', + }, + ], + }, + }); + + expect(refs.length).toEqual(2); + expect(refs[0]).toEqual({foo: 'bar'}); + expect(refs[1]).toEqual({lorem: 'ipsum'}); + }); +}); + +describe('hasRefType tests', () => { + test('should return false for undefined ref', () => { + expect(hasRefType('foo')()).toEqual(false); + }); + + test('should return false for empty ref', () => { + expect(hasRefType('foo')({})).toEqual(false); + }); + + test('should return false for non string type', () => { + expect(hasRefType('foo')({_type: 1})).toEqual(false); + }); + + test('should return false when searching for other type', () => { + expect(hasRefType('foo')({_type: 'bar'})).toEqual(false); + }); + + test('should return true when searching for same type', () => { + expect(hasRefType('foo')({_type: 'foo'})).toEqual(true); + }); + + test('should ignore case for type', () => { + expect(hasRefType('foo')({_type: 'Foo'})).toEqual(true); + expect(hasRefType('foo')({_type: 'FOO'})).toEqual(true); + expect(hasRefType('foo')({_type: 'FoO'})).toEqual(true); + }); +}); + +describe('getFilteredRefIds tests', () => { + test('should return empty array for undefined refs', () => { + const refs = getFilteredRefIds(undefined, 'foo'); + + expect(refs).toEqual([]); + }); + + test('should return empty array for for emtpy refs', () => { + const refs = getFilteredRefIds([], 'foo'); + + expect(refs).toEqual([]); + }); + + test('should return empty array when searching for other ref types', () => { + const refs = getFilteredRefIds( + [ + { + _type: 'bar', + _id: '1', + }, + { + _type: 'ipsum', + _id: '2', + }, + ], + 'foo', + ); + + expect(refs).toEqual([]); + }); + + test('should return ids of same type only', () => { + const refs = getFilteredRefIds( + [ + { + _type: 'bar', + _id: '1', + }, + { + _type: 'foo', + _id: '2', + }, + { + _type: 'ipsum', + _id: '3', + }, + { + _type: 'foo', + _id: '4', + }, + ], + 'foo', + ); + + expect(refs.length).toEqual(2); + expect(refs).toEqual(['2', '4']); }); }); diff --git a/gsa/src/gmp/models/nvt.js b/gsa/src/gmp/models/nvt.js index 9acfd5abaf..e7738a626d 100644 --- a/gsa/src/gmp/models/nvt.js +++ b/gsa/src/gmp/models/nvt.js @@ -42,40 +42,52 @@ const parse_tags = tags => { return newtags; }; -const getFilteredRefIds = (refs, type) => { - const filteredRefs = refs.filter(ref => ref._type === type); +export const getRefs = element => { + if ( + !isDefined(element) || + !isDefined(element.refs) || + !isDefined(element.refs.ref) + ) { + return []; + } + if (isArray(element.refs.ref)) { + return element.refs.ref; + } + return [element.refs.ref]; +}; + +export const hasRefType = refType => (ref = {}) => + isString(ref._type) && ref._type.toLowerCase() === refType; + +export const getFilteredRefIds = (refs = [], type) => { + const filteredRefs = refs.filter(hasRefType(type)); return filteredRefs.map(ref => ref._id); }; -const getFilteredRefs = (refs, type) => { - const filteredRefs = refs.filter(ref => { - const rtype = isString(ref._type) ? ref._type.toLowerCase() : undefined; - return rtype === type; - }); - const returnRefs = filteredRefs.map(ref => { +const getFilteredUrlRefs = refs => { + return refs.filter(hasRefType('url')).map(ref => { let id = ref._id; - if (type === 'url') { - if ( - !id.startsWith('http://') && - !id.startsWith('https://') && - !id.startsWith('ftp://') && - !id.startsWith('ftps://') - ) { - id = 'http://' + id; - } - return { - ref: id, - type: type, - }; + if ( + !id.startsWith('http://') && + !id.startsWith('https://') && + !id.startsWith('ftp://') && + !id.startsWith('ftps://') + ) { + id = 'http://' + id; } return { - id: id, - type: type, + ref: id, + type: 'url', }; }); - return returnRefs; }; +const getFilteredRefs = (refs, type) => + refs.filter(hasRefType(type)).map(ref => ({ + id: ref._id, + type, + })); + const getOtherRefs = refs => { const filteredRefs = refs.filter(ref => { const rtype = isString(ref._type) ? ref._type.toLowerCase() : undefined; @@ -104,18 +116,13 @@ class Nvt extends Info { static parseElement(element) { const ret = super.parseElement(element, 'nvt'); - ret.nvtType = element._type; + ret.nvtType = ret._type; ret.oid = isEmpty(ret._oid) ? undefined : ret._oid; ret.id = ret.oid; - ret.tags = parse_tags(element.tags); + ret.tags = parse_tags(ret.tags); - let refs = []; - if (isDefined(element.refs) && isArray(element.refs.ref)) { - refs = ret.refs.ref; - } else if (isDefined(element.refs) && isDefined(element.refs.ref)) { - refs = [ret.refs.ref]; - } + const refs = getRefs(ret); ret.cves = getFilteredRefIds(refs, 'cve').concat( getFilteredRefIds(refs, 'cve_id'), @@ -128,14 +135,14 @@ class Nvt extends Info { getFilteredRefs(refs, 'cert-bund'), ); - ret.xrefs = getFilteredRefs(refs, 'url').concat(getOtherRefs(refs)); + ret.xrefs = getFilteredUrlRefs(refs, 'url').concat(getOtherRefs(refs)); delete ret.refs; - ret.severity = parseSeverity(element.cvss_base); + ret.severity = parseSeverity(ret.cvss_base); delete ret.cvss_base; - if (isDefined(element.preferences)) { + if (isDefined(ret.preferences)) { ret.preferences = map(ret.preferences.preference, preference => { const pref = {...preference}; delete pref.nvt; @@ -145,29 +152,29 @@ class Nvt extends Info { ret.preferences = []; } - if (isDefined(element.qod)) { - if (isEmpty(element.qod.value)) { + if (isDefined(ret.qod)) { + if (isEmpty(ret.qod.value)) { delete ret.qod.value; } else { - ret.qod.value = parseFloat(element.qod.value); + ret.qod.value = parseFloat(ret.qod.value); } - if (isEmpty(element.qod.type)) { + if (isEmpty(ret.qod.type)) { delete ret.qod.type; } } - if (isEmpty(element.default_timeout)) { + if (isEmpty(ret.default_timeout)) { delete ret.default_timeout; } else { - ret.defaultTimeout = parseFloat(element.default_timeout); + ret.defaultTimeout = parseFloat(ret.default_timeout); delete ret.default_timeout; } - if (isEmpty(element.timeout)) { + if (isEmpty(ret.timeout)) { delete ret.timeout; } else { - ret.timeout = parseFloat(element.timeout); + ret.timeout = parseFloat(ret.timeout); } return ret; diff --git a/gsa/src/gmp/models/report/__tests__/app.js b/gsa/src/gmp/models/report/__tests__/app.js new file mode 100644 index 0000000000..6477223f9b --- /dev/null +++ b/gsa/src/gmp/models/report/__tests__/app.js @@ -0,0 +1,107 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import App from '../app'; + +describe('App tests', () => { + test('should initialize hosts', () => { + const app1 = new App(); + + expect(app1.hosts).toBeDefined(); + expect(app1.hosts.hostsByIp).toBeDefined(); + expect(app1.hosts.count).toEqual(0); + + const app2 = App.fromElement(); + + expect(app2.hosts).toBeDefined(); + expect(app2.hosts.hostsByIp).toBeDefined(); + expect(app2.hosts.count).toEqual(0); + }); + + test('should initialize occurrences', () => { + const app1 = new App(); + + expect(app1.occurrences).toBeDefined(); + expect(app1.occurrences.details).toEqual(0); + expect(app1.occurrences.total).toEqual(0); + expect(app1.occurrences.withoutDetails).toEqual(0); + + const app2 = App.fromElement(); + expect(app2.occurrences).toBeDefined(); + expect(app2.occurrences.details).toEqual(0); + expect(app2.occurrences.total).toEqual(0); + expect(app2.occurrences.withoutDetails).toEqual(0); + }); + + test('should parse cpe', () => { + const app = App.fromElement({value: 'foo'}); + + expect(app.id).toEqual('foo'); + expect(app.name).toEqual('foo'); + }); + + test('should parse severity', () => { + const app = App.fromElement({severity: '5.5'}); + + expect(app.severity).toEqual(5.5); + }); + + test('should add hosts', () => { + const app = App.fromElement(); + + expect(app.hosts).toBeDefined(); + expect(app.hosts.hostsByIp).toEqual({}); + expect(app.hosts.count).toEqual(0); + + const host = {name: 'foo', ip: '1.2.3.4'}; + app.addHost(host); + + expect(app.hosts.hostsByIp['1.2.3.4']).toEqual(host); + expect(app.hosts.count).toEqual(1); + }); + + test('should allow to add occurrence with details', () => { + const app = App.fromElement(); + + expect(app.occurrences).toBeDefined(); + expect(app.occurrences.details).toEqual(0); + expect(app.occurrences.total).toEqual(0); + expect(app.occurrences.withoutDetails).toEqual(0); + + app.addOccurence(5); + + expect(app.occurrences.details).toEqual(5); + expect(app.occurrences.total).toEqual(5); + expect(app.occurrences.withoutDetails).toEqual(0); + }); + + test('should allow to add occurrence without details', () => { + const app = App.fromElement(); + + expect(app.occurrences).toBeDefined(); + expect(app.occurrences.details).toEqual(0); + expect(app.occurrences.total).toEqual(0); + expect(app.occurrences.withoutDetails).toEqual(0); + + app.addOccurence(); + + expect(app.occurrences.details).toEqual(0); + expect(app.occurrences.total).toEqual(1); + expect(app.occurrences.withoutDetails).toEqual(1); + }); +}); diff --git a/gsa/src/gmp/models/report/__tests__/cve.js b/gsa/src/gmp/models/report/__tests__/cve.js index 8a23e8887b..e7ea8b56b5 100644 --- a/gsa/src/gmp/models/report/__tests__/cve.js +++ b/gsa/src/gmp/models/report/__tests__/cve.js @@ -19,6 +19,30 @@ import ReportCve from '../cve'; describe('ReportCve tests', () => { + test('should initialize hosts', () => { + const cve1 = new ReportCve(); + + expect(cve1.hosts).toBeDefined(); + expect(cve1.hosts.hostsByIp).toBeDefined(); + expect(cve1.hosts.count).toEqual(0); + + const cve2 = ReportCve.fromElement(); + + expect(cve2.hosts).toBeDefined(); + expect(cve2.hosts.hostsByIp).toBeDefined(); + expect(cve2.hosts.count).toEqual(0); + }); + + test('should initialize occurrences', () => { + const cve1 = new ReportCve(); + + expect(cve1.occurrences).toEqual(0); + + const cve2 = ReportCve.fromElement(); + + expect(cve2.occurrences).toEqual(0); + }); + test('should parse cves', () => { const reportcve1 = ReportCve.fromElement({}); const elem = { @@ -51,21 +75,21 @@ describe('ReportCve tests', () => { }); test('should add hosts', () => { - const reportcve = new ReportCve(); + const reportcve = ReportCve.fromElement(); expect(reportcve.hosts).toBeDefined(); - expect(reportcve.hosts.hosts_by_ip).toEqual({}); + expect(reportcve.hosts.hostsByIp).toEqual({}); expect(reportcve.hosts.count).toEqual(0); const host = {name: 'foo', ip: '1.2.3.4'}; reportcve.addHost(host); - expect(reportcve.hosts.hosts_by_ip['1.2.3.4']).toEqual(host); + expect(reportcve.hosts.hostsByIp['1.2.3.4']).toEqual(host); expect(reportcve.hosts.count).toEqual(1); }); test('should add result', () => { - const reportcve = new ReportCve(); + const reportcve = ReportCve.fromElement(); expect(reportcve.occurrences).toEqual(0); expect(reportcve.severity).toBeUndefined(); @@ -85,4 +109,11 @@ describe('ReportCve tests', () => { expect(reportcve.occurrences).toEqual(3); expect(reportcve.severity).toEqual(9.0); }); + + test('should parse nvtName', () => { + const reportcve = ReportCve.fromElement({ + nvt: {_oid: '1.2.3', name: 'Foo'}, + }); + expect(reportcve.nvtName).toEqual('Foo'); + }); }); diff --git a/gsa/src/gmp/models/report/__tests__/host.js b/gsa/src/gmp/models/report/__tests__/host.js new file mode 100644 index 0000000000..d035faf0b6 --- /dev/null +++ b/gsa/src/gmp/models/report/__tests__/host.js @@ -0,0 +1,227 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import ReportHost from '../host'; +import {isDate} from 'gmp/models/date'; + +describe('ReportHost tests', () => { + test('should initialize result counts', () => { + const host1 = new ReportHost(); + + expect(host1.result_counts).toBeDefined(); + expect(host1.result_counts.false_positive).toEqual(0); + expect(host1.result_counts.high).toEqual(0); + expect(host1.result_counts.info).toEqual(0); + expect(host1.result_counts.log).toEqual(0); + expect(host1.result_counts.warning).toEqual(0); + expect(host1.result_counts.total).toEqual(0); + + const host2 = ReportHost.fromElement(); + + expect(host2.result_counts).toBeDefined(); + expect(host2.result_counts.false_positive).toEqual(0); + expect(host2.result_counts.high).toEqual(0); + expect(host2.result_counts.info).toEqual(0); + expect(host2.result_counts.log).toEqual(0); + expect(host2.result_counts.warning).toEqual(0); + expect(host2.result_counts.total).toEqual(0); + }); + + test('should initialize details', () => { + const host1 = new ReportHost(); + + expect(host1.details).toEqual({}); + + const host2 = ReportHost.fromElement(); + + expect(host2.details).toEqual({}); + }); + + test('should initialize authSuccess', () => { + const host1 = new ReportHost(); + + expect(host1.authSuccess).toEqual({}); + + const host2 = ReportHost.fromElement(); + + expect(host2.authSuccess).toEqual({}); + }); + + test('should parse asset', () => { + const host = ReportHost.fromElement({ + asset: { + _asset_id: 'a1', + }, + }); + + expect(host.asset).toBeDefined(); + expect(host.asset.id).toEqual('a1'); + }); + + test('should parse port count', () => { + const host1 = ReportHost.fromElement({ + port_count: {}, + }); + + expect(host1.port_count).toEqual(0); + + const host2 = ReportHost.fromElement({ + port_count: { + page: '2', + }, + }); + + expect(host2.port_count).toEqual(2); + }); + + test('should parse result counts', () => { + const host1 = ReportHost.fromElement({ + result_count: { + hole: {}, + warning: {}, + info: {}, + log: {}, + false_positive: {}, + }, + }); + + expect(host1.result_counts.total).toEqual(0); + expect(host1.result_counts.high).toEqual(0); + expect(host1.result_counts.warning).toEqual(0); + expect(host1.result_counts.info).toEqual(0); + expect(host1.result_counts.log).toEqual(0); + expect(host1.result_counts.false_positive).toEqual(0); + + expect(host1.result_count).toBeUndefined(); + + const host2 = ReportHost.fromElement({ + result_count: { + page: '6', + hole: { + page: '1', + }, + warning: { + page: '2', + }, + info: { + page: '3', + }, + log: { + page: '4', + }, + false_positive: { + page: '5', + }, + }, + }); + + expect(host2.result_counts.total).toEqual(6); + expect(host2.result_counts.high).toEqual(1); + expect(host2.result_counts.warning).toEqual(2); + expect(host2.result_counts.info).toEqual(3); + expect(host2.result_counts.log).toEqual(4); + expect(host2.result_counts.false_positive).toEqual(5); + + expect(host2.result_count).toBeUndefined(); + }); + + test('should parse start and end dates', () => { + const host = ReportHost.fromElement({ + start: '2019-10-02T12:17:10+02:00', + end: '2019-10-02T12:29:22+02:00', + }); + + expect(host.start).toBeDefined(); + expect(isDate(host.start)).toEqual(true); + expect(host.end).toBeDefined(); + expect(isDate(host.end)).toEqual(true); + }); + + test('should parse auth success information', () => { + const host = ReportHost.fromElement({ + detail: [ + { + name: 'Auth-SNMP-Failure', + }, + { + name: 'Auth-SSH-Success', + }, + ], + }); + + expect(host.authSuccess.snmp).toEqual(false); + expect(host.authSuccess.ssh).toEqual(true); + expect(host.detail).toBeUndefined(); + }); + + test('should parse app information', () => { + const host = ReportHost.fromElement({ + detail: [ + { + name: 'App', + value: 'cpe1', + }, + { + name: 'App', + value: 'cpe2', + }, + ], + }); + + expect(host.details.appsCount).toEqual(2); + expect(host.detail).toBeUndefined(); + }); + + test('should parse detail information', () => { + const host = ReportHost.fromElement({ + detail: [ + { + name: 'hostname', + value: 'foo.bar', + }, + { + name: 'best_os_cpe', + value: 'cpe:/foo/bar', + }, + { + name: 'best_os_txt', + value: 'Foo OS', + }, + { + name: 'traceroute', + value: '1.1.1.1,2.2.2.2,3.3.3.3', + }, + ], + }); + + expect(host.detail).toBeUndefined(); + expect(host.hostname).toEqual('foo.bar'); + expect(host.details.best_os_cpe).toEqual('cpe:/foo/bar'); + expect(host.details.best_os_txt).toEqual('Foo OS'); + expect(host.details.distance).toEqual(2); + }); + + test('should parse ip as id', () => { + const host = ReportHost.fromElement({ + ip: '1.2.3.4', + }); + + expect(host.ip).toEqual('1.2.3.4'); + expect(host.id).toEqual('1.2.3.4'); + }); +}); diff --git a/gsa/src/gmp/models/report/__tests__/os.js b/gsa/src/gmp/models/report/__tests__/os.js new file mode 100644 index 0000000000..df3e566013 --- /dev/null +++ b/gsa/src/gmp/models/report/__tests__/os.js @@ -0,0 +1,79 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import ReportOperatingSystem from '../os'; + +describe('ReportOperatingSystem tests', () => { + test('should initialize hosts', () => { + const os1 = new ReportOperatingSystem(); + + expect(os1.hosts).toBeDefined(); + expect(os1.hosts.hostsByIp).toBeDefined(); + expect(os1.hosts.count).toEqual(0); + + const os2 = ReportOperatingSystem.fromElement(); + + expect(os2.hosts).toBeDefined(); + expect(os2.hosts.hostsByIp).toBeDefined(); + expect(os2.hosts.count).toEqual(0); + }); + + test('should add host', () => { + const os = ReportOperatingSystem.fromElement(); + + expect(os.hosts).toBeDefined(); + expect(os.hosts.hostsByIp).toEqual({}); + expect(os.hosts.count).toEqual(0); + + const host = {name: 'foo', ip: '1.2.3.4'}; + os.addHost(host); + + expect(os.hosts.hostsByIp['1.2.3.4']).toEqual(host); + expect(os.hosts.count).toEqual(1); + }); + + test('should allow to set severity', () => { + const os = ReportOperatingSystem.fromElement(); + + expect(os.severity).toBeUndefined(); + + os.setSeverity(5.5); + + expect(os.severity).toEqual(5.5); + + os.setSeverity(3.5); + + expect(os.severity).toEqual(5.5); + + os.setSeverity(9.5); + + expect(os.severity).toEqual(9.5); + }); + + test('should parse best os', () => { + const os = ReportOperatingSystem.fromElement({ + best_os_cpe: 'cpe:/foo/bar', + best_os_txt: 'Foo OS', + }); + + expect(os.name).toEqual('Foo OS'); + expect(os.id).toEqual('cpe:/foo/bar'); + expect(os.cpe).toEqual('cpe:/foo/bar'); + }); +}); diff --git a/gsa/src/gmp/models/report/__tests__/parser.js b/gsa/src/gmp/models/report/__tests__/parser.js index 3e08010a8d..a6fbee54b2 100644 --- a/gsa/src/gmp/models/report/__tests__/parser.js +++ b/gsa/src/gmp/models/report/__tests__/parser.js @@ -17,10 +17,17 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {parseHosts} from '../parser'; +import { + parseHosts, + parsePorts, + parseApps, + parseOperatingSystems, + parseTlsCertificates, + parseCves, +} from '../parser'; describe('report parser tests', () => { - test('parse_hosts tests', () => { + test('should parse hosts', () => { const hosts = { host: [ { @@ -78,6 +85,663 @@ describe('report parser tests', () => { expect(parsedHosts.counts).toEqual(countsResult); expect(parsedHosts.filter).toEqual('foo=bar'); }); + + test('should parse empty hosts', () => { + const filterString = 'foo=bar'; + const hosts = parseHosts({}, filterString); + const counts = { + first: 0, + all: 0, + filtered: 0, + length: 0, + rows: 0, + last: 0, + }; + + expect(hosts.entities.length).toEqual(0); + expect(hosts.counts).toEqual(counts); + expect(hosts.filter).toEqual('foo=bar'); + }); + + test('should parse ports', () => { + const filterString = 'foo=bar rows=5'; + const report = { + ports: { + count: 123, + port: [ + {__text: '123/tcp', host: '1.2.3.4', severity: 5.5, threat: 'Medium'}, + {__text: '234/udp', host: '1.2.3.5', severity: 1.0, threat: 'Log'}, + {__text: '234/udp', host: '1.2.3.6', severity: 9.0, threat: 'High'}, + {__text: '234/udp', host: '1.2.3.5', severity: 7.5, threat: 'High'}, + { + __text: 'general/tcp', + host: '1.2.3.4', + severity: 5, + threat: 'Medium', + }, + {host: '1.2.3.4', severity: 9, threat: 'High'}, + ], + }, + }; + const counts = { + first: 1, + all: 123, + filtered: 2, + length: 2, + rows: 2, + last: 2, + }; + const ports = parsePorts(report, filterString); + + expect(ports.entities.length).toEqual(2); + expect(ports.counts).toEqual(counts); + expect(ports.filter).toEqual('foo=bar rows=5'); + + const [port1, port2] = ports.entities; + + expect(port1.id).toEqual('123/tcp'); + expect(port1.threat).toEqual('Medium'); + expect(port1.severity).toEqual(5.5); + expect(port1.number).toEqual(123); + expect(port1.protocol).toEqual('tcp'); + expect(port1.hosts.count).toEqual(1); + + expect(port2.id).toEqual('234/udp'); + expect(port2.threat).toEqual('Log'); + expect(port2.severity).toEqual(9.0); + expect(port2.number).toEqual(234); + expect(port2.protocol).toEqual('udp'); + expect(port2.hosts.count).toEqual(2); + }); + + test('should parse empty ports', () => { + const filterString = 'foo=bar'; + const report = {}; + const counts = { + first: 0, + all: 0, + filtered: 0, + length: 0, + rows: 0, + last: 0, + }; + const ports = parsePorts(report, filterString); + + expect(ports.entities.length).toEqual(0); + expect(ports.counts).toEqual(counts); + expect(ports.filter).toEqual('foo=bar'); + }); + + test('should parse apps', () => { + const filterString = 'foo=bar rows=5'; + const report = { + // apps are gathered from the host details + host: [ + { + detail: [ + { + name: 'App', + value: 'cpe:/a:foo:bar', + }, + ], + ip: '1.1.1.1', + }, + { + detail: [ + { + name: 'App', + value: 'cpe:/a:foo:bar', + }, + { + name: 'cpe:/a:foo:bar', + value: '123/tcp', + }, + ], + ip: '2.2.2.2', + }, + { + detail: [ + { + name: 'App', + value: 'cpe:/a:lorem:ipsum', + }, + ], + ip: '1.1.1.1', + }, + ], + apps: { + count: '123', + }, + // results are used to get the app severity + results: { + result: [ + { + severity: '5.5', + detection: { + result: { + details: { + detail: [ + { + name: 'foo', // another details that gets ignored + value: 'foo/bar', + }, + { + name: 'product', + value: 'cpe:/a:foo:bar', + }, + ], + }, + }, + }, + }, + { + severity: '7.5', + detection: { + result: { + details: { + detail: [ + { + name: 'product', + value: 'cpe:/a:foo:bar', + }, + ], + }, + }, + }, + }, + { + severity: '4.5', + detection: { + result: { + details: { + detail: [ + { + name: 'product', + value: 'cpe:/a:foo:bar', + }, + ], + }, + }, + }, + }, + { + severity: '5.5', + detection: { + result: { + details: { + detail: [ + { + name: 'product', + value: 'cpe:/a:lorem:ipsum', + }, + ], + }, + }, + }, + }, + ], + }, + }; + const counts = { + first: 1, + all: 123, + filtered: 2, + length: 2, + rows: 2, + last: 2, + }; + const apps = parseApps(report, filterString); + + expect(apps.entities.length).toEqual(2); + expect(apps.counts).toEqual(counts); + expect(apps.filter).toEqual('foo=bar rows=5'); + + const [app1, app2] = apps.entities; + + expect(app1.id).toEqual('cpe:/a:foo:bar'); + expect(app1.name).toEqual('cpe:/a:foo:bar'); + expect(app1.severity).toEqual(7.5); + expect(app1.hosts.count).toEqual(2); + expect(app1.occurrences.details).toEqual(1); + expect(app1.occurrences.withoutDetails).toEqual(0); + expect(app1.occurrences.total).toEqual(1); + + expect(app2.id).toEqual('cpe:/a:lorem:ipsum'); + expect(app2.name).toEqual('cpe:/a:lorem:ipsum'); + expect(app2.severity).toEqual(5.5); + expect(app2.hosts.count).toEqual(1); + expect(app2.occurrences.details).toEqual(0); + expect(app2.occurrences.withoutDetails).toEqual(1); + expect(app2.occurrences.total).toEqual(1); + }); + + test('should parse empty apps', () => { + const filterString = 'foo=bar rows=5'; + const report = {}; + const counts = { + first: 0, + all: 0, + filtered: 0, + length: 0, + rows: 0, + last: 0, + }; + const apps = parseApps(report, filterString); + + expect(apps.entities.length).toEqual(0); + expect(apps.counts).toEqual(counts); + expect(apps.filter).toEqual('foo=bar rows=5'); + }); + + test('should parse operating systems', () => { + const filterString = 'foo=bar rows=5'; + const report = { + os: { + count: '123', + }, + // os severities are parsed from the results of a host + results: { + result: [ + { + host: { + __text: '1.1.1.1', + }, + severity: '5.5', + }, + { + host: { + __text: '1.1.1.1', + }, + severity: '9.5', + }, + { + host: { + __text: '1.1.1.1', + }, + severity: '3.5', + }, + { + host: { + __text: '2.2.2.2', + }, + severity: '5.5', + }, + { + host: { + __text: '3.3.3.3', + }, + severity: '6.5', + }, + ], + }, + host: [ + { + ip: '1.1.1.1', + detail: [ + { + name: 'best_os_cpe', + value: 'cpe:/foo/os', + }, + { + name: 'best_os_txt', + value: 'Foo OS', + }, + { + // will be ignored + name: 'foo', + value: 'bar', + }, + ], + }, + { + ip: '2.2.2.2', + detail: [ + { + name: 'best_os_cpe', + value: 'cpe:/foo/os', + }, + { + name: 'best_os_txt', + value: 'Foo OS', + }, + ], + }, + { + ip: '3.3.3.3', + detail: [ + { + name: 'best_os_cpe', + value: 'cpe:/bar/os', + }, + { + name: 'best_os_txt', + value: 'Bar OS', + }, + ], + }, + ], + }; + const counts = { + first: 1, + all: 123, + filtered: 2, + length: 2, + rows: 2, + last: 2, + }; + const operatingSystems = parseOperatingSystems(report, filterString); + + expect(operatingSystems.entities.length).toEqual(2); + expect(operatingSystems.counts).toEqual(counts); + expect(operatingSystems.filter).toEqual('foo=bar rows=5'); + + const [os1, os2] = operatingSystems.entities; + + expect(os1.name).toEqual('Foo OS'); + expect(os1.id).toEqual('cpe:/foo/os'); + expect(os1.cpe).toEqual('cpe:/foo/os'); + expect(os1.severity).toEqual(9.5); + expect(os1.hosts.count).toEqual(2); + + expect(os2.name).toEqual('Bar OS'); + expect(os2.id).toEqual('cpe:/bar/os'); + expect(os2.cpe).toEqual('cpe:/bar/os'); + expect(os2.severity).toEqual(6.5); + expect(os2.hosts.count).toEqual(1); + }); + + test('should parse empty operating systems', () => { + const filterString = 'foo=bar rows=5'; + const report = {}; + const counts = { + first: 0, + all: 0, + filtered: 0, + length: 0, + rows: 0, + last: 0, + }; + const operatingSystems = parseOperatingSystems(report, filterString); + + expect(operatingSystems.entities.length).toEqual(0); + expect(operatingSystems.counts).toEqual(counts); + expect(operatingSystems.filter).toEqual('foo=bar rows=5'); + }); + + test('should parse tls certificates', () => { + const filterString = 'foo=bar rows=5'; + const report = { + host: [ + { + ip: '1.1.1.1', + detail: [ + { + name: 'SSLInfo', + value: '123::fingerprint1', + }, + { + name: 'SSLDetails:fingerprint1', + value: + 'issuer:CN=Foo Bar,O=Foo Bar,C=BM|serial:foobar|notBefore:20150930T144006|notAfter:20120930T145000', + }, + { + name: 'Cert:fingerprint1', + value: 'x509:foobar', + }, + { + name: 'hostname', + value: 'foo.bar', + }, + ], + }, + + { + ip: '2.2.2.2', + detail: [ + { + name: 'SSLInfo', + value: '123::fingerprint2', + }, + { + name: 'SSLInfo', + value: '234::fingerprint2', + }, + { + name: 'SSLInfo', + value: '234::fingerprint1', + }, + ], + }, + ], + ssl_certs: {count: '123'}, + }; + const counts = { + first: 1, + all: 123, + filtered: 4, + length: 4, + rows: 4, + last: 4, + }; + const tlsCerts = parseTlsCertificates(report, filterString); + + expect(tlsCerts.entities.length).toEqual(4); + expect(tlsCerts.counts).toEqual(counts); + expect(tlsCerts.filter).toEqual('foo=bar rows=5'); + + const [cert1, cert2, cert3, cert4] = tlsCerts.entities; + + expect(cert1.fingerprint).toEqual('fingerprint1'); + expect(cert1.hostname).toEqual('foo.bar'); + expect(cert1.ip).toEqual('1.1.1.1'); + expect(cert1.data).toEqual('foobar'); + expect(cert1._data).toEqual('x509:foobar'); + expect(cert1.ports).toBeUndefined(); + expect(cert1.port).toEqual(123); + + expect(cert2.fingerprint).toEqual('fingerprint2'); + expect(cert2.hostname).toBeUndefined(); + expect(cert2.ip).toEqual('2.2.2.2'); + expect(cert2.data).toBeUndefined(); + expect(cert2._data).toBeUndefined(); + expect(cert2.ports).toBeUndefined(); + expect(cert2.port).toEqual(123); + + expect(cert3.fingerprint).toEqual('fingerprint2'); + expect(cert3.hostname).toBeUndefined(); + expect(cert3.ip).toEqual('2.2.2.2'); + expect(cert3.data).toBeUndefined(); + expect(cert3._data).toBeUndefined(); + expect(cert3.ports).toBeUndefined(); + expect(cert3.port).toEqual(234); + + expect(cert4.fingerprint).toEqual('fingerprint1'); + expect(cert4.ip).toEqual('2.2.2.2'); + expect(cert4.data).toBeUndefined(); + expect(cert4._data).toBeUndefined(); + expect(cert4.ports).toBeUndefined(); + expect(cert4.port).toEqual(234); + }); + + test('should parse empty tls certificates', () => { + const filterString = 'foo=bar rows=5'; + const report = {}; + const counts = { + first: 0, + all: 0, + filtered: 0, + length: 0, + rows: 0, + last: 0, + }; + const tlsCerts = parseTlsCertificates(report, filterString); + + expect(tlsCerts.entities.length).toEqual(0); + expect(tlsCerts.counts).toEqual(counts); + expect(tlsCerts.filter).toEqual('foo=bar rows=5'); + }); + + test('should parse empty cves', () => { + const filterString = 'foo=bar rows=5'; + const report = {}; + const counts = { + first: 0, + all: 0, + filtered: 0, + length: 0, + rows: 0, + last: 0, + }; + const cves = parseCves(report, filterString); + + expect(cves.entities.length).toEqual(0); + expect(cves.counts).toEqual(counts); + expect(cves.filter).toEqual('foo=bar rows=5'); + }); + + test('should parse cves', () => { + const filterString = 'foo=bar rows=5'; + const report = { + results: { + result: [ + { + nvt: { + refs: { + ref: [{}], + }, + }, + }, + { + nvt: { + refs: { + ref: [ + { + _type: '', + }, + ], + }, + }, + }, + { + nvt: { + _oid: '1.2.3', + name: 'Foo', + refs: { + ref: [ + { + _type: 'cve', + _id: 'CVE-123', + }, + ], + }, + }, + host: { + __text: '1.1.1.1', + }, + severity: '4.5', + }, + { + nvt: { + _oid: '1.2.3', + name: 'Foo', + refs: { + ref: [ + { + _type: 'cve', + _id: 'CVE-123', + }, + { + _type: 'foo', + _id: 'foo1', + }, + ], + }, + }, + host: { + __text: '2.2.2.2', + }, + severity: '9.5', + }, + { + nvt: { + _oid: '2.2.3', + name: 'Bar', + refs: { + ref: [ + { + _type: 'cve', + _id: 'CVE-234', + }, + ], + }, + }, + host: { + __text: '1.1.1.1', + }, + severity: '5.5', + }, + { + nvt: { + _oid: '2.3.3', + name: 'Ipsum', + refs: { + ref: [ + { + _type: 'cve', + _id: 'CVE-234', + }, + { + _type: 'cve', + _id: 'CVE-334', + }, + ], + }, + }, + host: { + __text: '1.1.1.1', + }, + severity: '6.5', + }, + ], + }, + }; + const counts = { + first: 1, + all: 3, + filtered: 3, + length: 3, + rows: 3, + last: 3, + }; + const cves = parseCves(report, filterString); + + expect(cves.entities.length).toEqual(3); + expect(cves.counts).toEqual(counts); + expect(cves.filter).toEqual('foo=bar rows=5'); + + const [cve1, cve2, cve3] = cves.entities; + + expect(cve1.id).toEqual('1.2.3'); + expect(cve1.nvtName).toEqual('Foo'); + expect(cve1.cves).toEqual(['CVE-123']); + expect(cve1.severity).toEqual(9.5); + expect(cve1.hosts.count).toEqual(2); + expect(cve1.occurrences).toEqual(2); + + expect(cve2.id).toEqual('2.2.3'); + expect(cve2.nvtName).toEqual('Bar'); + expect(cve2.cves).toEqual(['CVE-234']); + expect(cve2.severity).toEqual(5.5); + expect(cve2.hosts.count).toEqual(1); + expect(cve2.occurrences).toEqual(1); + + expect(cve3.id).toEqual('2.3.3'); + expect(cve3.nvtName).toEqual('Ipsum'); + expect(cve3.cves).toEqual(['CVE-234', 'CVE-334']); + expect(cve3.severity).toEqual(6.5); + expect(cve3.hosts.count).toEqual(1); + expect(cve3.occurrences).toEqual(1); + }); }); // vim: set ts=2 sw=2 tw=80: diff --git a/gsa/src/gmp/models/report/__tests__/port.js b/gsa/src/gmp/models/report/__tests__/port.js new file mode 100644 index 0000000000..255b933b92 --- /dev/null +++ b/gsa/src/gmp/models/report/__tests__/port.js @@ -0,0 +1,97 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import ReportPort from '../port'; + +describe('ReportPort tests', () => { + test('should initialize hosts', () => { + const port1 = new ReportPort(); + + expect(port1.hosts).toBeDefined(); + expect(port1.hosts.hostsByIp).toBeDefined(); + expect(port1.hosts.count).toEqual(0); + + const port2 = ReportPort.fromElement(); + + expect(port2.hosts).toBeDefined(); + expect(port2.hosts.hostsByIp).toBeDefined(); + expect(port2.hosts.count).toEqual(0); + }); + + test('should add hosts', () => { + const port = ReportPort.fromElement(); + + expect(port.hosts).toBeDefined(); + expect(port.hosts.hostsByIp).toEqual({}); + expect(port.hosts.count).toEqual(0); + + const host = {name: 'foo', ip: '1.2.3.4'}; + port.addHost(host); + + expect(port.hosts.hostsByIp['1.2.3.4']).toEqual(host); + expect(port.hosts.count).toEqual(1); + }); + + test('should allow to set severity', () => { + const port = ReportPort.fromElement(); + + expect(port.severity).toBeUndefined(); + + port.setSeverity(5.5); + + expect(port.severity).toEqual(5.5); + + port.setSeverity(3.5); + + expect(port.severity).toEqual(5.5); + + port.setSeverity(9.5); + + expect(port.severity).toEqual(9.5); + }); + + test('should parse severity', () => { + const port = ReportPort.fromElement({severity: '5.5'}); + + expect(port.severity).toEqual(5.5); + }); + + test('should parse threat', () => { + const port = ReportPort.fromElement({threat: 'Low'}); + + expect(port.threat).toEqual('Low'); + }); + + test('should parse port information', () => { + const port1 = ReportPort.fromElement({ + __text: '123/tcp', + }); + + expect(port1.id).toEqual('123/tcp'); + expect(port1.number).toEqual(123); + expect(port1.protocol).toEqual('tcp'); + + const port2 = ReportPort.fromElement({ + __text: 'general/tcp', + }); + + expect(port2.id).toEqual('general/tcp'); + expect(port2.number).toEqual(0); + expect(port2.protocol).toEqual('tcp'); + }); +}); diff --git a/gsa/src/gmp/models/report/__tests__/task.js b/gsa/src/gmp/models/report/__tests__/task.js new file mode 100644 index 0000000000..b4cf4e6723 --- /dev/null +++ b/gsa/src/gmp/models/report/__tests__/task.js @@ -0,0 +1,55 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import ReportTask from '../task'; + +describe('ReportTask tests', () => { + test('should parse id', () => { + const task = ReportTask.fromElement({_id: 't1'}); + + expect(task.id).toEqual('t1'); + }); + + test('should be container task without target', () => { + const task = ReportTask.fromElement(); + + expect(task.isContainer()).toEqual(true); + }); + + test('should parse target', () => { + const task = ReportTask.fromElement({ + target: { + _id: 't1', + }, + }); + + expect(task.target).toBeDefined(); + expect(task.target.id).toEqual('t1'); + expect(task.isContainer()).toEqual(false); + }); + + test('should parse progress', () => { + const task1 = ReportTask.fromElement({progress: {}}); + + expect(task1.progress).toEqual(0); + + const task2 = ReportTask.fromElement({progress: {__text: '99'}}); + + expect(task2.progress).toEqual(99); + }); +}); diff --git a/gsa/src/gmp/models/report/__tests__/tlscertificate.js b/gsa/src/gmp/models/report/__tests__/tlscertificate.js new file mode 100644 index 0000000000..badbb52fa4 --- /dev/null +++ b/gsa/src/gmp/models/report/__tests__/tlscertificate.js @@ -0,0 +1,93 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import ReportTlsCertificate from '../tlscertificate'; + +describe('ReportTlsCertificate tests', () => { + test('should init ports', () => { + const cert1 = new ReportTlsCertificate(); + + expect(cert1.ports).toBeDefined(); + expect(cert1.ports.length).toEqual(0); + + const cert2 = ReportTlsCertificate.fromElement(); + + expect(cert2.ports).toBeDefined(); + expect(cert2.ports.length).toEqual(0); + }); + + test('should create new ReportTlsCertificate from fingerprint', () => { + const cert = ReportTlsCertificate.fromElement({fingerprint: 'foo'}); + + expect(cert.fingerprint).toEqual('foo'); + expect(cert.ports).toEqual([]); + + /* test properties which are set during parsing */ + expect(cert.port).toBeUndefined(); + expect(cert.ip).toBeUndefined(); + expect(cert.details).toBeUndefined(); + expect(cert.data).toBeUndefined(); + expect(cert._data).toBeUndefined(); + expect(cert.hostname).toBeUndefined(); + }); + + test('should allow to set id data', () => { + const cert = ReportTlsCertificate.fromElement({fingerprint: 'foo'}); + + cert.ip = '1.2.3.4'; + cert.port = '123'; + + expect(cert.id).toEqual('1.2.3.4:123:foo'); + }); + + test('should allow to copy a tls certificate', () => { + const cert = ReportTlsCertificate.fromElement({fingerprint: 'foo'}); + + cert.ip = '1.2.3.4'; + cert.port = '123'; + cert.data = {foo: 'bar'}; + cert._data = {a: 1}; + cert.hostname = 'foo.bar'; + cert.details = {lorem: 2}; + + const cert2 = cert.copy(); + + expect(cert.ip).toEqual('1.2.3.4'); + expect(cert.port).toEqual('123'); + expect(cert.data).toEqual({foo: 'bar'}); + expect(cert._data).toEqual({a: 1}); + expect(cert.hostname).toEqual('foo.bar'); + expect(cert.details).toEqual({lorem: 2}); + expect(cert2.id).toEqual('1.2.3.4:123:foo'); + }); + + test('should allow to add a port', () => { + const cert = ReportTlsCertificate.fromElement({fingerprint: 'foo'}); + + cert.ip = '1.2.3.4'; + cert.port = '123'; + + expect(cert.id).toEqual('1.2.3.4:123:foo'); + + cert.addPort('234'); + + expect(cert.port).toEqual('123'); + expect(cert.ports.length).toEqual(1); + expect(cert.ports[0]).toEqual(234); + }); +}); diff --git a/gsa/src/gmp/models/report/app.js b/gsa/src/gmp/models/report/app.js index 54bc37215d..3875104364 100644 --- a/gsa/src/gmp/models/report/app.js +++ b/gsa/src/gmp/models/report/app.js @@ -21,48 +21,53 @@ import {isDefined} from 'gmp/utils/identity'; import {parseSeverity, setProperties} from 'gmp/parser'; class App { - constructor(elem) { - const properties = this.parseProperties(elem); - setProperties(properties, this); + constructor() { + this.hosts = { + hostsByIp: {}, + count: 0, + }; + + this.occurrences = { + details: 0, + withoutDetails: 0, + total: 0, + }; } addHost(host) { - if (!(host.ip in this.hosts.hosts_by_ip)) { - this.hosts.hosts_by_ip[host.ip] = host; + if (!(host.ip in this.hosts.hostsByIp)) { + this.hosts.hostsByIp[host.ip] = host; this.hosts.count++; } } addOccurence(count) { if (isDefined(count)) { - this.occurrences.detail += count; + this.occurrences.details += count; this.occurrences.total += count; } else { - this.occurrences.without_details += 1; + this.occurrences.withoutDetails += 1; this.occurrences.total += 1; } } - parseProperties(elem) { + static fromElement(element) { + const app = new App(); + + setProperties(this.parseElement(element), app); + + return app; + } + + static parseElement(element = {}) { const copy = {}; - const {value: cpe} = elem; + const {value: cpe} = element; copy.id = cpe; copy.name = cpe; - copy.hosts = { - hosts_by_ip: {}, - count: 0, - }; - - copy.occurrences = { - detail: 0, - without_detail: 0, - total: 0, - }; - - copy.severity = parseSeverity(elem.severity); + copy.severity = parseSeverity(element.severity); return copy; } diff --git a/gsa/src/gmp/models/report/cve.js b/gsa/src/gmp/models/report/cve.js index e6b0c1aec6..6ae5ed60c3 100644 --- a/gsa/src/gmp/models/report/cve.js +++ b/gsa/src/gmp/models/report/cve.js @@ -27,7 +27,7 @@ class ReportCve { this.occurrences = 0; this.hosts = { - hosts_by_ip: {}, + hostsByIp: {}, count: 0, }; } @@ -41,8 +41,8 @@ class ReportCve { } addHost(host) { - if (!(host.ip in this.hosts.hosts_by_ip)) { - this.hosts.hosts_by_ip[host.ip] = host; + if (!(host.ip in this.hosts.hostsByIp)) { + this.hosts.hostsByIp[host.ip] = host; this.hosts.count++; } } @@ -63,6 +63,7 @@ class ReportCve { const nvt = Nvt.fromElement(element); copy.id = nvt.id; + copy.nvtName = nvt.name; copy.cves = nvt.cves; return copy; diff --git a/gsa/src/gmp/models/report/host.js b/gsa/src/gmp/models/report/host.js index fe37b68610..df1e0f90b7 100644 --- a/gsa/src/gmp/models/report/host.js +++ b/gsa/src/gmp/models/report/host.js @@ -39,14 +39,31 @@ const parse_page_count = value => { }; class Host { - constructor(elem) { - this.parseProperties(elem); + constructor() { + this.authSuccess = {}; + this.details = {}; + this.result_counts = { + false_positive: 0, + high: 0, + info: 0, + log: 0, + warning: 0, + total: 0, + }; } - parseProperties(elem) { - const copy = {...elem}; + static fromElement(element) { + const host = new Host(); - const {asset = {}, port_count = {}, result_count} = elem; + setProperties(this.parseElement(element), host); + + return host; + } + + static parseElement(element = {}) { + const copy = {...element}; + + const {asset = {}, port_count = {}, result_count} = element; if (isEmpty(asset._asset_id)) { delete copy.asset; @@ -55,11 +72,11 @@ class Host { copy.asset.id = asset._asset_id; } - copy.port_count = parse_page_count(port_count.page); + copy.port_count = parse_page_count(port_count); if (isDefined(result_count)) { copy.result_counts = { - hole: parse_page_count(result_count.hole), + high: parse_page_count(result_count.hole), warning: parse_page_count(result_count.warning), info: parse_page_count(result_count.info), log: parse_page_count(result_count.log), @@ -77,17 +94,17 @@ class Host { }; } - copy.start = parseDate(elem.start); - copy.end = parseDate(elem.end); + copy.start = parseDate(element.start); + copy.end = parseDate(element.end); delete copy.result_count; copy.authSuccess = {}; copy.details = {}; - if (isArray(elem.detail)) { + if (isArray(element.detail)) { let appsCount = 0; - elem.detail.forEach(details => { + element.detail.forEach(details => { const {name, value} = details; switch (name) { case 'hostname': @@ -119,11 +136,9 @@ class Host { delete copy.detail; - copy.id = elem.ip; // use ip as id. we need an id for react key prop - - setProperties(copy, this); + copy.id = element.ip; // use ip as id. we need an id for react key prop - return this; + return copy; } } diff --git a/gsa/src/gmp/models/report/os.js b/gsa/src/gmp/models/report/os.js index 1a555d9f9d..97889ab403 100644 --- a/gsa/src/gmp/models/report/os.js +++ b/gsa/src/gmp/models/report/os.js @@ -21,38 +21,43 @@ import {isDefined} from 'gmp/utils/identity'; import {setProperties} from 'gmp/parser'; class OperatingSystem { - constructor(elem) { - const properties = this.parseProperties(elem); - setProperties(properties, this); + constructor() { + this.hosts = { + hostsByIp: {}, + count: 0, + }; } addHost(host) { - if (!(host.ip in this.hosts.hosts_by_ip)) { - this.hosts.hosts_by_ip[host.ip] = host; + if (!(host.ip in this.hosts.hostsByIp)) { + this.hosts.hostsByIp[host.ip] = host; this.hosts.count++; } } - addSeverity(severity) { + setSeverity(severity) { if (!isDefined(this.severity) || this.severity < severity) { this.severity = severity; } } - parseProperties(elem) { + static fromElement(element) { + const os = new OperatingSystem(); + + setProperties(this.parseElement(element), os); + + return os; + } + + static parseElement(element = {}) { const copy = {}; - const {best_os_cpe, best_os_txt} = elem; + const {best_os_cpe, best_os_txt} = element; copy.name = best_os_txt; copy.id = best_os_cpe; copy.cpe = best_os_cpe; - copy.hosts = { - hosts_by_ip: {}, - count: 0, - }; - return copy; } } diff --git a/gsa/src/gmp/models/report/parser.js b/gsa/src/gmp/models/report/parser.js index 095fbf4671..c789098189 100644 --- a/gsa/src/gmp/models/report/parser.js +++ b/gsa/src/gmp/models/report/parser.js @@ -41,9 +41,9 @@ import ReportHost from './host'; import ReportOperatingSystem from './os'; import ReportPort from './port'; import ReportTLSCertificate from './tlscertificate'; -import ReportVulnerability from './vulnerability'; import Result from '../result'; +import {getRefs, hasRefType} from '../nvt'; const emptyCollectionList = filter => { return { @@ -57,7 +57,7 @@ const getTlsCertificate = (certs, fingerprint) => { let cert = certs[fingerprint]; if (!isDefined(cert)) { - cert = new ReportTLSCertificate(fingerprint); + cert = ReportTLSCertificate.fromElement({fingerprint}); certs[fingerprint] = cert; } return cert; @@ -189,7 +189,7 @@ export const parsePorts = (report, filter) => { tport.setSeverity(severity); } else { - tport = new ReportPort(port); + tport = ReportPort.fromElement(port); temp_ports[id] = tport; } @@ -215,55 +215,6 @@ export const parsePorts = (report, filter) => { }; }; -export const parseVulnerabilities = (report, filter) => { - const temp_vulns = {}; - const {vulns, results = {}} = report; - - if (!isDefined(vulns)) { - return emptyCollectionList(filter); - } - - const {count: full_count} = vulns; - - forEach(results.result, result => { - const {nvt = {}, host} = result; - const {_oid: oid} = nvt; - - if (isDefined(oid)) { - const severity = parseSeverity(result.severity); - - let vuln = temp_vulns[oid]; - - if (isDefined(vuln)) { - vuln.addResult(results); - } else { - vuln = new ReportVulnerability(result); - temp_vulns[oid] = vuln; - } - - vuln.setSeverity(severity); - vuln.addHost(host); - } - }); - - const vulns_array = Object.values(temp_vulns); - const filtered_count = vulns_array.length; - - const counts = new CollectionCounts({ - all: full_count, - filtered: filtered_count, - first: 1, - length: filtered_count, - rows: filtered_count, - }); - - return { - entities: vulns_array, - filter: isDefined(filter) ? filter : parseFilter(report), - counts, - }; -}; - export const parseApps = (report, filter) => { const {host: hosts, apps, results = {}} = report; const apps_temp = {}; @@ -318,7 +269,7 @@ export const parseApps = (report, filter) => { let app = apps_temp[cpe]; if (!isDefined(app)) { - app = new ReportApp({...detail, severity: severities[cpe]}); + app = ReportApp.fromElement({...detail, severity: severities[cpe]}); apps_temp[cpe] = app; } @@ -417,14 +368,16 @@ export const parseOperatingSystems = (report, filter) => { const severity = severities[ip]; if (!isDefined(os)) { - os = operating_systems[best_os_cpe] = new ReportOperatingSystem({ + os = operating_systems[ + best_os_cpe + ] = ReportOperatingSystem.fromElement({ best_os_cpe, best_os_txt, }); } os.addHost(host); - os.addSeverity(severity); + os.setSeverity(severity); } } }); @@ -459,7 +412,11 @@ export const parseHosts = (report, filter) => { const hosts_array = map(hosts, host => { const {port_count = {}} = host; const severity = severities[host.ip]; - return new ReportHost({...host, severity, portsCount: port_count.page}); + return ReportHost.fromElement({ + ...host, + severity, + portsCount: port_count.page, + }); }); const {length: filtered_count} = hosts_array; @@ -657,30 +614,27 @@ export const parseCves = (report, filter) => { const cves = {}; - const results_with_cve = filter_func( - results.result, - result => result.nvt.cve !== 'NOCVE' && !isEmpty(result.nvt.cve), - ); + const results_with_cve = filter_func(results.result, result => { + const refs = getRefs(result.nvt); + return refs.some(hasRefType('cve')); + }); results_with_cve.forEach(result => { - const {host = {}, nvt = {}} = result; - const {cve: id} = nvt; + const {host = {}, nvt} = result; + const {_oid: id} = nvt; + let cve = cves[id]; - if (isDefined(id)) { - let cve = cves[id]; - - if (!isDefined(cve)) { - cve = ReportCve.fromElement(nvt); - cves[id] = cve; - } + if (!isDefined(cve)) { + cve = ReportCve.fromElement(nvt); + cves[id] = cve; + } - const {__text: ip} = host; + const {__text: ip} = host; - if (isDefined(ip)) { - cve.addHost({ip}); - } - cve.addResult(result); + if (isDefined(ip)) { + cve.addHost({ip}); } + cve.addResult(result); }); const cves_array = Object.values(cves); diff --git a/gsa/src/gmp/models/report/port.js b/gsa/src/gmp/models/report/port.js index 971f093d7e..7947f73b1a 100644 --- a/gsa/src/gmp/models/report/port.js +++ b/gsa/src/gmp/models/report/port.js @@ -23,31 +23,43 @@ import {isDefined} from '../../utils/identity'; import {setProperties, parseInt, parseSeverity} from '../../parser'; class ReportPort { - constructor(elem) { - this.parseProperties(elem); + constructor() { + this.hosts = { + hostsByIp: {}, + count: 0, + }; } addHost(host) { - if (!(host.ip in this.hosts.hosts_by_ip)) { - this.hosts.hosts_by_ip[host.ip] = host; + if (!(host.ip in this.hosts.hostsByIp)) { + this.hosts.hostsByIp[host.ip] = host; this.hosts.count++; } } setSeverity(severity) { - if (severity > this.severity) { - this._severity = severity; + if (!isDefined(this.severity) || this.severity < severity) { + this.severity = severity; } } - parseProperties(elem) { + static fromElement(element) { + const port = new ReportPort(); + + /* use writable=true to allow overriding severity */ + setProperties(this.parseElement(element), port, {writable: true}); + + return port; + } + + static parseElement(element = {}) { const copy = {}; - const {__text: name} = elem; + const {__text: name} = element; copy.id = name; - copy.threat = elem.threat; + copy.threat = element.threat; - if (name.includes('/')) { + if (isDefined(name) && name.includes('/')) { const [number, protocol] = name.split('/'); copy.number = parseInt(number); @@ -60,21 +72,10 @@ class ReportPort { copy.protocol = protocol; } - copy.hosts = { - hosts_by_ip: {}, - count: 0, - }; - - setProperties(copy, this); - - this._severity = parseSeverity(elem.severity); + copy.severity = parseSeverity(element.severity); return copy; } - - get severity() { - return this._severity; - } } export default ReportPort; diff --git a/gsa/src/gmp/models/report/report.js b/gsa/src/gmp/models/report/report.js index 4304693666..41d9b96a92 100644 --- a/gsa/src/gmp/models/report/report.js +++ b/gsa/src/gmp/models/report/report.js @@ -37,7 +37,6 @@ import { parsePorts, parseResults, parseTlsCertificates, - parseVulnerabilities, } from './parser'; class ReportReport extends Model { @@ -89,8 +88,6 @@ class ReportReport extends Model { copy.applications = parseApps(element, filter); - copy.vulnerabilities = parseVulnerabilities(element, filter); - copy.operatingsystems = parseOperatingSystems(element, filter); copy.ports = parsePorts(element, filter); diff --git a/gsa/src/gmp/models/report/task.js b/gsa/src/gmp/models/report/task.js index 70db51118b..d45041c3bd 100644 --- a/gsa/src/gmp/models/report/task.js +++ b/gsa/src/gmp/models/report/task.js @@ -30,10 +30,6 @@ import Model, {parseModelFromElement} from '../../model'; class ReportTask extends Model { static entityType = 'task'; - parseProperties(element) { - return ReportTask.parseElement(element); - } - static parseElement(element) { const copy = super.parseElement(element); diff --git a/gsa/src/gmp/models/report/tlscertificate.js b/gsa/src/gmp/models/report/tlscertificate.js index d4fccb7eef..a49d88bdc6 100644 --- a/gsa/src/gmp/models/report/tlscertificate.js +++ b/gsa/src/gmp/models/report/tlscertificate.js @@ -18,37 +18,54 @@ */ import 'core-js/features/object/entries'; -import {isDefined} from '../../utils/identity'; +import {isDefined} from 'gmp/utils/identity'; -import {parseInt} from '../../parser'; +import {setProperties, parseInt} from 'gmp/parser'; class TLSCertificate { - constructor(fingerprint) { - this.fingerprint = fingerprint; + constructor() { this.ports = []; } addPort(port) { - let c_port = parseInt(port); - if (!isDefined(c_port)) { + let parsedPort = parseInt(port); + if (!isDefined(parsedPort)) { // port wasn't a number - c_port = port; + parsedPort = port; } - this.ports.push(port); + this.ports.push(parsedPort); } copy() { - const cert = new TLSCertificate(this.fingerprint); + const cert = TLSCertificate.fromElement({fingerprint: this.fingerprint}); + for (const [key, value] of Object.entries(this)) { + if (key === 'fingerprint') { + continue; + } cert[key] = value; } + return cert; } get id() { return this.ip + ':' + this.port + ':' + this.fingerprint; } + + static fromElement(element) { + const cert = new TLSCertificate(); + + setProperties(this.parseElement(element), cert); + + return cert; + } + + static parseElement(element = {}) { + const {fingerprint} = element; + return {fingerprint}; + } } export default TLSCertificate; diff --git a/gsa/src/gmp/models/report/vulnerability.js b/gsa/src/gmp/models/report/vulnerability.js deleted file mode 100644 index 004355bcef..0000000000 --- a/gsa/src/gmp/models/report/vulnerability.js +++ /dev/null @@ -1,76 +0,0 @@ -/* Copyright (C) 2017-2019 Greenbone Networks GmbH - * - * SPDX-License-Identifier: GPL-2.0-or-later - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import {isDefined} from 'gmp/utils/identity'; - -import {parseQod} from 'gmp/parser'; - -import Vulnerability from 'gmp/models/vulnerability'; - -class RfpVulnerability extends Vulnerability { - addHost(host) { - if (!(host.ip in this.hosts.hosts_by_ip)) { - this.hosts.hosts_by_ip[host.ip] = host; - this.hosts.count++; - } - } - - addResult(result) { - this.results.count += 1; - } - - setSeverity(severity) { - if ( - isDefined(severity) && - (!isDefined(this.severity) || severity > this.severity) - ) { - this.severity = severity; - } - } - - parseProperties(element) { - return RfpVulnerability.parseElement(element); - } - - static parseElement(element) { - const copy = {}; - - const {nvt = {}, name, qod = {}} = element; - const {_oid: oid} = nvt; - - copy.id = oid; - copy.name = name; - copy.qod = parseQod(qod); - - copy.hosts = { - hosts_by_ip: {}, - count: 0, - }; - - copy.results = { - count: 1, - }; - - return copy; - } -} - -export default RfpVulnerability; - -// vim: set ts=2 sw=2 tw=80: diff --git a/gsa/src/gmp/parser.js b/gsa/src/gmp/parser.js index 799896860d..411fdf53c8 100644 --- a/gsa/src/gmp/parser.js +++ b/gsa/src/gmp/parser.js @@ -166,13 +166,17 @@ export const parseProperties = (element = {}, object = {}) => { return copy; }; -export const setProperties = (properties, object = {}) => { +export const setProperties = ( + properties, + object = {}, + {writable = false} = {}, +) => { if (isDefined(properties)) { for (const [key, value] of Object.entries(properties)) { if (!key.startsWith('_')) { Object.defineProperty(object, key, { value, - writable: false, + writable, enumerable: true, }); } diff --git a/gsa/src/web/pages/reports/cvestable.js b/gsa/src/web/pages/reports/cvestable.js index 40d597dff1..cb9ba2a3b5 100644 --- a/gsa/src/web/pages/reports/cvestable.js +++ b/gsa/src/web/pages/reports/cvestable.js @@ -20,6 +20,8 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; +import {shorten} from 'gmp/utils/string'; + import PropTypes from 'web/utils/proptypes'; import SeverityBar from 'web/components/bar/severitybar'; @@ -27,6 +29,7 @@ import SeverityBar from 'web/components/bar/severitybar'; import Divider from 'web/components/layout/divider'; import CveLink from 'web/components/link/cvelink'; +import DetailsLink from 'web/components/link/detailslink'; import TableData from 'web/components/table/data'; import TableHead from 'web/components/table/head'; @@ -42,30 +45,41 @@ const Header = ({currentSortDir, currentSortBy, sort = true, onSortChange}) => ( currentSortDir={currentSortDir} currentSortBy={currentSortBy} sortBy={sort ? 'cve' : false} - onSortChange={onSortChange} title={_('CVE')} + width="50%" + onSortChange={onSortChange} /> + @@ -79,7 +93,7 @@ Header.propTypes = { }; const Row = ({entity}) => { - const {cves, hosts, occurrences, severity} = entity; + const {cves, hosts, occurrences, severity, id, nvtName} = entity; return ( @@ -89,6 +103,11 @@ const Row = ({entity}) => { ))} + + + {shorten(nvtName, 80)} + + {hosts.count} {occurrences} diff --git a/gsa/src/web/pages/reports/sort.js b/gsa/src/web/pages/reports/sort.js index d474934545..1552c0a500 100644 --- a/gsa/src/web/pages/reports/sort.js +++ b/gsa/src/web/pages/reports/sort.js @@ -42,6 +42,7 @@ export const closedCvesSortFunctions = { export const cvesSortFunctions = { cve: makeCompareString(entity => entity.cves.join(' ')), hosts: makeCompareNumber(entity => entity.hosts.count), + nvt: makeCompareString(entity => entity.nvtName), occurrences: makeCompareNumber(entity => entity.occurrences), severity: makeCompareSeverity(), }; @@ -107,14 +108,4 @@ export const tlsCertificatesSortFunctions = { port: makeCompareString('port'), }; -export const vulnerabilitiesSortFunctions = { - name: makeCompareString('name'), - oldest: makeCompareDate(entity => entity.results.oldest), - newest: makeCompareDate(entity => entity.results.newest), - qod: makeCompareNumber(entity => entity.qod.value), - results: makeCompareNumber(entity => entity.results.count), - hosts: makeCompareNumber(entity => entity.hosts.count), - severity: makeCompareSeverity(), -}; - // vim: set ts=2 sw=2 tw=80: