Skip to content

Commit

Permalink
Breaking: Simplify report generator and customization (#125)
Browse files Browse the repository at this point in the history
* first draft to simplify report generation
  • Loading branch information
blockisec committed Jan 10, 2024
1 parent 47ffb23 commit 7c58c13
Show file tree
Hide file tree
Showing 112 changed files with 2,610 additions and 1,362 deletions.
38 changes: 16 additions & 22 deletions frontend/src/components/dialogs/ReportTemplateCreateDialog.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
<script>
import AdminService from '@/service/AdminService'
import AdminService from '@/service/AdminService';
export default {
name: "ReportTemplateCreateDialog",
emits: ["object-created"],
name: 'ReportTemplateCreateDialog',
emits: ['object-created'],
data() {
return {
visible: false,
model: {
name: null,
path: null,
status: null
},
statusChoices: [
{ title: "Active", value: "Active" },
{ title: "Draft", value: "Draft" },
{ title: "Deactivated", value: "Deactivated" }
{ title: 'Active', value: 'Active' },
{ title: 'Draft', value: 'Draft' },
{ title: 'Deactivated', value: 'Deactivated' }
],
service: new AdminService(),
service: new AdminService()
};
},
methods: {
Expand All @@ -33,20 +31,20 @@ export default {
name: this.model.name,
status: this.model.status,
path: this.model.path
}
};
this.service.createReportTemplate(this.$api, data).then((response) => {
this.$toast.add({
severity: "success",
summary: "Report template Created!",
severity: 'success',
summary: 'Report template Created!',
life: 3000,
detail: "Report template created successfully!"
detail: 'Report template created successfully!'
});
this.$emit("object-created", response.data);
this.$emit('object-created', response.data);
this.visible = false;
});
},
},
}
}
}
};
</script>

<template>
Expand All @@ -57,10 +55,6 @@ export default {
<label for="name">Name</label>
<InputText id="name" v-model="model.name"></InputText>
</div>
<div class="flex flex-column gap-2">
<label for="first_name">Path</label>
<InputText id="path" v-model="model.path"></InputText>
</div>
<div class="flex flex-column gap-2">
<label for="status">Status</label>
<Dropdown v-model="model.status" optionLabel="title" :options="statusChoices" optionValue="value"></Dropdown>
Expand All @@ -71,4 +65,4 @@ export default {
<Button label="Save" @click="create" icon="pi pi-check" class="p-button-outlined"></Button>
</template>
</Dialog>
</template>
</template>
29 changes: 11 additions & 18 deletions frontend/src/components/dialogs/ReportTemplateUpdateDialog.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
<script>
import AdminService from '@/service/AdminService'
import AdminService from '@/service/AdminService';
export default {
name: "ReportTemplateUpdateDialog",
name: 'ReportTemplateUpdateDialog',
props: {
template: {
required: true
}
},
emits: ["object-updated"],
emits: ['object-updated'],
data() {
return {
visible: false,
model: this.user,
service: new AdminService(),
statusChoices: [
{ title: "Active", value: "Active" },
{ title: "Draft", value: "Draft" },
{ title: "Deactivated", value: "Deactivated" }
{ title: 'Active', value: 'Active' },
{ title: 'Draft', value: 'Draft' },
{ title: 'Deactivated', value: 'Deactivated' }
]
};
},
Expand All @@ -32,14 +31,13 @@ export default {
patch() {
let data = {
name: this.model.name,
path: this.model.path,
status: this.model.status
}
};
this.service.patchReportTemplate(this.$api, this.template.pk, data).then(() => {
this.$emit("object-updated", this.model);
this.$emit('object-updated', this.model);
this.visible = false;
});
},
}
},
watch: {
template: {
Expand All @@ -49,23 +47,18 @@ export default {
this.model = value;
}
}
},
}
}
};
</script>

<template>
<Button icon="fa fa-pen-to-square" size="small" @click="open" outlined></Button>

<Dialog header="Update Report Template" v-model:visible="visible" :modal="true" :style="{ width: '70vw' }">

<div class="flex flex-column gap-2">
<label for="name">Name</label>
<InputText id="name" v-model="model.name"></InputText>
</div>
<div class="flex flex-column gap-2">
<label for="first_name">Path</label>
<InputText id="path" v-model="model.path"></InputText>
</div>
<div class="flex flex-column gap-2">
<label for="status">Status</label>
<Dropdown v-model="model.status" optionLabel="title" :options="statusChoices" optionValue="value"></Dropdown>
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/utils/file.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
function forceFileDownload(response) {
const filename = filename_from_response(response);
const url = window.URL.createObjectURL(new Blob([response.data], { type: response.headers['content-type'] }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
const filename = filename_from_response(response);
const url = window.URL.createObjectURL(new Blob([response.data], { type: response.headers['content-type'] }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
}

function filename_from_response(response) {
return response.headers['content-disposition'].split('filename=')[1].split(';')[0];
return response.headers['content-disposition'].split('filename=')[1].split(';')[0];
}

export default forceFileDownload;
3 changes: 1 addition & 2 deletions frontend/src/views/pages/admin/ReportTemplateList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,8 @@ export default {
<div class="col-12">
<Card>
<template #content>
<DataTable paginator dataKey rowHover :rows="pagination.limit" :value="items" lazy filterDisplay="menu" :totalRecords="totalRecords" :loading="loading" @page="onPage" responsiveLayout="scroll" @filter="onFilter" @sort="onSort">
<DataTable paginator rowHover :rows="pagination.limit" :value="items" lazy filterDisplay="menu" :totalRecords="totalRecords" :loading="loading" @page="onPage" responsiveLayout="scroll" @filter="onFilter" @sort="onSort">
<Column field="name" header="Name"></Column>
<Column field="path" header="Path"></Column>
<Column field="status" header="Status"></Column>
<Column header="Actions">
<template #body="slotProps">
Expand Down
51 changes: 6 additions & 45 deletions frontend/src/views/pages/advisories/AdvisoryDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,6 @@ export default {
{ label: 'Medium', value: 'Medium' },
{ label: 'Low', value: 'Low' },
{ label: 'Informational', value: 'Informational' }
],
downloadMenuItems: [
{
label: 'Default PDF',
command: () => {
this.downloadAsPDF();
}
},
{
label: 'Default Markdown',
command: () => {
this.downloadAsMarkdown();
}
}
]
};
},
Expand Down Expand Up @@ -87,9 +73,6 @@ export default {
this.previewData = response.data;
});
},
toggleDownloadMenu(event) {
this.$refs.downloadMenu.toggle(event);
},
getAdvisory() {
this.service.getAdvisory(this.$api, this.advisoryId).then((response) => {
this.advisory = response.data;
Expand Down Expand Up @@ -134,36 +117,16 @@ export default {
};
this.patchAdvisory(data);
},
forceFileDownload(response, title) {
let blob = new Blob([response.data], { type: 'application/pdf' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', title);
link.click();
this.downloadPending = false;
},
forceMarkdownFileDownload(response, title) {
let blob = new Blob([response.data], { type: 'text/plain' });
forceFileDownload(response) {
let blob = new Blob([response.data], { type: response.headers['Content-Type'] });
let filename = response.headers['content-disposition'].split('filename=')[1].split(';')[0];
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', title);
link.setAttribute('download', filename);
link.click();
this.downloadPending = false;
},
downloadAsMarkdown() {
this.downloadPending = true;
this.service
.downloadAdvisoryAsMarkdown(this.$api, this.advisoryId)
.then((response) => {
const filename = 'advisory_' + this.advisoryId + '.md';
this.forceMarkdownFileDownload(response, filename);
})
.finally(() => {
this.downloadPending = false;
});
},
downloadAsPDF() {
this.downloadPending = true;
let params = {};
Expand All @@ -173,8 +136,7 @@ export default {
this.service
.downloadAdvisoryAsPDF(this.$api, this.advisoryId, params)
.then((response) => {
const filename = 'advisory_' + this.advisoryId + '.pdf';
this.forceFileDownload(response, filename);
this.forceFileDownload(response);
this.exportTemplate = null;
})
.finally(() => {
Expand Down Expand Up @@ -235,8 +197,7 @@ export default {
<div class="flex justify-content-end">
<Button icon="fa fa-eye" outlined label="Preview" @click="togglePreview"></Button>

<Button label="Download" icon="fa fa-download" outlined :loading="downloadPending" :disabled="downloadPending" @click="toggleDownloadMenu"></Button>
<Menu ref="downloadMenu" :model="downloadMenuItems" :popup="true"></Menu>
<Button label="Download" icon="fa fa-download" outlined :loading="downloadPending" :disabled="downloadPending" @click="downloadAsPDF"></Button>
<Button label="Edit" icon="fa fa-pen-to-square" outlined @click="this.$router.push({ name: 'AdvisoryUpdate', params: { advisoryId: this.advisoryId } })"></Button>
<Button label="Delete" severity="danger" @click="confirmDialogDelete" icon="fa fa-trash" outlined></Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export default {
this.findingService
.downloadAsPDF(this.$api, this.projectId, this.findingId)
.then((response) => {
const filename = 'finding_' + this.finding.internal_id + '.pdf';
const filename = 'finding-' + this.finding.unique_id.toLowerCase() + '.pdf';
this.forceFileDownload(response, filename);
})
.finally(() => {
Expand Down
45 changes: 0 additions & 45 deletions server/advisories/tests/test_advisory_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,48 +38,3 @@ def test_management_draft(self):
self.advisory1.save()
self.client.force_login(self.advisory_manager1)
self.basic_status_code_check(self.url, self.client.get, 403)


class AdvisoryMarkdownExportView(APITestCase, PeCoReTTestCaseMixin):
def setUp(self) -> None:
self.init_mixin()
self.url = self.get_url(
"advisories:advisory-export-markdown", pk=self.advisory1.pk
)

def test_allowed(self):
users = [
self.vendor1,
self.pentester1,
self.advisory_manager1,
self.read_only_vendor,
]
for user in users:
self.client.force_login(user)
self.basic_status_code_check(self.url, self.client.get, 200)

def test_forbidden(self):
users = [
self.management2,
self.management1,
self.user1,
self.vendor2,
self.read_only1,
self.pentester2,
]
for user in users:
self.client.force_login(user)
self.basic_status_code_check(self.url, self.client.get, 403)

def test_pentester1(self):
self.client.force_login(self.pentester1)
response = self.basic_status_code_check(self.url, self.client.get, 200)
self.assertNotEqual(response.headers.get("Content-Disposition"), None)
self.assertIn(self.advisory1.pk, response.headers["Content-Disposition"])

def test_advisory_management1_draft(self):
# test draft is not downloadable
self.advisory1.is_draft = True
self.advisory1.save()
self.client.force_login(self.advisory_manager1)
self.basic_status_code_check(self.url, self.client.get, 403)
11 changes: 1 addition & 10 deletions server/advisories/tests/test_advisory_export_tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework.test import APITestCase
from django_q.tasks import async_task, result
from backend import models
from backend.tasks.finding_export import export_advisory, export_advisory_markdown
from backend.tasks.reporting import export_advisory
from pecoret.core.test import PeCoReTTestCaseMixin


Expand All @@ -23,12 +23,3 @@ def test_export_advisory(self):
)
task_result = result(task_id, 200)
self.assertIsNotNone(task_result)

def test_markdown_export(self):
"""test if markdown export of advisory works
"""
task_id = async_task(
export_advisory_markdown, self.advisory, self.report_template, sync=True
)
task_result = result(task_id, 200)
self.assertIsNotNone(task_result)
Loading

0 comments on commit 7c58c13

Please sign in to comment.