Skip to content

Commit c3b1d54

Browse files
Added VEX upload support to UI
DependencyTrack/dependency-track#1387 Signed-off-by: Steve Springett <[email protected]>
1 parent 8e20713 commit c3b1d54

File tree

5 files changed

+117
-5
lines changed

5 files changed

+117
-5
lines changed

src/i18n/locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@
208208
"inventory": "Inventory",
209209
"inventory_with_vulnerabilities": "Inventory with Vulnerabilities",
210210
"vex_long_desc": "Vulnerability Exploitability Exchange (VEX)",
211+
"apply_vex": "Apply VEX",
212+
"export_vex": "Export VEX",
213+
"upload_vex": "Upload VEX",
214+
"vex_uploaded": "VEX uploaded",
211215
"reset": "Reset",
212216
"bom_uploaded": "BOM uploaded",
213217
"license_text": "License Text",

src/shared/api.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@
4949
"URL_POLICY_VIOLATION_ANALYSIS": "api/v1/violation/analysis",
5050
"URL_LICENSE_GROUP": "api/v1/licenseGroup",
5151
"URL_ACL_MAPPING": "api/v1/acl/mapping",
52-
"URL_ACL_TEAM": "api/v1/acl/team"
52+
"URL_ACL_TEAM": "api/v1/acl/team",
53+
"URL_VEX": "api/v1/vex"
5354
}

src/views/portfolio/projects/ProjectComponents.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
</template>
2424
<b-dropdown-item @click="downloadBom('inventory')" href="#">{{ $t('message.inventory') }}</b-dropdown-item>
2525
<b-dropdown-item @click="downloadBom('withVulnerabilities')" href="#">{{ $t('message.inventory_with_vulnerabilities') }}</b-dropdown-item>
26-
<b-dropdown-item @click="downloadBom('vex')" href="#">{{ $t('message.vex_long_desc') }}</b-dropdown-item>
2726
</b-dropdown>
2827
</div>
2928
</div>

src/views/portfolio/projects/ProjectFindings.vue

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@
55
dropdown for version is changes, the table will not update. For whatever reason, adding the toolbar fixes it.
66
-->
77
<div id="findingsToolbar" class="bs-table-custom-toolbar">
8+
<b-button size="md" variant="outline-primary"
9+
v-b-modal.projectUploadVexModal
10+
v-permission:or="[PERMISSIONS.VIEW_VULNERABILITY, PERMISSIONS.VULNERABILITY_ANALYSIS]">
11+
<span class="fa fa-upload"></span> {{ $t('message.apply_vex') }}
12+
</b-button>
13+
14+
<b-button size="md" variant="outline-primary"
15+
@click="downloadVex()"
16+
v-permission:or="[PERMISSIONS.VIEW_VULNERABILITY, PERMISSIONS.VULNERABILITY_ANALYSIS]">
17+
<span class="fa fa-download"></span> {{ $t('message.export_vex') }}
18+
</b-button>
19+
20+
<!-- Future use when CSAF support is added
21+
<b-dropdown variant="outline-primary" v-permission:or="[PERMISSIONS.VIEW_VULNERABILITY, PERMISSIONS.VULNERABILITY_ANALYSIS]">
22+
<template #button-content>
23+
<span class="fa fa-download"></span> {{ $t('message.export_vex') }}
24+
</template>
25+
<b-dropdown-item @click="downloadVex('cyclonedx')" href="#">CycloneDX</b-dropdown-item>
26+
<b-dropdown-item @click="downloadVex('csaf')" href="#">CSAF</b-dropdown-item>
27+
</b-dropdown>
28+
-->
829
<c-switch style="margin-left:1rem; margin-right:.5rem" id="showSuppressedFindings" color="primary" v-model="showSuppressedFindings" label v-bind="labelIcon" /><span class="text-muted">{{ $t('message.show_suppressed_findings') }}</span>
930
</div>
1031

@@ -15,6 +36,8 @@
1536
:options="options"
1637
@on-load-success="tableLoaded">
1738
</bootstrap-table>
39+
40+
<project-upload-vex-modal :uuid="this.uuid" />
1841
</div>
1942
</template>
2043

@@ -26,15 +49,20 @@
2649
import i18n from "../../../i18n";
2750
import permissionsMixin from "../../../mixins/permissionsMixin";
2851
import BootstrapToggle from 'vue-bootstrap-toggle';
52+
import ProjectUploadVexModal from "@/views/portfolio/projects/ProjectUploadVexModal";
2953
3054
export default {
3155
props: {
3256
uuid: String
3357
},
34-
mixins: [bootstrapTableMixin],
58+
mixins: [
59+
bootstrapTableMixin,
60+
permissionsMixin
61+
],
3562
components: {
3663
cSwitch,
37-
BootstrapToggle
64+
BootstrapToggle,
65+
ProjectUploadVexModal
3866
},
3967
data() {
4068
return {
@@ -156,6 +184,7 @@
156184
detailViewByClick: false,
157185
detailFormatter: (index, row) => {
158186
let projectUuid = this.uuid;
187+
console.log(row);
159188
return this.vueFormatter({
160189
i18n,
161190
template: `
@@ -225,7 +254,7 @@
225254
return {
226255
auditTrail: null,
227256
comment: null,
228-
isSuppressed: null,
257+
isSuppressed: !!(row && row.analysis && row.analysis.isSuppressed),
229258
finding: row,
230259
analysisChoices: [
231260
{ value: 'NOT_SET', text: this.$t('message.not_set') },
@@ -306,8 +335,10 @@
306335
}
307336
if (Object.prototype.hasOwnProperty.call(analysis, "isSuppressed")) {
308337
this.isSuppressed = analysis.isSuppressed;
338+
console.log("Setting isSuppressed to " + analysis.isSuppressed);
309339
} else {
310340
this.isSuppressed = false;
341+
console.log("Setting isSuppressed to false");
311342
}
312343
},
313344
makeAnalysis: function() {
@@ -365,6 +396,33 @@
365396
}
366397
return url;
367398
},
399+
downloadVex: function (data) {
400+
let url = `${this.$api.BASE_URL}/${this.$api.URL_VEX}/cyclonedx/project/${this.uuid}`;
401+
this.axios.request({
402+
responseType: 'blob',
403+
url: url,
404+
method: 'get',
405+
params: {
406+
download: 'true'
407+
}
408+
}).then((response) => {
409+
const url = window.URL.createObjectURL(new Blob([response.data]));
410+
const link = document.createElement('a');
411+
link.href = url;
412+
let filename = "vex.json";
413+
let disposition = response.headers["content-disposition"]
414+
if (disposition && disposition.indexOf('attachment') !== -1) {
415+
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
416+
let matches = filenameRegex.exec(disposition);
417+
if (matches != null && matches[1]) {
418+
filename = matches[1].replace(/['"]/g, '');
419+
}
420+
}
421+
link.setAttribute('download', filename);
422+
document.body.appendChild(link);
423+
link.click();
424+
});
425+
},
368426
refreshTable: function() {
369427
this.$refs.table.refresh({
370428
url: this.apiUrl(),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<template>
2+
<b-modal id="projectUploadVexModal" @hide="resetValues()" size="md" hide-header-close no-stacking :title="$t('message.upload_vex')">
3+
4+
<b-form-file v-model="file" class="mb-2"></b-form-file>
5+
6+
<template v-slot:modal-footer="{ cancel }">
7+
<b-button size="md" variant="secondary" @click="cancel()">{{ $t('message.cancel') }}</b-button>
8+
<b-button size="md" variant="secondary" @click="file = null">{{ $t('message.reset') }}</b-button>
9+
<b-button size="md" variant="primary" :disabled="file == null" @click="upload()">{{ $t('message.upload') }}</b-button>
10+
</template>
11+
</b-modal>
12+
</template>
13+
14+
<script>
15+
16+
export default {
17+
name: "ProjectUploadVexModal",
18+
props: {
19+
uuid: String
20+
},
21+
data() {
22+
return {
23+
file: null
24+
}
25+
},
26+
methods: {
27+
resetValues: function () {
28+
this.file = null;
29+
},
30+
upload: function () {
31+
let data = new FormData();
32+
data.set("project", this.uuid);
33+
data.set('vex', this.file);
34+
let config = {
35+
headers: {
36+
'Content-Type': 'multipart/form-data'
37+
}
38+
};
39+
let url = `${this.$api.BASE_URL}/${this.$api.URL_VEX}`;
40+
this.axios.post(url, data, config)
41+
.then((response) => {
42+
this.$root.$emit('bv::hide::modal', 'projectUploadVexModal');
43+
this.$toastr.s(this.$t('message.vex_uploaded'));
44+
}).catch((error) => {
45+
this.$toastr.w(this.$t('condition.unsuccessful_action'));
46+
});
47+
}
48+
}
49+
}
50+
</script>

0 commit comments

Comments
 (0)