Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking: Simplify report generator and customization #125

Merged
merged 4 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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