From 74dbc9f002c41f543ae42f55a4e049a72c3f1cf8 Mon Sep 17 00:00:00 2001 From: mykter <git@mykter.com> Date: Tue, 26 Dec 2023 18:35:02 +0000 Subject: [PATCH 1/4] Refactor finding audit detail to SFC Signed-off-by: mykter <git@mykter.com> --- src/views/portfolio/projects/FindingAudit.vue | 246 ++++++++++++++++++ .../portfolio/projects/ProjectFindings.vue | 224 +--------------- vue.config.js | 1 + 3 files changed, 260 insertions(+), 211 deletions(-) create mode 100644 src/views/portfolio/projects/FindingAudit.vue diff --git a/src/views/portfolio/projects/FindingAudit.vue b/src/views/portfolio/projects/FindingAudit.vue new file mode 100644 index 000000000..b3038f944 --- /dev/null +++ b/src/views/portfolio/projects/FindingAudit.vue @@ -0,0 +1,246 @@ +<template> + <b-row class="expanded-row"> + <b-col sm="6"> + <div v-if="finding.vulnerability.aliases && finding.vulnerability.aliases.length > 0"> + <label>Aliases</label> + <b-card class="font-weight-bold"> + <b-card-text> + <span + v-for="alias in resolveVulnAliases(finding.vulnerability.aliases, finding.vulnerability.source)"> + <b-link style="margin-right:1.0rem" + :href="'/vulnerabilities/' + alias.source + '/' + alias.vulnId">{{ alias.vulnId }}</b-link> + </span> + </b-card-text> + </b-card> + </div> + <b-form-group v-if="finding.vulnerability.title" id="fieldset-1" :label="this.$t('message.title')" + label-for="input-1"> + <b-form-input id="input-1" v-model="finding.vulnerability.title" class="form-control disabled" readonly + trim /> + </b-form-group> + <b-form-group v-if="finding.vulnerability.subtitle" id="fieldset-2" :label="this.$t('message.subtitle')" + label-for="input-2"> + <b-form-input id="input-2" v-model="finding.vulnerability.subtitle" class="form-control disabled" readonly + trim /> + </b-form-group> + <b-form-group v-if="finding.vulnerability.description" id="fieldset-3" :label="this.$t('message.description')" + label-for="input-3"> + <b-form-textarea id="input-3" v-model="finding.vulnerability.description" rows="7" + class="form-control disabled" readonly trim /> + </b-form-group> + <b-form-group v-if="finding.vulnerability.recommendation" id="fieldset-4" + :label="this.$t('message.recommendation')" label-for="input-4"> + <b-form-textarea id="input-4" v-model="finding.vulnerability.recommendation" rows="7" + class="form-control disabled" readonly trim /> + </b-form-group> + <b-form-group v-if="finding.vulnerability.cvssV2Vector" id="fieldset-5" + :label="this.$t('message.cvss_v2_vector')" label-for="input-5"> + <b-form-input id="input-5" v-model="finding.vulnerability.cvssV2Vector" class="form-control disabled" + readonly trim /> + </b-form-group> + <b-form-group v-if="finding.vulnerability.cvssV3Vector" id="fieldset-6" + :label="this.$t('message.cvss_v3_vector')" label-for="input-6"> + <b-form-input id="input-6" v-model="finding.vulnerability.cvssV3Vector" class="form-control disabled" + readonly trim /> + </b-form-group> + </b-col> + <b-col sm="6"> + <b-form-group id="fieldset-7" :label="this.$t('message.audit_trail')" label-for="auditTrailField"> + <b-form-textarea id="auditTrailField" v-model="auditTrail" rows="7" class="form-control disabled" readonly + trim /> + </b-form-group> + <b-form-group id="fieldset-8" v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" + :label="this.$t('message.comment')" label-for="input-8"> + <b-form-textarea id="input-8" v-model="comment" rows="4" class="form-control" trim /> + <div class="pull-right"> + <b-button size="sm" variant="outline-primary" @click="addComment"><span class="fa fa-comment-o"></span> + {{ this.$t('message.add_comment') }}</b-button> + </div> + </b-form-group> + <b-form-group id="fieldset-9" v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" + :label="this.$t('message.analysis')" label-for="input-9"> + <b-input-group id="input-9"> + <b-form-select v-model="analysisState" :options="analysisChoices" @change="makeAnalysis" + style="flex:0 1 auto; width:auto; margin-right:2rem;" v-b-tooltip.hover + :title="this.$t('message.analysis_tooltip')" /> + <bootstrap-toggle v-model="isSuppressed" + :options="{ on: this.$t('message.suppressed'), off: this.$t('message.suppress'), onstyle: 'warning', offstyle: 'outline-disabled' }" + :disabled="analysisState === null" /> + </b-input-group> + </b-form-group> + <b-row v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)"> + <b-col sm="6"> + <b-form-group id="fieldset-10" :label="this.$t('message.justification')" label-for="input-10"> + <b-input-group id="input-10"> + <b-form-select v-model="analysisJustification" :options="justificationChoices" + @change="makeAnalysis" + :disabled="analysisState === null || analysisState !== 'NOT_AFFECTED'" v-b-tooltip.hover + :title="$t('message.justification_tooltip')" /> + </b-input-group> + </b-form-group> + </b-col> + <b-col sm="6"> + <b-form-group id="fieldset-11" :label="this.$t('message.response')" label-for="input-11"> + <b-input-group id="input-11"> + <b-form-select v-model="analysisResponse" :options="responseChoices" + :disabled="analysisState === null" @change="makeAnalysis" v-b-tooltip.hover + :title="this.$t('message.response_tooltip')" /> + </b-input-group> + </b-form-group> + </b-col> + </b-row> + <b-form-group id="fieldset-12" v-if="this.isPermitted(this.PERMISSIONS.VIEW_VULNERABILITY)" + :label="this.$t('message.details')" label-for="analysisDetailsField"> + <b-form-textarea id="analysisDetailsField" v-model="analysisDetails" rows="7" class="form-control" + :disabled="analysisState === null || !this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" + v-b-tooltip.hover :title="this.$t('message.analysis_details_tooltip')" /> + <div class="pull-right"> + <b-button v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" + :disabled="analysisState === null" size="sm" variant="outline-primary" @click="makeAnalysis"><span + class="fa fa-comment-o"></span> {{ this.$t('message.update_details') }}</b-button> + </div> + </b-form-group> + </b-col> + </b-row> +</template> + +<script> +import common from "@/shared/common"; +import BootstrapToggle from 'vue-bootstrap-toggle'; +import permissionsMixin from "@/mixins/permissionsMixin"; + +export default { + props: { + finding: Object, + projectUuid: String + }, + data() { + return { + auditTrail: null, + comment: null, + isSuppressed: !!(this.finding?.analysis?.isSuppressed), + analysisChoices: [ + { value: 'NOT_SET', text: this.$t('message.not_set') }, + { value: 'EXPLOITABLE', text: this.$t('message.exploitable') }, + { value: 'IN_TRIAGE', text: this.$t('message.in_triage') }, + { value: 'RESOLVED', text: this.$t('message.resolved') }, + { value: 'FALSE_POSITIVE', text: this.$t('message.false_positive') }, + { value: 'NOT_AFFECTED', text: this.$t('message.not_affected') }, + ], + justificationChoices: [ + { value: 'NOT_SET', text: this.$t('message.not_set') }, + { value: 'CODE_NOT_PRESENT', text: this.$t('message.code_not_present') }, + { value: 'CODE_NOT_REACHABLE', text: this.$t('message.code_not_reachable') }, + { value: 'REQUIRES_CONFIGURATION', text: this.$t('message.requires_configuration') }, + { value: 'REQUIRES_DEPENDENCY', text: this.$t('message.requires_dependency') }, + { value: 'REQUIRES_ENVIRONMENT', text: this.$t('message.requires_environment') }, + { value: 'PROTECTED_BY_COMPILER', text: this.$t('message.protected_by_compiler') }, + { value: 'PROTECTED_AT_RUNTIME', text: this.$t('message.protected_at_runtime') }, + { value: 'PROTECTED_AT_PERIMETER', text: this.$t('message.protected_at_perimeter') }, + { value: 'PROTECTED_BY_MITIGATING_CONTROL', text: this.$t('message.protected_by_mitigating_control') } + ], + responseChoices: [ + { value: 'NOT_SET', text: this.$t('message.not_set') }, + { value: 'CAN_NOT_FIX', text: this.$t('message.can_not_fix') }, + { value: 'WILL_NOT_FIX', text: this.$t('message.will_not_fix') }, + { value: 'UPDATE', text: this.$t('message.update') }, + { value: 'ROLLBACK', text: this.$t('message.rollback') }, + { value: 'WORKAROUND_AVAILABLE', text: this.$t('message.workaround_available') } + ], + analysisState: null, + analysisJustification: null, + analysisResponse: null, + analysisDetails: null, + } + }, + watch: { + isSuppressed: function (currentValue, oldValue) { + if (oldValue != null) { + this.callRestEndpoint(this.analysisState, this.analysisJustification, this.analysisResponse, null, null, currentValue); + } + } + }, + mixins: [permissionsMixin], + methods: { + resolveVulnAliases: function (aliases, vulnSource) { + return common.resolveVulnAliases(vulnSource ? vulnSource : this.source, aliases); + }, + getAnalysis: function () { + let queryString = "?project=" + this.projectUuid + "&component=" + this.finding.component.uuid + "&vulnerability=" + this.finding.vulnerability.uuid; + let url = `${this.$api.BASE_URL}/${this.$api.URL_ANALYSIS}` + queryString; + this.axios.get(url, { + validateStatus: (status) => status === 200 || status === 404 + }).then((response) => { + this.updateAnalysisData(response.data); + }); + }, + updateAnalysisData: function (analysis) { + if (Object.prototype.hasOwnProperty.call(analysis, "analysisComments")) { + let trail = ""; + for (let i = 0; i < analysis.analysisComments.length; i++) { + if (Object.prototype.hasOwnProperty.call(analysis.analysisComments[i], "commenter")) { + trail += analysis.analysisComments[i].commenter + " - "; + } + trail += common.formatTimestamp(analysis.analysisComments[i].timestamp, true); + trail += "\n"; + trail += analysis.analysisComments[i].comment; + trail += "\n\n"; + } + this.auditTrail = trail; + } + if (Object.prototype.hasOwnProperty.call(analysis, "analysisState")) { + this.analysisState = analysis.analysisState; + } + if (Object.prototype.hasOwnProperty.call(analysis, "analysisJustification")) { + this.analysisJustification = analysis.analysisJustification; + } + if (Object.prototype.hasOwnProperty.call(analysis, "analysisResponse")) { + this.analysisResponse = analysis.analysisResponse; + } + if (Object.prototype.hasOwnProperty.call(analysis, "analysisDetails")) { + this.analysisDetails = analysis.analysisDetails; + } + if (Object.prototype.hasOwnProperty.call(analysis, "isSuppressed")) { + this.isSuppressed = analysis.isSuppressed; + } else { + this.isSuppressed = false; + } + }, + makeAnalysis: function () { + this.callRestEndpoint(this.analysisState, this.analysisJustification, this.analysisResponse, this.analysisDetails, null, null); + }, + addComment: function () { + if (this.comment != null) { + this.callRestEndpoint(this.analysisState, this.analysisJustification, this.analysisResponse, this.analysisDetails, this.comment, null); + } + this.comment = null; + }, + callRestEndpoint: function (analysisState, analysisJustification, analysisResponse, analysisDetails, comment, isSuppressed) { + let url = `${this.$api.BASE_URL}/${this.$api.URL_ANALYSIS}`; + this.axios.put(url, { + project: this.projectUuid, + component: this.finding.component.uuid, + vulnerability: this.finding.vulnerability.uuid, + analysisState: analysisState, + analysisJustification: analysisJustification, + analysisResponse: analysisResponse, + analysisDetails: analysisDetails, + comment: comment, + isSuppressed: isSuppressed + }).then((response) => { + this.$toastr.s(this.$t('message.updated')); + this.updateAnalysisData(response.data); + }).catch((error) => { + this.$toastr.w(this.$t('condition.unsuccessful_action')); + }); + } + }, + beforeMount() { + this.finding && this.getAnalysis(); + }, + components: { + BootstrapToggle + } + +} +</script> \ No newline at end of file diff --git a/src/views/portfolio/projects/ProjectFindings.vue b/src/views/portfolio/projects/ProjectFindings.vue index 6f342ea17..de0cbe9fe 100644 --- a/src/views/portfolio/projects/ProjectFindings.vue +++ b/src/views/portfolio/projects/ProjectFindings.vue @@ -59,16 +59,17 @@ </template> <script> -import { compareVersions, loadUserPreferencesForBootstrapTable } from "@/shared/utils"; -import ProjectUploadVexModal from "@/views/portfolio/projects/ProjectUploadVexModal"; import { Switch as cSwitch } from '@coreui/vue'; import $ from "jquery"; -import BootstrapToggle from 'vue-bootstrap-toggle'; import xssFilters from "xss-filters"; -import i18n from "../../../i18n"; -import bootstrapTableMixin from "../../../mixins/bootstrapTableMixin"; -import permissionsMixin from "../../../mixins/permissionsMixin"; -import common from "../../../shared/common"; + +import common from "@/shared/common"; +import i18n from "@/i18n"; +import { compareVersions, loadUserPreferencesForBootstrapTable } from "@/shared/utils"; +import bootstrapTableMixin from "@/mixins/bootstrapTableMixin"; +import permissionsMixin from "@/mixins/permissionsMixin"; +import FindingAudit from "./FindingAudit"; +import ProjectUploadVexModal from "./ProjectUploadVexModal"; export default { props: { @@ -80,7 +81,6 @@ import common from "../../../shared/common"; ], components: { cSwitch, - BootstrapToggle, ProjectUploadVexModal }, beforeCreate() { @@ -249,211 +249,13 @@ import common from "../../../shared/common"; detailViewIcon: true, detailViewByClick: false, detailFormatter: (index, row) => { - let projectUuid = this.uuid; - return this.vueFormatter({ + return row && this.vueFormatter({ i18n, - template: ` - <b-row class="expanded-row"> - <b-col sm="6"> - <div v-if="finding.vulnerability.aliases && finding.vulnerability.aliases.length > 0"> - <label>Aliases</label> - <b-card class="font-weight-bold"> - <b-card-text> - <span v-for="alias in resolveVulnAliases(finding.vulnerability.aliases, finding.vulnerability.source)"> - <b-link style="margin-right:1.0rem" :href="'/vulnerabilities/' + alias.source + '/' + alias.vulnId">{{ alias.vulnId }}</b-link> - </span> - </b-card-text> - </b-card> - </div> - <b-form-group v-if="finding.vulnerability.title" id="fieldset-1" :label="this.$t('message.title')" label-for="input-1"> - <b-form-input id="input-1" v-model="finding.vulnerability.title" class="form-control disabled" readonly trim /> - </b-form-group> - <b-form-group v-if="finding.vulnerability.subtitle" id="fieldset-2" :label="this.$t('message.subtitle')" label-for="input-2"> - <b-form-input id="input-2" v-model="finding.vulnerability.subtitle" class="form-control disabled" readonly trim /> - </b-form-group> - <b-form-group v-if="finding.vulnerability.description" id="fieldset-3" :label="this.$t('message.description')" label-for="input-3"> - <b-form-textarea id="input-3" v-model="finding.vulnerability.description" rows="7" class="form-control disabled" readonly trim /> - </b-form-group> - <b-form-group v-if="finding.vulnerability.recommendation" id="fieldset-4" :label="this.$t('message.recommendation')" label-for="input-4"> - <b-form-textarea id="input-4" v-model="finding.vulnerability.recommendation" rows="7" class="form-control disabled" readonly trim /> - </b-form-group> - <b-form-group v-if="finding.vulnerability.cvssV2Vector" id="fieldset-5" :label="this.$t('message.cvss_v2_vector')" label-for="input-5"> - <b-form-input id="input-5" v-model="finding.vulnerability.cvssV2Vector" class="form-control disabled" readonly trim /> - </b-form-group> - <b-form-group v-if="finding.vulnerability.cvssV3Vector" id="fieldset-6" :label="this.$t('message.cvss_v3_vector')" label-for="input-6"> - <b-form-input id="input-6" v-model="finding.vulnerability.cvssV3Vector" class="form-control disabled" readonly trim /> - </b-form-group> - </b-col> - <b-col sm="6"> - <b-form-group id="fieldset-7" :label="this.$t('message.audit_trail')" label-for="auditTrailField"> - <b-form-textarea id="auditTrailField" v-model="auditTrail" rows="7" class="form-control disabled" readonly trim /> - </b-form-group> - <b-form-group id="fieldset-8" v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" :label="this.$t('message.comment')" label-for="input-8"> - <b-form-textarea id="input-8" v-model="comment" rows="4" class="form-control" trim /> - <div class="pull-right"> - <b-button size="sm" variant="outline-primary" @click="addComment"><span class="fa fa-comment-o"></span> {{ this.$t('message.add_comment') }}</b-button> - </div> - </b-form-group> - <b-form-group id="fieldset-9" v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" :label="this.$t('message.analysis')" label-for="input-9"> - <b-input-group id="input-9"> - <b-form-select v-model="analysisState" :options="analysisChoices" @change="makeAnalysis" style="flex:0 1 auto; width:auto; margin-right:2rem;" v-b-tooltip.hover :title="this.$t('message.analysis_tooltip')"/> - <bootstrap-toggle v-model="isSuppressed" :options="{ on: this.$t('message.suppressed'), off: this.$t('message.suppress'), onstyle: 'warning', offstyle: 'outline-disabled'}" :disabled="analysisState === null" /> - </b-input-group> - </b-form-group> - <b-row v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)"> - <b-col sm="6"> - <b-form-group id="fieldset-10" :label="this.$t('message.justification')" label-for="input-10"> - <b-input-group id="input-10"> - <b-form-select v-model="analysisJustification" :options="justificationChoices" @change="makeAnalysis" :disabled="analysisState === null || analysisState !== 'NOT_AFFECTED'" v-b-tooltip.hover :title="$t('message.justification_tooltip')" /> - </b-input-group> - </b-form-group> - </b-col> - <b-col sm="6"> - <b-form-group id="fieldset-11" :label="this.$t('message.response')" label-for="input-11"> - <b-input-group id="input-11"> - <b-form-select v-model="analysisResponse" :options="responseChoices" :disabled="analysisState === null" @change="makeAnalysis" v-b-tooltip.hover :title="this.$t('message.response_tooltip')" /> - </b-input-group> - </b-form-group> - </b-col> - </b-row> - <b-form-group id="fieldset-12" v-if="this.isPermitted(this.PERMISSIONS.VIEW_VULNERABILITY)" :label="this.$t('message.details')" label-for="analysisDetailsField"> - <b-form-textarea id="analysisDetailsField" v-model="analysisDetails" rows="7" class="form-control" :disabled="analysisState === null || !this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" v-b-tooltip.hover :title="this.$t('message.analysis_details_tooltip')" /> - <div class="pull-right"> - <b-button v-if="this.isPermitted(this.PERMISSIONS.VULNERABILITY_ANALYSIS)" :disabled="analysisState === null" size="sm" variant="outline-primary" @click="makeAnalysis"><span class="fa fa-comment-o"></span> {{ this.$t('message.update_details') }}</b-button> - </div> - </b-form-group> - </b-col> - </b-row> - `, - data() { - return { - auditTrail: null, - comment: null, - isSuppressed: !!(row && row.analysis && row.analysis.isSuppressed), - finding: row, - analysisChoices: [ - { value: 'NOT_SET', text: this.$t('message.not_set') }, - { value: 'EXPLOITABLE', text: this.$t('message.exploitable') }, - { value: 'IN_TRIAGE', text: this.$t('message.in_triage') }, - { value: 'RESOLVED', text: this.$t('message.resolved') }, - { value: 'FALSE_POSITIVE', text: this.$t('message.false_positive') }, - { value: 'NOT_AFFECTED', text: this.$t('message.not_affected') }, - ], - justificationChoices: [ - { value: 'NOT_SET', text: this.$t('message.not_set') }, - { value: 'CODE_NOT_PRESENT', text: this.$t('message.code_not_present') }, - { value: 'CODE_NOT_REACHABLE', text: this.$t('message.code_not_reachable') }, - { value: 'REQUIRES_CONFIGURATION', text: this.$t('message.requires_configuration') }, - { value: 'REQUIRES_DEPENDENCY', text: this.$t('message.requires_dependency') }, - { value: 'REQUIRES_ENVIRONMENT', text: this.$t('message.requires_environment') }, - { value: 'PROTECTED_BY_COMPILER', text: this.$t('message.protected_by_compiler') }, - { value: 'PROTECTED_AT_RUNTIME', text: this.$t('message.protected_at_runtime') }, - { value: 'PROTECTED_AT_PERIMETER', text: this.$t('message.protected_at_perimeter') }, - { value: 'PROTECTED_BY_MITIGATING_CONTROL', text: this.$t('message.protected_by_mitigating_control') } - ], - responseChoices: [ - { value: 'NOT_SET', text: this.$t('message.not_set') }, - { value: 'CAN_NOT_FIX', text: this.$t('message.can_not_fix') }, - { value: 'WILL_NOT_FIX', text: this.$t('message.will_not_fix') }, - { value: 'UPDATE', text: this.$t('message.update') }, - { value: 'ROLLBACK', text: this.$t('message.rollback') }, - { value: 'WORKAROUND_AVAILABLE', text: this.$t('message.workaround_available') } - ], - analysisState: null, - analysisJustification: null, - analysisResponse: null, - analysisDetails: null, - projectUuid: projectUuid - } - }, - watch: { - isSuppressed: function (currentValue, oldValue) { - if (oldValue != null) { - this.callRestEndpoint(this.analysisState, this.analysisJustification, this.analysisResponse, null, null, currentValue); - } - } - }, - mixins: [permissionsMixin], - methods: { - resolveVulnAliases: function(aliases, vulnSource) { - return common.resolveVulnAliases(vulnSource ? vulnSource : this.source, aliases); - }, - getAnalysis: function() { - let queryString = "?project=" + projectUuid + "&component=" + this.finding.component.uuid + "&vulnerability=" + this.finding.vulnerability.uuid; - let url = `${this.$api.BASE_URL}/${this.$api.URL_ANALYSIS}` + queryString; - this.axios.get(url, { - validateStatus: (status) => status === 200 || status === 404 - }).then((response) => { - this.updateAnalysisData(response.data); - }); - }, - updateAnalysisData: function(analysis) { - if (Object.prototype.hasOwnProperty.call(analysis, "analysisComments")) { - let trail = ""; - for (let i = 0; i < analysis.analysisComments.length; i++) { - if (Object.prototype.hasOwnProperty.call(analysis.analysisComments[i], "commenter")) { - trail += analysis.analysisComments[i].commenter + " - "; - } - trail += common.formatTimestamp(analysis.analysisComments[i].timestamp, true); - trail += "\n"; - trail += analysis.analysisComments[i].comment; - trail += "\n\n"; - } - this.auditTrail = trail; - } - if (Object.prototype.hasOwnProperty.call(analysis, "analysisState")) { - this.analysisState = analysis.analysisState; - } - if (Object.prototype.hasOwnProperty.call(analysis, "analysisJustification")) { - this.analysisJustification = analysis.analysisJustification; - } - if (Object.prototype.hasOwnProperty.call(analysis, "analysisResponse")) { - this.analysisResponse = analysis.analysisResponse; - } - if (Object.prototype.hasOwnProperty.call(analysis, "analysisDetails")) { - this.analysisDetails = analysis.analysisDetails; - } - if (Object.prototype.hasOwnProperty.call(analysis, "isSuppressed")) { - this.isSuppressed = analysis.isSuppressed; - } else { - this.isSuppressed = false; - } - }, - makeAnalysis: function() { - this.callRestEndpoint(this.analysisState, this.analysisJustification, this.analysisResponse, this.analysisDetails, null, null); - }, - addComment: function() { - if (this.comment != null) { - this.callRestEndpoint(this.analysisState, this.analysisJustification, this.analysisResponse, this.analysisDetails, this.comment, null); - } - this.comment = null; - }, - callRestEndpoint: function(analysisState, analysisJustification, analysisResponse, analysisDetails, comment, isSuppressed) { - let url = `${this.$api.BASE_URL}/${this.$api.URL_ANALYSIS}`; - this.axios.put(url, { - project: projectUuid, - component: this.finding.component.uuid, - vulnerability: this.finding.vulnerability.uuid, - analysisState: analysisState, - analysisJustification: analysisJustification, - analysisResponse: analysisResponse, - analysisDetails: analysisDetails, - comment: comment, - isSuppressed: isSuppressed - }).then((response) => { - this.$toastr.s(this.$t('message.updated')); - this.updateAnalysisData(response.data); - }).catch((error) => { - this.$toastr.w(this.$t('condition.unsuccessful_action')); - }); - } + propsData: { + finding: row, + projectUuid: this.uuid }, - beforeMount() { - this.getAnalysis(); - }, - components: { - BootstrapToggle - } + ...FindingAudit }) }, onExpandRow: this.vueFormatterInit, diff --git a/vue.config.js b/vue.config.js index 9cf4b618f..d95b9a695 100644 --- a/vue.config.js +++ b/vue.config.js @@ -11,6 +11,7 @@ module.exports = { proxy: { "/api": { target: process.env.VUE_APP_SERVER_URL} } }, configureWebpack: { + devtool: 'source-map', plugins: [ new CopyPlugin([ { from: "node_modules/axios/dist/axios.min.js", to: "static/js", force: true }, From b10be4c10449d9c0b8b309032ee7699ff2bccd7c Mon Sep 17 00:00:00 2001 From: mykter <git@mykter.com> Date: Thu, 28 Dec 2023 19:39:57 +0000 Subject: [PATCH 2/4] Fix static analysis issues in formerly inlined code Signed-off-by: mykter <git@mykter.com> --- src/views/portfolio/projects/FindingAudit.vue | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/views/portfolio/projects/FindingAudit.vue b/src/views/portfolio/projects/FindingAudit.vue index b3038f944..b271d8d4d 100644 --- a/src/views/portfolio/projects/FindingAudit.vue +++ b/src/views/portfolio/projects/FindingAudit.vue @@ -6,7 +6,8 @@ <b-card class="font-weight-bold"> <b-card-text> <span - v-for="alias in resolveVulnAliases(finding.vulnerability.aliases, finding.vulnerability.source)"> + v-for="alias in resolveVulnAliases(finding.vulnerability.aliases, finding.vulnerability.source)" + :key="alias.vulnId"> <b-link style="margin-right:1.0rem" :href="'/vulnerabilities/' + alias.source + '/' + alias.vulnId">{{ alias.vulnId }}</b-link> </span> @@ -15,32 +16,32 @@ </div> <b-form-group v-if="finding.vulnerability.title" id="fieldset-1" :label="this.$t('message.title')" label-for="input-1"> - <b-form-input id="input-1" v-model="finding.vulnerability.title" class="form-control disabled" readonly + <b-form-input id="input-1" :value="finding.vulnerability.title" class="form-control disabled" readonly trim /> </b-form-group> <b-form-group v-if="finding.vulnerability.subtitle" id="fieldset-2" :label="this.$t('message.subtitle')" label-for="input-2"> - <b-form-input id="input-2" v-model="finding.vulnerability.subtitle" class="form-control disabled" readonly + <b-form-input id="input-2" :value="finding.vulnerability.subtitle" class="form-control disabled" readonly trim /> </b-form-group> <b-form-group v-if="finding.vulnerability.description" id="fieldset-3" :label="this.$t('message.description')" label-for="input-3"> - <b-form-textarea id="input-3" v-model="finding.vulnerability.description" rows="7" + <b-form-textarea id="input-3" :value="finding.vulnerability.description" rows="7" class="form-control disabled" readonly trim /> </b-form-group> <b-form-group v-if="finding.vulnerability.recommendation" id="fieldset-4" :label="this.$t('message.recommendation')" label-for="input-4"> - <b-form-textarea id="input-4" v-model="finding.vulnerability.recommendation" rows="7" + <b-form-textarea id="input-4" :value="finding.vulnerability.recommendation" rows="7" class="form-control disabled" readonly trim /> </b-form-group> <b-form-group v-if="finding.vulnerability.cvssV2Vector" id="fieldset-5" :label="this.$t('message.cvss_v2_vector')" label-for="input-5"> - <b-form-input id="input-5" v-model="finding.vulnerability.cvssV2Vector" class="form-control disabled" + <b-form-input id="input-5" :value="finding.vulnerability.cvssV2Vector" class="form-control disabled" readonly trim /> </b-form-group> <b-form-group v-if="finding.vulnerability.cvssV3Vector" id="fieldset-6" :label="this.$t('message.cvss_v3_vector')" label-for="input-6"> - <b-form-input id="input-6" v-model="finding.vulnerability.cvssV3Vector" class="form-control disabled" + <b-form-input id="input-6" :value="finding.vulnerability.cvssV3Vector" class="form-control disabled" readonly trim /> </b-form-group> </b-col> @@ -230,7 +231,7 @@ export default { }).then((response) => { this.$toastr.s(this.$t('message.updated')); this.updateAnalysisData(response.data); - }).catch((error) => { + }).catch(() => { this.$toastr.w(this.$t('condition.unsuccessful_action')); }); } From 3895307e761d56e259b340edfc6e9a320db98e41 Mon Sep 17 00:00:00 2001 From: mykter <git@mykter.com> Date: Tue, 26 Dec 2023 22:15:43 +0000 Subject: [PATCH 3/4] Fix project finding route with components and/or vulnerabilities Signed-off-by: mykter <git@mykter.com> --- .../portfolio/projects/ProjectFindings.vue | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/views/portfolio/projects/ProjectFindings.vue b/src/views/portfolio/projects/ProjectFindings.vue index de0cbe9fe..3b0165d99 100644 --- a/src/views/portfolio/projects/ProjectFindings.vue +++ b/src/views/portfolio/projects/ProjectFindings.vue @@ -85,6 +85,15 @@ import ProjectUploadVexModal from "./ProjectUploadVexModal"; }, beforeCreate() { this.showSuppressedFindings = (localStorage && localStorage.getItem("ProjectFindingsShowSuppressedFindings") !== null) ? (localStorage.getItem("ProjectFindingsShowSuppressedFindings") === "true") : false; + + if (this.$route.params.vulnerability) { + if (this.$route.params.affectedComponent) { + // search for the last portion of the finding's matrix ID + this.initialSearchText = this.$route.params.affectedComponent + ":" + this.$route.params.vulnerability + } else { + this.initialSearchText = this.$route.params.vulnerability + } // the route doesn't allow a component to be specified without a vulnerability + } }, data() { return { @@ -239,7 +248,7 @@ import ProjectUploadVexModal from "./ProjectUploadVexModal"; pageSize: (localStorage && localStorage.getItem("ProjectFindingsPageSize") !== null) ? Number(localStorage.getItem("ProjectFindingsPageSize")) : 10, sortName: (localStorage && localStorage.getItem("ProjectFindingsSortName") !== null) ? localStorage.getItem("ProjectFindingsSortName") : undefined, sortOrder: (localStorage && localStorage.getItem("ProjectFindingsSortOrder") !== null) ? localStorage.getItem("ProjectFindingsSortOrder") : undefined, - searchText: this.$route.params.vulnerability ? ":" + this.$route.params.vulnerability : undefined, + searchText: this.initialSearchText, icons: { detailOpen: 'fa-fw fa-angle-right', detailClose: 'fa-fw fa-angle-down', @@ -366,8 +375,10 @@ import ProjectUploadVexModal from "./ProjectUploadVexModal"; }, tableLoaded: function(data) { loadUserPreferencesForBootstrapTable(this, "ProjectFindings", this.$refs.table.columns); - this.$emit('total', data.total); - if (this.$route.params.vulnerability) { + this.$emit('total', data.total); // the unfiltered length + if (this.$route.params.vulnerability && this.$refs.table.getData().length === 1) { + // If there's only one visible row due to a URL that selects a single finding, show it in full + // Don't expand if there are multiple findings, as it makes it harder to notice that there is more than one result this.$refs.table.expandRow(0); } }, From cec631bb099cfb885e2c5c03e8b7c2863eab36e7 Mon Sep 17 00:00:00 2001 From: mykter <git@mykter.com> Date: Thu, 28 Dec 2023 19:54:46 +0000 Subject: [PATCH 4/4] De-duplicate route names Signed-off-by: mykter <git@mykter.com> --- src/router/index.js | 2 +- src/views/portfolio/vulnerabilities/AffectedProjects.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/router/index.js b/src/router/index.js index 8f8c76c15..dfae068f9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -132,7 +132,7 @@ function configRoutes() { }, { path: 'projects/:uuid/findings/:vulnerability', - name: 'Project Finding Lookup', + name: 'Project Vulnerability Lookup', props: (route) => ( { uuid: route.params.uuid, vulnerability: route.params.vulnerability diff --git a/src/views/portfolio/vulnerabilities/AffectedProjects.vue b/src/views/portfolio/vulnerabilities/AffectedProjects.vue index 9e36900ce..f3f3e9a9e 100644 --- a/src/views/portfolio/vulnerabilities/AffectedProjects.vue +++ b/src/views/portfolio/vulnerabilities/AffectedProjects.vue @@ -27,7 +27,7 @@ field: "name", sortable: true, formatter: (value, row) => { - const url = this.$router.resolve({name: 'Project Finding Lookup', + const url = this.$router.resolve({name: 'Project Vulnerability Lookup', params: {'uuid': row.uuid, vulnerability:this.vulnerability}}).href; let html = `<a href="${url}">${xssFilters.inHTMLData(value)}</a>`;