feat: As a user I want a timer to track the time spent on each objective

This commit is contained in:
Marcel Haag 2023-03-03 13:08:05 +01:00 committed by Cel
parent 4ca2828e0f
commit 3be43fa96e
70 changed files with 2376 additions and 2268 deletions

View File

@ -2305,27 +2305,48 @@
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "1.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"dependencies": {
"@fortawesome/fontawesome-common-types": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
}
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"dependencies": {
"@fortawesome/fontawesome-common-types": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
}
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
"@fortawesome/fontawesome-common-types": "6.3.0"
},
"dependencies": {
"@fortawesome/fontawesome-common-types": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
}
}
},
"@istanbuljs/load-nyc-config": {

View File

@ -24,9 +24,9 @@
"@angular/router": "~12.2.16",
"@fortawesome/angular-fontawesome": "^0.8.2",
"@fortawesome/fontawesome-common-types": "^0.2.36",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-regular-svg-icons": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@nebular/eva-icons": "^8.0.0",
"@nebular/theme": "^8.0.0",
"@ngneat/until-destroy": "~8.0.4",

View File

@ -1,11 +1,14 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {NbMenuItem, NbMenuService} from '@nebular/theme';
import {Subject} from 'rxjs';
import {of, Subject} from 'rxjs';
import {Store} from '@ngxs/store';
import {ChangeCategory} from '@shared/stores/project-state/project-state.actions';
import {Category} from '@shared/models/category.model';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {TranslateService} from '@ngx-translate/core';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {catchError, switchMap, tap} from 'rxjs/operators';
import {Pentest, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
@Component({
selector: 'app-objective-categories',
@ -25,8 +28,22 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.initTranslation();
// Set first item in list as selected
this.categories[0].selected = true;
this.store.select(ProjectState.selectedCategory).pipe(
untilDestroyed(this)
).subscribe({
next: (categoryIndex) => {
if (categoryIndex) {
this.selectedCategory = categoryIndex;
this.categories[categoryIndex].selected = true;
} else {
// Set first item in list as selected
this.categories[0].selected = true;
}
},
error: error => {
console.error(error);
}
});
this.menuService.onItemClick()
.pipe(
untilDestroyed(this)

View File

@ -5,17 +5,8 @@
class="comment-cell"
fragment="{{comment.data['commentId']}}">
</tr>
<!-- Comment ID -->
<ng-container [nbTreeGridColumnDef]="columns[0]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.commentId' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
{{ comment.data['commentId'] || '-' }}
</td>
</ng-container>
<!-- Title -->
<ng-container [nbTreeGridColumnDef]="columns[1]">
<ng-container [nbTreeGridColumnDef]="columns[0]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.title' | translate }}
</th>
@ -24,7 +15,7 @@
</td>
</ng-container>
<!-- Description -->
<ng-container [nbTreeGridColumnDef]="columns[2]">
<ng-container [nbTreeGridColumnDef]="columns[1]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.description' | translate }}
</th>
@ -32,29 +23,15 @@
{{ comment.data['description'] }}
</td>
</ng-container>
<!-- Related Findings -->
<ng-container [nbTreeGridColumnDef]="columns[3]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.relatedFindings' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="related-finding-cell">
<ng-container *ngIf="comment.data['relatedFindings'].length > 0; else NoRelatedFindings">
<app-findig-widget [numberOfFindings]="comment.data['relatedFindings'].length"></app-findig-widget>
</ng-container>
<ng-template #NoRelatedFindings>
{{ 'comment.no.relatedFindings' | translate }}
</ng-template>
</td>
</ng-container>
<!-- Actions -->
<ng-container [nbTreeGridColumnDef]="columns[4]">
<ng-container [nbTreeGridColumnDef]="columns[2]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">
<button nbButton hero
status="info"
size="small"
shape="round"
class="add-comment-button"
[disabled]="pentestInfo$.getValue().status === notStartedStatus"
[disabled]="pentestInfo$.getValue().status !== inProgressStatus"
(click)="onClickAddComment()">
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
{{'comment.add' | translate}}

View File

@ -9,7 +9,7 @@ import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
import {
Comment,
CommentDialogBody,
CommentEntry, RelatedFindingOption,
CommentEntry,
transformCommentsToObjectiveEntries,
transformCommentToRequestBody
} from '@shared/models/comment.model';
@ -35,15 +35,15 @@ export class PentestCommentsComponent implements OnInit {
// HTML only
readonly fa = FA;
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
// HTML only for button enabling
inProgressStatus: PentestStatus = PentestStatus.IN_PROGRESS;
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
objectiveFindings: RelatedFindingOption[] = [];
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
columns: Array<CommentColumns> = [
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.ACTIONS
];
dataSource: NbTreeGridDataSource<CommentEntry>;
data: CommentEntry[] = [];
@ -109,7 +109,6 @@ export class PentestCommentsComponent implements OnInit {
this.commentDialogService.openCommentDialog(
CommentDialogComponent,
this.pentestInfo$.getValue().findingIds,
this.objectiveFindings,
null,
{
closeOnEsc: false,
@ -149,7 +148,6 @@ export class PentestCommentsComponent implements OnInit {
this.commentDialogService.openCommentDialog(
CommentDialogComponent,
this.pentestInfo$.getValue().findingIds,
this.objectiveFindings,
existingComment,
{
closeOnEsc: false,
@ -187,12 +185,11 @@ export class PentestCommentsComponent implements OnInit {
}
requestFindingsData(pentestId: string): void {
this.objectiveFindings = [];
this.findingService.getFindingsByPentestId(pentestId).pipe(
untilDestroyed(this)
).subscribe({
next: (findings: Finding[]) => {
findings.forEach(finding => this.objectiveFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
// findings.forEach(finding => this.objectiveFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
},
error: err => {
console.error(err);

View File

@ -5,13 +5,13 @@
class="finding-cell"
fragment="{{finding.data['findingId']}}">
</tr>
<!-- Finding ID -->
<!-- Title -->
<ng-container [nbTreeGridColumnDef]="columns[0]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'finding.findingId' | translate }}
{{ 'finding.title' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let finding">
{{ finding.data['findingId'] || '-' }}
{{ finding.data['title'] }}
</td>
</ng-container>
<!-- Severity -->
@ -25,13 +25,13 @@
</ng-container>
</td>
</ng-container>
<!-- Title -->
<!-- Description -->
<ng-container [nbTreeGridColumnDef]="columns[2]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'finding.title' | translate }}
{{ 'finding.description' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let finding">
{{ finding.data['title'] }}
{{ finding.data['description'] }}
</td>
</ng-container>
<!-- Impact -->
@ -51,7 +51,7 @@
size="small"
shape="round"
class="add-finding-button"
[disabled]="pentestInfo$.getValue().status === notStartedStatus"
[disabled]="pentestInfo$.getValue().status !== inProgressStatus"
(click)="onClickAddFinding()">
<fa-icon [icon]="fa.faPlus" class="new-finding-icon"></fa-icon>
{{'finding.add' | translate}}

View File

@ -1,5 +1,4 @@
import {Component, OnInit} from '@angular/core';
import {PentestService} from '@shared/services/api/pentest.service';
import {BehaviorSubject, Observable} from 'rxjs';
import {Pentest} from '@shared/models/pentest.model';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
@ -46,10 +45,11 @@ export class PentestFindingsComponent implements OnInit {
// HTML only
readonly fa = FA;
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
// HTML only for button enabling
inProgressStatus: PentestStatus = PentestStatus.IN_PROGRESS;
columns: Array<FindingColumns> = [
FindingColumns.FINDING_ID, FindingColumns.SEVERITY, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.ACTIONS
FindingColumns.TITLE, FindingColumns.SEVERITY, FindingColumns.DESCRIPTION, FindingColumns.IMPACT, FindingColumns.ACTIONS
];
dataSource: NbTreeGridDataSource<FindingEntry>;
@ -218,8 +218,9 @@ export class PentestFindingsComponent implements OnInit {
enum FindingColumns {
FINDING_ID = 'findingId',
SEVERITY = 'severity',
TITLE = 'title',
SEVERITY = 'severity',
DESCRIPTION = 'description',
IMPACT = 'impact',
ACTIONS = 'actions'
}

View File

@ -13,42 +13,23 @@
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
<div class="pentest-status-container">
<!--ToDo: Add changing dialog here-->
<!--<app-status-tag [currentStatus]="pentest$.getValue().status"></app-status-tag>-->
<!-- Pentest Status Selection -->
<div class="pentest-status-dialog">
<nb-select class="status"
[(selected)]="currentStatus"
shape="round" filled
status="{{getPentestFillStatus(currentStatus)}}">
<nb-option *ngFor="let status of statusTexts" [value]="status.value">
{{ status.translationText | translate }}
</nb-option>
</nb-select>
<div class="pentest-status-container" fxLayout="row" fxLayoutGap="2.5rem" fxLayoutAlign="end center">
<!-- Pentest Timer-->
<div class="timer-component">
<app-timer></app-timer>
</div>
<div *ngIf="!pentest$.getValue().id; else updatePentest">
<!-- Complete Pentest -->
<div>
<button nbButton
class="save-pentest-button"
status="primary"
[disabled]="!pentestStatusChanged()"
status="success"
[disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
title="{{ 'global.action.save' | translate }}"
(click)="onClickSavePentest()">
<span class="exit-element-text"> {{ 'global.action.save' | translate }} </span>
(click)="onClickCompletePentestAndRouteBack()">
<fa-icon [icon]="fa.faSquare"></fa-icon>
<span class="action-element-text"> {{ 'global.action.complete' | translate }} </span>
</button>
</div>
<ng-template #updatePentest>
<button nbButton
class="save-pentest-button"
status="primary"
[disabled]="!pentestStatusChanged()"
title="{{ 'global.action.update' | translate }}"
(click)="onClickUpdatePentest()">
<span class="exit-element-text"> {{ 'global.action.update' | translate }} </span>
</button>
</ng-template>
</div>
</div>

View File

@ -11,10 +11,18 @@
}
.pentest-status-container {
display: flex;
align-content: flex-end;
// margin-right: 0.5rem;
// height: 5%;
// display: flex;
// align-content: flex-end;
.timer-component {
height: 2rem !important;
max-height: 2rem !important;
margin: 0.5rem 2.25rem 1rem 0;
}
.action-element-text {
padding-left: 0.5rem;
}
.pentest-status-dialog {
margin: 1rem 2.25rem 1rem 0;
@ -41,6 +49,7 @@
}
.save-pentest-button {
// height: 1rem !important;
margin: 1rem 0 1rem 0;
}
}

View File

@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core';
import {Component, OnDestroy, OnInit} from '@angular/core';
import * as FA from '@fortawesome/free-solid-svg-icons';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {Route} from '@shared/models/route.enum';
@ -20,7 +20,7 @@ import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
templateUrl: './pentest-header.component.html',
styleUrls: ['./pentest-header.component.scss']
})
export class PentestHeaderComponent implements OnInit {
export class PentestHeaderComponent implements OnInit, OnDestroy {
// HTML only
readonly fa = FA;
@ -28,16 +28,21 @@ export class PentestHeaderComponent implements OnInit {
selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('');
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
// Pentest Timer Handler
currentTimeSpent = 0;
private initialTimeSpent: number;
// Pentest Status Handler
status = PentestStatus;
currentStatus: PentestStatus = PentestStatus.NOT_STARTED;
private initialPentestStatus: PentestStatus;
// Status Text Translation Texts
readonly statusTexts: Array<StatusText> = [
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
/* ToDo: Disabled not needed inside pentest */
/*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/
{value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'},
{value: PentestStatus.PAUSED, translationText: 'pentest.statusText.paused'},
{value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
];
@ -51,7 +56,7 @@ export class PentestHeaderComponent implements OnInit {
}
ngOnInit(): void {
this.store.select(ProjectState.project).pipe(
this.store.selectOnce(ProjectState.project).pipe(
untilDestroyed(this)
).subscribe({
next: (selectedProject: Project) => {
@ -68,17 +73,20 @@ export class PentestHeaderComponent implements OnInit {
).subscribe({
next: (selectedPentest: Pentest) => {
this.currentStatus = selectedPentest.status;
this.initialPentestStatus = selectedPentest.status;
this.currentTimeSpent = selectedPentest.timeSpent ? selectedPentest.timeSpent : 0;
this.pentest$.next(selectedPentest);
},
error: err => {
console.error(err);
}
});
// Setup initial values for status and time outside of store subscription
this.initialPentestStatus = this.currentStatus;
this.initialTimeSpent = this.currentTimeSpent;
}
onClickRouteBack(): void {
// ToDo: Change to Objective Overview after routing is fixed
// Route back to overview
this.router.navigate([Route.OBJECTIVE_OVERVIEW])
.then(
() => {
@ -87,27 +95,18 @@ export class PentestHeaderComponent implements OnInit {
).finally();
}
onClickSavePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.store.dispatch(new ChangePentest(pentest));
this.notificationService.showPopup('pentest.popup.save.success', PopupType.SUCCESS);
},
error: err => {
console.log(err);
this.notificationService.showPopup('pentest.popup.save.failed', PopupType.FAILURE);
}
});
onClickCompletePentestAndRouteBack(): void {
// Update existing Pentest
this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.COMPLETED, timeSpent: this.currentTimeSpent});
this.updatePentest();
}
onClickUpdatePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
private updatePentest(): void {
this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.store.dispatch(new ChangePentest(pentest));
this.initialTimeSpent = pentest.timeSpent;
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
},
error: err => {
@ -121,7 +120,7 @@ export class PentestHeaderComponent implements OnInit {
* @return true if initial pentest Status is different from current pentest status
*/
pentestStatusChanged(): boolean {
if (this.initialPentestStatus !== this.currentStatus) {
if (this.initialTimeSpent !== this.currentTimeSpent && this.currentTimeSpent !== 0) {
this.pentestChanged$.next(true);
} else {
this.pentestChanged$.next(false);
@ -129,6 +128,15 @@ export class PentestHeaderComponent implements OnInit {
return this.pentestChanged$.getValue();
}
/**
* @return true if pentest includes at least one finding or comment
*/
pentestHasFindingsOrComments(): boolean {
const pentest: Pentest = this.pentest$.getValue();
// Check if pentest includes any findings or comments
return pentest?.findingIds?.length > 0 || pentest?.commentIds?.length > 0;
}
/**
* @return the correct nb-status for current pentest-status
*/
@ -139,7 +147,7 @@ export class PentestHeaderComponent implements OnInit {
pentestFillStatus = 'basic';
break;
}
case PentestStatus.OPEN: {
case PentestStatus.PAUSED: {
pentestFillStatus = 'info';
break;
}
@ -158,4 +166,12 @@ export class PentestHeaderComponent implements OnInit {
}
return pentestFillStatus;
}
ngOnDestroy(): void {
if (this.pentestStatusChanged()) {
// Save current Pentest before exiting
this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.PAUSED, timeSpent: this.currentTimeSpent});
this.updatePentest();
}
}
}

View File

@ -17,6 +17,7 @@ import {SeverityTagModule} from '@shared/widgets/severity-tag/severity-tag.modul
import {FindingDialogModule} from '@shared/modules/finding-dialog/finding-dialog.module';
import {CommentDialogModule} from '@shared/modules/comment-dialog/comment-dialog.module';
import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.module';
import {TimerModule} from '@shared/modules/timer/timer.module';
@NgModule({
declarations: [
@ -49,6 +50,8 @@ import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.mo
FindingDialogModule,
CommentDialogModule,
FindigWidgetModule,
// Modules
TimerModule,
]
})
export class PentestModule {

View File

@ -14,6 +14,7 @@
"action.download": "Herunterladen",
"action.report": "Bericht",
"action.reset": "Zurücksetzen",
"action.complete": "Fertig",
"action.yes": "Ja",
"action.no": "Nein",
"username": "Nutzername",
@ -222,11 +223,14 @@
"not_started": "Nicht angefangen",
"disabled": "Deaktiviert",
"open": "Offen",
"paused": "Pausiert",
"in_progress": "In Bearbeitung",
"completed": "Fertig"
},
"popup": {
"not.found": "Keine pentests gefunden",
"initial.save.success": "Initialer Pentest erfolgreich aufgesetzt",
"initial.save.failed": "Initialer Pentest konnte nicht aufgesetzt werden",
"save.success": "Pentest erfolgreich gespeichert",
"save.failed": "Pentest konnte nicht gespeichert werden",
"update.success": "Pentest erfolgreich aktualisiert",

View File

@ -14,6 +14,7 @@
"action.download": "Download",
"action.report": "Report",
"action.reset": "Reset",
"action.complete": "Complete",
"action.yes": "Yes",
"action.no": "No",
"username": "Username",
@ -222,11 +223,14 @@
"not_started": "Not Started",
"disabled": "Disabled",
"open": "Open",
"paused": "Paused",
"in_progress": "In progress",
"completed": "Completed"
},
"popup": {
"not.found": "No pentest found",
"initial.save.success": "Initial Pentest successfully setup",
"initial.save.failed": "Initial Pentest could not be setup",
"save.success": "Pentest saved successfully",
"save.failed": "Pentest could not be saved",
"update.success": "Pentest updated successfully",

View File

@ -4,16 +4,17 @@ export class Comment {
id?: string;
title: string;
description?: string;
relatedFindings?: Array<string>;
// List of attachment id's for file upload
attachments?: Array<string>;
constructor(title: string,
description: string,
id?: string,
relatedFindings?: Array<string>) {
attachments?: Array<string>) {
this.id = id ? id : UUID();
this.title = title;
this.description = description;
this.relatedFindings = relatedFindings;
this.attachments = attachments;
}
}
@ -21,7 +22,7 @@ export interface CommentEntry {
commentId: string;
title: string;
description: string;
relatedFindings: Array<string>;
attachments: Array<string>;
kind?: string;
childEntries?: [];
expanded?: boolean;
@ -34,7 +35,7 @@ export function transformCommentsToObjectiveEntries(findings: Comment[]): Commen
commentId: value.id,
title: value.title,
description: value.description,
relatedFindings: value.relatedFindings,
attachments: value.attachments,
kind: 'cell',
childEntries: null,
expanded: false
@ -48,8 +49,7 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme
...comment,
title: comment.title,
description: comment.description,
// Transforms related findings from RelatedFindingOption to list of finding ids
relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.id) : [],
attachments: comment.attachments,
/* Remove Table Entry Object Properties */
childEntries: undefined,
kind: undefined,
@ -62,10 +62,6 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme
export interface CommentDialogBody {
title: string;
description: string;
relatedFindings: Array<RelatedFindingOption>;
attachments: Array<string>;
}
export interface RelatedFindingOption {
id: string;
title: string;
}

View File

@ -10,6 +10,8 @@ export class Finding {
affectedUrls?: Array<string>;
reproduction: string;
mitigation?: string;
// List of attachment id's for file upload
attachments?: Array<string>;
constructor(title: string,
severity: Severity,
@ -18,7 +20,8 @@ export class Finding {
reproduction: string,
id?: string,
affectedUrls?: Array<string>,
mitigation?: string) {
mitigation?: string,
attachments?: Array<string>) {
this.id = id ? id : UUID();
this.severity = severity;
this.title = title;
@ -27,13 +30,15 @@ export class Finding {
this.affectedUrls = affectedUrls ? affectedUrls : null;
this.reproduction = reproduction;
this.mitigation = mitigation ? mitigation : null;
this.attachments = attachments ? attachments : null;
}
}
export interface FindingEntry {
findingId: string;
severity: Severity;
title: string;
severity: Severity;
description: string;
impact: string;
kind?: string;
childEntries?: [];
@ -45,8 +50,9 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin
findings.forEach((value: Finding) => {
findingEntries.push({
findingId: value.id,
severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity,
title: value.title,
severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity,
description: value.description,
impact: value.impact,
kind: 'cell',
childEntries: null,

View File

@ -1,7 +1,7 @@
export enum PentestStatus {
NOT_STARTED = 'NOT_STARTED',
DISABLED = 'DISABLED',
OPEN = 'OPEN',
PAUSED = 'PAUSED',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
}

View File

@ -11,6 +11,7 @@ export class Pentest {
status: PentestStatus;
findingIds?: Array<string>;
commentIds?: Array<string>;
timeSpent?: number;
constructor(category: Category,
refNumber: string,
@ -18,7 +19,8 @@ export class Pentest {
id?: string,
projectId?: string,
findingsIds?: Array<string>,
commentsIds?: Array<string>) {
commentsIds?: Array<string>,
timeSpent?: number) {
this.id = id ? id : UUID();
this.projectId = projectId ? projectId : '';
this.category = category;
@ -26,6 +28,7 @@ export class Pentest {
this.status = status;
this.findingIds = findingsIds ? findingsIds : [];
this.commentIds = commentsIds ? commentsIds : [];
this.timeSpent = timeSpent ? timeSpent : 0;
}
}

View File

@ -50,25 +50,6 @@
</nb-form-field>
</div>
</div>
<!-- Related Findings Layout -->
<!-- Related Findings Form Field -->
<nb-form-field class="comment-form-field">
<label for="{{formArray[2].fieldName}}" class="label">
{{formArray[2].labelKey | translate}}
</label>
<!--<fa-icon nbPrefix [icon]="fa.faExclamationCircle" size="lg" class="finding-icon"></fa-icon>-->
<nb-select placeholder="{{formArray[2].placeholder | translate}}"
id="{{formArray[2].fieldName}}"
formControlName="{{formArray[2].fieldName}}"
(selectedChange)="changeSelected($event)"
multiple fullWidth shape="semi-round" filled status="info"
[size]="'large'" class="form-field relatedFindings">
<nb-option class="reset-option">{{'global.action.reset' | translate}}</nb-option>
<nb-option class="finding-option" *ngFor="let finding of relatedFindings" [value]="finding">
{{finding.title}}
</nb-option>
</nb-select>
</nb-form-field>
</form>
</nb-card-body>
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">

View File

@ -31,7 +31,8 @@
.form-textarea {
width: 42rem !important;
height: 8rem;
// Change back to 16rem after attachments can be uploaded
height: 24rem;
}
.comment-form-field {

View File

@ -5,7 +5,6 @@ import * as FA from '@fortawesome/free-solid-svg-icons';
import deepEqual from 'deep-equal';
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
import {UntilDestroy} from '@ngneat/until-destroy';
import {RelatedFindingOption} from '@shared/models/comment.model';
@Component({
selector: 'app-comment-dialog',
@ -24,11 +23,6 @@ export class CommentDialogComponent implements OnInit {
// HTML only
readonly fa = FA;
relatedFindings: RelatedFindingOption[] = [];
// Includes the findings that got selected as an option
selectedFindings: RelatedFindingOption[] = [];
initialSelectedFindings: RelatedFindingOption[] = [];
constructor(
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
private fb: FormBuilder,
@ -38,7 +32,6 @@ export class CommentDialogComponent implements OnInit {
ngOnInit(): void {
this.dialogData = this.data;
this.relatedFindings = this.dialogData.options[0].additionalData;
this.commentFormGroup = this.generateFormCreationFieldArray();
}
@ -48,29 +41,19 @@ export class CommentDialogComponent implements OnInit {
...accumulator,
[currentValue?.fieldName]: currentValue?.controlsConfig
}), {});
// tslint:disable-next-line:no-string-literal
const preSelectedRelatedFindings = this.data.form['commentRelatedFindings'].controlsConfig[0].value;
if (preSelectedRelatedFindings && preSelectedRelatedFindings.length > 0) {
this.relatedFindings.forEach(finding => {
if (preSelectedRelatedFindings.includes(finding)) {
this.initialSelectedFindings.push(finding);
this.selectedFindings.push(finding);
}
});
}
return this.fb.group(config);
}
changeSelected($event): void {
changeAttachments($event): void {
// tslint:disable-next-line:no-string-literal
this.selectedFindings = this.commentFormGroup.controls['commentRelatedFindings'].value;
// this.commentFormGroup.controls['commentAttachments'].value;
}
onClickSave(value: any): void {
this.dialogRef.close({
title: value.commentTitle,
description: value.commentDescription,
relatedFindings: this.selectedFindings ? this.selectedFindings : []
// relatedFindings: this.selectedFindings ? this.selectedFindings : []
});
}
@ -90,12 +73,8 @@ export class CommentDialogComponent implements OnInit {
const newCommentData = this.commentFormGroup.getRawValue();
Object.entries(newCommentData).forEach(entry => {
const [key, value] = entry;
// Related Findings form field can be ignored since changes here will be recognised inside commentRelatedFindings of tag-list
if (value === null || key === 'commentRelatedFindings') {
newCommentData[key] = '';
}
});
const didChange = !deepEqual(oldCommentData, newCommentData) || !deepEqual(this.initialSelectedFindings, this.selectedFindings);
const didChange = !deepEqual(oldCommentData, newCommentData);
return didChange;
}
@ -109,10 +88,6 @@ export class CommentDialogComponent implements OnInit {
const [key, value] = entry;
commentData[key] = value.controlsConfig[0] ?
(value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : '';
// Related Findings form field can be ignored since changes here will be recognised inside commentRelatedFindings of tag-list
if (key === 'commentRelatedFindings') {
commentData[key] = '';
}
});
return commentData;
}

View File

@ -5,7 +5,7 @@ import {ComponentType} from '@angular/cdk/overlay';
import {Observable} from 'rxjs';
import {Validators} from '@angular/forms';
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
import {Comment, RelatedFindingOption} from '@shared/models/comment.model';
import {Comment} from '@shared/models/comment.model';
@Injectable()
export class CommentDialogService {
@ -30,18 +30,15 @@ export class CommentDialogService {
public openCommentDialog(componentOrTemplateRef: ComponentType<any>,
findingIds: string[],
relatedFindings: RelatedFindingOption[],
comment?: Comment,
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
let dialogData: GenericDialogData;
// Preselect related findings
const selectedRelatedFindings: RelatedFindingOption[] = [];
if (comment && comment.relatedFindings.length > 0 && relatedFindings) {
relatedFindings.forEach(finding => {
if (comment.relatedFindings.includes(finding.id)) {
selectedRelatedFindings.push(finding);
}
// Preselect attachments
const attachments: string[] = [];
if (comment && comment.attachments.length > 0) {
comment.attachments.forEach(attachment => {
// Load attachment to show
});
}
// Setup CommentDialogBody
@ -72,22 +69,6 @@ export class CommentDialogService {
errors: [
{errorCode: 'required', translationKey: 'comment.validationMessage.descriptionRequired'}
]
},
commentRelatedFindings: {
fieldName: 'commentRelatedFindings',
type: 'text',
labelKey: 'comment.relatedFindings.label',
placeholder: findingIds.length === 0 ? 'comment.noFindingsInObjectivePlaceholder' : 'comment.relatedFindingsPlaceholder',
controlsConfig: [
{
value: comment ? selectedRelatedFindings : [],
disabled: findingIds.length === 0
},
[]
],
errors: [
{errorCode: 'required', translationKey: 'finding.validationMessage.relatedFindings'}
]
}
},
options: []
@ -97,8 +78,7 @@ export class CommentDialogService {
{
headerLabelKey: 'comment.edit.header',
buttonKey: 'global.action.update',
accentColor: 'warning',
additionalData: relatedFindings
accentColor: 'warning'
},
];
} else {
@ -106,8 +86,7 @@ export class CommentDialogService {
{
headerLabelKey: 'comment.create.header',
buttonKey: 'global.action.save',
accentColor: 'info',
additionalData: relatedFindings
accentColor: 'info'
},
];
}

View File

@ -1,7 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ExportReportDialogComponent} from './export-report-dialog.component';
import {CommonModule} from '@angular/common';
import {
NB_DIALOG_CONFIG,
NbButtonModule,
@ -9,11 +8,10 @@ import {
NbDialogRef,
NbFormFieldModule,
NbInputModule,
NbLayoutModule, NbRadioModule,
NbTagModule
NbRadioModule
} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms';
import {ReactiveFormsModule} from '@angular/forms';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ThemeModule} from '@assets/@theme/theme.module';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
@ -29,7 +27,6 @@ import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.compon
import {Project, ProjectPentests} from '@shared/models/project.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
import {forwardRef} from '@angular/core';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
describe('ExportReportDialogComponent', () => {

View File

@ -41,7 +41,7 @@ export class ObjectiveChartComponent implements OnInit {
readonly pentestStatusLabels: Array<string> = [
'pentest.statusText.disabled',
'pentest.statusText.not_started',
'pentest.statusText.open',
'pentest.statusText.paused',
'pentest.statusText.in_progress',
'pentest.statusText.completed'
];
@ -58,7 +58,7 @@ export class ObjectiveChartComponent implements OnInit {
const disabledPentests: ProjectPentests[]
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.DISABLED);
const openPentests: ProjectPentests[]
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.OPEN);
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.PAUSED);
const inProgressPentests: ProjectPentests[]
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.IN_PROGRESS);
const completedPentests: ProjectPentests[]

View File

@ -3,7 +3,7 @@
.project-dialog {
width: 34rem !important;
height: 42.5rem;
height: 43.5rem;
.project-dialog-header {
height: 8vh;

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { TimerService } from './timer.service';
describe('TimerService', () => {
let service: TimerService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TimerService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class TimerService {
constructor() { }
}

View File

@ -0,0 +1,18 @@
<div class="timer-module" fxLayout="row" fxLayoutGap="0.5rem" fxLayoutAlign="end center">
<div class="time">
<fa-icon [icon]="fa.faStopwatch" class="stopwatch-icon fa-xl"></fa-icon>
<span>{{ timer | timerDuration }}</span>
</div>
<!-- status="{{getStopwatchButtonStatus()}}" -->
<button nbButton outline
shape="semi-round"
status="{{timerRunning$.getValue() === true ? 'warning' : 'info'}}"
class="stopwatch-player-button"
(click)="onClickTriggerTimer()">
<fa-icon *ngIf="timerRunning$.getValue(), else changeTimerIcon" [icon]="fa.faPause"
class="new-element-icon"></fa-icon>
<ng-template #changeTimerIcon>
<fa-icon [icon]="fa.faPlay" class="stopwatch-player-icon"></fa-icon>
</ng-template>
</button>
</div>

View File

@ -0,0 +1,28 @@
@import '../../../assets/@theme/styles/themes';
.timer-module {
width: 12rem;
max-width: 12rem;
// height: 3rem;
// max-height: 3rem;
// overflow: auto;
background-color: nb-theme(color-basic-transparent-focus);
border-radius: 10px;
.time {
//font-family: Courier, serif;
.stopwatch-icon {
padding-right: 1.5rem;
}
}
.stopwatch-player-button {
margin-left: 1rem;
width: 3.25rem !important;
}
}

View File

@ -0,0 +1,54 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {TimerComponent} from './timer.component';
import {MockPipe} from 'ng-mocks';
import {TimerDurationPipe} from '@shared/pipes/timer-duration.pipe';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {CommonModule} from '@angular/common';
import {NotificationService} from '@shared/services/toaster-service/notification.service';
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
import {NgxsModule} from '@ngxs/store';
describe('TimerComponent', () => {
let component: TimerComponent;
let fixture: ComponentFixture<TimerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TimerComponent,
MockPipe(TimerDurationPipe)
],
imports: [
CommonModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NgxsModule.forRoot([]),
HttpClientModule,
HttpClientTestingModule
],
providers: [
{provide: NotificationService, useValue: new NotificationServiceMock()}
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TimerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,142 @@
import {ChangeDetectionStrategy, Component, OnDestroy, OnInit} from '@angular/core';
import * as FA from '@fortawesome/free-solid-svg-icons';
import {BehaviorSubject} from 'rxjs';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {Store} from '@ngxs/store';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
import {ChangePentest, UpdatePentestStatus, UpdatePentestTime} from '@shared/stores/project-state/project-state.actions';
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
import {PentestService} from '@shared/services/api/pentest.service';
import {Project} from '@shared/models/project.model';
@Component({
selector: 'app-timer',
templateUrl: './timer.component.html',
styleUrls: ['./timer.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
@UntilDestroy()
export class TimerComponent implements OnInit, OnDestroy {
readonly fa = FA;
timer = 0;
interval;
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
timerRunning$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
// Needed for initial pentest creation
selectedProjectId$: BehaviorSubject<string> = new BehaviorSubject<string>('');
constructor(private pentestService: PentestService,
private notificationService: NotificationService,
private readonly store: Store) {
}
ngOnInit(): void {
this.store.selectOnce(ProjectState.project).pipe(
untilDestroyed(this)
).subscribe({
next: (selectedProject: Project) => {
this.selectedProjectId$.next(selectedProject.id);
},
error: err => {
console.error(err);
}
});
this.store.selectOnce(ProjectState.pentest).pipe(
untilDestroyed(this)
).subscribe({
next: (selectedPentest: Pentest) => {
this.pentestInfo$.next(selectedPentest);
// In case pentest time spent is undefined use 0
this.timer = selectedPentest.timeSpent ? selectedPentest.timeSpent : 0;
if (!selectedPentest.id) {
this.createIntialPentestInBackend();
}
},
error: err => {
console.error(err);
}
});
}
private createIntialPentestInBackend(): void {
// Save initial Pentest a new
this.pentestInfo$.next({...this.pentestInfo$.getValue(), timeSpent: this.timer});
this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentestInfo$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.store.dispatch(new ChangePentest(pentest));
this.notificationService.showPopup('pentest.popup.initial.save.success', PopupType.SUCCESS);
},
error: err => {
console.log(err);
this.notificationService.showPopup('pentest.popup.initial.save.failed', PopupType.FAILURE);
}
});
}
onClickTriggerTimer(): void {
this.timerRunning$.next(!this.timerRunning$.getValue());
// Start or pause timer
if (this.timerRunning$.getValue()) {
this.startTimer();
} else {
this.pauseTimer();
}
}
// TIMER related functions below
private startTimer(): boolean {
this.interval = setInterval(() => this.timer++, 1000);
this.store.dispatch(new UpdatePentestStatus(PentestStatus.IN_PROGRESS));
return true;
}
private pauseTimer(): boolean {
clearInterval(this.interval);
this.store.dispatch(new UpdatePentestTime(this.timer));
this.store.dispatch(new UpdatePentestStatus(PentestStatus.PAUSED));
return false;
}
/**
* @return the correct nb-status for current pentest-status
*/
getStopwatchButtonStatus(): string {
const value: PentestStatus = this.pentestInfo$.getValue().status;
let stopwatchButtonStatus;
switch (value) {
case PentestStatus.NOT_STARTED: {
stopwatchButtonStatus = 'basic';
break;
}
case PentestStatus.PAUSED: {
stopwatchButtonStatus = 'info';
break;
}
case PentestStatus.IN_PROGRESS: {
stopwatchButtonStatus = 'warning';
break;
}
case PentestStatus.COMPLETED: {
stopwatchButtonStatus = 'success';
break;
}
default: {
stopwatchButtonStatus = 'basic';
break;
}
}
return stopwatchButtonStatus;
}
ngOnDestroy(): void {
// Automatically push new time spent to store
this.store.dispatch(new UpdatePentestTime(this.timer));
}
}

View File

@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {TimerComponent} from '@shared/modules/timer/timer.component';
import {TimerService} from '@shared/modules/timer/service/timer.service';
import {NbButtonModule, NbCardModule} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TimerDurationPipe} from '@shared/pipes/timer-duration.pipe';
@NgModule({
declarations: [
TimerComponent,
TimerDurationPipe
],
imports: [
CommonModule,
NbCardModule,
FlexLayoutModule,
FontAwesomeModule,
NbButtonModule
],
providers: [
TimerService
],
exports: [
TimerComponent
]
})
export class TimerModule { }

View File

@ -0,0 +1,8 @@
import { TimerDurationPipe } from './timer-duration.pipe';
describe('TimerDurationPipe', () => {
it('create an instance', () => {
const pipe = new TimerDurationPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,38 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'timerDuration',
pure: false
})
export class TimerDurationPipe implements PipeTransform {
/**
* Transforms input time into readable time indication
* @param time of type number
* @param args The unit to be used for calculation
* @returns string in the format `HH:mm:ss`
*/
transform(time: any, ...args: any[]): string {
let hours: string | number = 0;
let minutes: string | number = 0;
let seconds: string | number = 0;
if (time) {
// tslint:disable-next-line:variable-name
const sec_num = parseInt(time, 10); // don't forget the second param
hours = Math.floor(sec_num / 3600);
minutes = Math.floor((sec_num - (hours * 3600)) / 60);
seconds = sec_num - (hours * 3600) - (minutes * 60);
}
/**
* Add the relevant `0` prefix if any of the numbers are less than 10
* i.e. 5 -> 05
*/
seconds = (seconds < 10) ? '0' + seconds : seconds;
minutes = (minutes < 10) ? '0' + minutes : minutes;
hours = (hours < 10) ? '0' + hours : hours;
// Return time in as string in `HH:mm:ss` format
return `${hours}:${minutes}:${seconds}`;
}
}

View File

@ -1,6 +1,7 @@
import {Project} from '@shared/models/project.model';
import {Category} from '@shared/models/category.model';
import {Pentest} from '@shared/models/pentest.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
export class InitProjectState {
@ -34,6 +35,20 @@ export class ChangePentest {
}
}
export class UpdatePentestStatus {
static readonly type = '[ProjectState] UpdatePentestStatus';
constructor(public newPentestStatus: PentestStatus) {
}
}
export class UpdatePentestTime {
static readonly type = '[ProjectState] UpdatePentestTime';
constructor(public time: number) {
}
}
export class UpdatePentestFindings {
static readonly type = '[ProjectState] UpdatePentestFindings';

View File

@ -5,11 +5,14 @@ import {
ChangeCategory,
ChangePentest,
ChangeProject,
InitProjectState, UpdatePentestComments,
UpdatePentestFindings
InitProjectState,
UpdatePentestComments,
UpdatePentestFindings, UpdatePentestStatus,
UpdatePentestTime
} from '@shared/stores/project-state/project-state.actions';
import {Category} from '@shared/models/category.model';
import {Pentest} from '@shared/models/pentest.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
export const PROJECT_STATE_NAME = 'project';
@ -91,6 +94,40 @@ export class ProjectState {
});
}
@Action(UpdatePentestStatus)
updatePentestStatus(ctx: StateContext<ProjectStateModel>, {newPentestStatus}: UpdatePentestStatus): void {
const state = ctx.getState();
let stateSelectedPentest: Pentest = state.selectedPentest;
// State object
const statePentestStatus: PentestStatus = stateSelectedPentest.status || PentestStatus.NOT_STARTED;
// overwrites only timeSpent
stateSelectedPentest = {
...stateSelectedPentest,
status: newPentestStatus
};
// patch project state
ctx.patchState({
selectedPentest: stateSelectedPentest
});
}
@Action(UpdatePentestTime)
updatePentestTime(ctx: StateContext<ProjectStateModel>, {time}: UpdatePentestTime): void {
const state = ctx.getState();
let stateSelectedPentest: Pentest = state.selectedPentest;
// State object
const statePentestTimeSpent: number = stateSelectedPentest.timeSpent || 0;
// overwrites only timeSpent
stateSelectedPentest = {
...stateSelectedPentest,
timeSpent: time
};
// patch project state
ctx.patchState({
selectedPentest: stateSelectedPentest
});
}
@Action(UpdatePentestFindings)
updatePentestFindings(ctx: StateContext<ProjectStateModel>, {findingId}: UpdatePentestFindings): void {
const state = ctx.getState();
@ -109,7 +146,7 @@ export class ProjectState {
...stateSelectedPentest,
findingIds: updatedFindingIds
};
// path project state
// patch project state
ctx.patchState({
selectedPentest: stateSelectedPentest
});
@ -133,7 +170,7 @@ export class ProjectState {
...stateSelectedPentest,
commentIds: updatedCommentIds
};
// path project state
// patch project state
ctx.patchState({
selectedPentest: stateSelectedPentest
});

View File

@ -1,6 +1,6 @@
<ng-container [ngSwitch]="currentStatus">
<nb-tag-list>
<nb-tag *ngSwitchCase="status.OPEN" status="info" appearance="filled"
<nb-tag *ngSwitchCase="status.PAUSED" status="info" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase="status.IN_PROGRESS" status="warning" appearance="filled"
text=" {{getTranslationKey() | translate}}"></nb-tag>

View File

@ -14,7 +14,7 @@ export class StatusTagComponent implements OnInit {
readonly statusTexts: Array<StatusText> = [
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},
{value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'},
{value: PentestStatus.PAUSED, translationText: 'pentest.statusText.paused'},
{value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
];

View File

@ -550,7 +550,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\",\n \"relatedFindings\": []\n}",
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\"\n}",
"options": {
"raw": {
"language": "json"
@ -700,7 +700,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Edited Test Comment Description\",\n \"relatedFindings\": []\n}",
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Edited Test Comment Description\"\n}",
"options": {
"raw": {
"language": "json"

View File

@ -14,7 +14,8 @@ data class Pentest(
val refNumber: String,
val status: PentestStatus,
var findingIds: List<String> = emptyList(),
var commentIds: List<String> = emptyList()
var commentIds: List<String> = emptyList(),
var timeSpent: Int
)
fun buildPentest(body: PentestRequestBody, pentestEntity: PentestEntity): Pentest {
@ -25,7 +26,8 @@ fun buildPentest(body: PentestRequestBody, pentestEntity: PentestEntity): Pentes
refNumber = body.refNumber,
status = PentestStatus.valueOf(body.status),
findingIds = body.findingIds,
commentIds = body.commentIds
commentIds = body.commentIds,
timeSpent = body.timeSpent
)
}
@ -49,7 +51,8 @@ fun Pentest.toPentestResponseBody(): ResponseBody {
"refNumber" to refNumber,
"status" to status,
"findingIds" to findingIds,
"commentIds" to commentIds
"commentIds" to commentIds,
"timeSpent" to timeSpent
)
}
@ -81,7 +84,8 @@ data class PentestRequestBody(
val category: String,
val status: String,
val findingIds: List<String>,
val commentIds: List<String>
val commentIds: List<String>,
val timeSpent: Int
)
/**
@ -107,6 +111,7 @@ fun PentestRequestBody.toPentest(): Pentest {
refNumber = this.refNumber,
status = PentestStatus.valueOf(this.status),
findingIds = this.findingIds,
commentIds = this.commentIds
commentIds = this.commentIds,
timeSpent = this.timeSpent
)
}

View File

@ -21,7 +21,8 @@ fun PentestEntity.toPentest(): Pentest {
this.data.refNumber,
this.data.status,
this.data.findingIds,
this.data.commentIds
this.data.commentIds,
this.data.timeSpent
)
}

View File

@ -3,7 +3,7 @@ package com.securityc4po.api.pentest
enum class PentestStatus {
NOT_STARTED,
DISABLED,
OPEN,
PAUSED,
IN_PROGRESS,
COMPLETED
}

View File

@ -10,30 +10,28 @@ data class Comment (
val id: String = UUID.randomUUID().toString(),
val title: String,
val description: String,
val relatedFindings: List<String>? = emptyList()
// List of attachment id's for file upload
val attachments: List<String>? = emptyList()
)
fun buildComment(body: CommentRequestBody, commentEntity: CommentEntity): Comment {
return Comment(
id = commentEntity.data.id,
title = body.title,
description = body.description,
relatedFindings = body.relatedFindings
description = body.description
)
}
data class CommentRequestBody(
val title: String,
val description: String,
val relatedFindings: List<String>? = emptyList()
val description: String
)
fun Comment.toCommentResponseBody(): ResponseBody {
return mapOf(
"id" to id,
"title" to title,
"description" to description,
"relatedFindings" to relatedFindings
"description" to description
)
}
@ -60,7 +58,6 @@ fun CommentRequestBody.toComment(): Comment {
return Comment(
id = UUID.randomUUID().toString(),
title = this.title,
description = this.description,
relatedFindings = this.relatedFindings
description = this.description
)
}

View File

@ -13,6 +13,6 @@ fun CommentEntity.toComment(): Comment {
this.data.id,
this.data.title,
this.data.description,
this.data.relatedFindings
this.data.attachments
)
}

View File

@ -13,7 +13,9 @@ data class Finding (
val impact: String,
val affectedUrls: List<String>? = emptyList(),
val reproduction: String,
val mitigation: String?
val mitigation: String?,
// List of attachment id's for file upload
val attachments: List<String>? = emptyList()
)
fun buildFinding(body: FindingRequestBody, findingEntity: FindingEntity): Finding {

View File

@ -17,6 +17,7 @@ fun FindingEntity.toFinding(): Finding {
this.data.impact,
this.data.affectedUrls,
this.data.reproduction,
this.data.mitigation
this.data.mitigation,
this.data.attachments
)
}

View File

@ -70,8 +70,10 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
Preprocessors.prettyPrint()
),
RequestDocumentation.relaxedRequestParameters(
RequestDocumentation.parameterWithName("projectId").description("The id of the project you want to get the pentests for"),
RequestDocumentation.parameterWithName("category").description("The category you want to get the pentests for")
RequestDocumentation.parameterWithName("projectId")
.description("The id of the project you want to get the pentests for"),
RequestDocumentation.parameterWithName("category")
.description("The category you want to get the pentests for")
),
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING)
@ -87,7 +89,9 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
PayloadDocumentation.fieldWithPath("[].findingIds").type(JsonFieldType.ARRAY)
.description("List of ids of the findings in the requested pentest"),
PayloadDocumentation.fieldWithPath("[].commentIds").type(JsonFieldType.ARRAY)
.description("List of ids of the comments of the requested pentest")
.description("List of ids of the comments of the requested pentest"),
PayloadDocumentation.fieldWithPath("[].timeSpent").type(JsonFieldType.NUMBER)
.description("Time spent on the pentest")
)
)
)
@ -100,7 +104,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
private val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -109,7 +114,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
private fun getProjectsResponse() = listOf(
@ -143,7 +149,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
Preprocessors.prettyPrint()
),
RequestDocumentation.relaxedPathParameters(
RequestDocumentation.parameterWithName("projectId").description("The id of the project you want to save the pentest for")
RequestDocumentation.parameterWithName("projectId")
.description("The id of the project you want to save the pentest for")
),
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
@ -159,7 +166,9 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
PayloadDocumentation.fieldWithPath("findingIds").type(JsonFieldType.ARRAY)
.description("List of ids of the findings in the created pentest"),
PayloadDocumentation.fieldWithPath("commentIds").type(JsonFieldType.ARRAY)
.description("List of ids of the comments of the created pentest")
.description("List of ids of the comments of the created pentest"),
PayloadDocumentation.fieldWithPath("timeSpent").type(JsonFieldType.NUMBER)
.description("Time spent on the pentest")
)
)
)
@ -171,7 +180,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-CLIENT-001",
status = "IN_PROGRESS",
findingIds = emptyList<String>(),
commentIds = emptyList<String>()
commentIds = emptyList<String>(),
timeSpent = 0
)
}
@ -200,7 +210,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
Preprocessors.prettyPrint()
),
RequestDocumentation.relaxedPathParameters(
RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to update")
RequestDocumentation.parameterWithName("pentestId")
.description("The id of the pentest you want to update")
),
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
@ -216,7 +227,9 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
PayloadDocumentation.fieldWithPath("findingIds").type(JsonFieldType.ARRAY)
.description("List of ids of the findings in the updated pentest"),
PayloadDocumentation.fieldWithPath("commentIds").type(JsonFieldType.ARRAY)
.description("List of ids of the comments of the updated pentest")
.description("List of ids of the comments of the updated pentest"),
PayloadDocumentation.fieldWithPath("timeSpent").type(JsonFieldType.NUMBER)
.description("Time spent on the pentest")
)
)
)
@ -226,9 +239,10 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
category = "INFORMATION_GATHERING",
refNumber = "OTG-INFO-001",
status = "OPEN",
status = "PAUSED",
findingIds = emptyList<String>(),
commentIds = emptyList<String>()
commentIds = emptyList<String>(),
timeSpent = 0
)
}
@ -252,7 +266,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -261,7 +276,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestThree = Pentest(
id = "74eae112-f62c-11ec-b939-0242ac120002",
@ -270,7 +286,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-AUTHN-001",
status = PentestStatus.COMPLETED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
// persist test data in database
mongoTemplate.save(ProjectEntity(projectOne))

View File

@ -77,7 +77,8 @@ class PentestControllerIntTest : BaseIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
private val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -86,7 +87,8 @@ class PentestControllerIntTest : BaseIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
private fun getPentests() = listOf(
@ -122,7 +124,8 @@ class PentestControllerIntTest : BaseIntTest() {
refNumber = "OTG-CLIENT-001",
status = "IN_PROGRESS",
findingIds = emptyList<String>(),
commentIds = emptyList<String>()
commentIds = emptyList<String>(),
timeSpent = 0
)
}
@ -143,7 +146,7 @@ class PentestControllerIntTest : BaseIntTest() {
.jsonPath("$.projectId").isEqualTo("d2e126ba-f608-11ec-b939-0242ac120025")
.jsonPath("$.category").isEqualTo("INFORMATION_GATHERING")
.jsonPath("$.refNumber").isEqualTo("OTG-INFO-001")
.jsonPath("$.status").isEqualTo("OPEN")
.jsonPath("$.status").isEqualTo("PAUSED")
.jsonPath("$.findingIds").isEmpty
.jsonPath("$.commentIds").isEmpty
}
@ -152,9 +155,10 @@ class PentestControllerIntTest : BaseIntTest() {
projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
category = "INFORMATION_GATHERING",
refNumber = "OTG-INFO-001",
status = "OPEN",
status = "PAUSED",
findingIds = emptyList<String>(),
commentIds = emptyList<String>()
commentIds = emptyList<String>(),
timeSpent = 24
)
}
@ -177,7 +181,8 @@ class PentestControllerIntTest : BaseIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -186,7 +191,8 @@ class PentestControllerIntTest : BaseIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestThree = Pentest(
id = "16vbc63c-f624-11ec-b939-0242ac120002",
@ -195,7 +201,8 @@ class PentestControllerIntTest : BaseIntTest() {
refNumber = "OTG-AUTHN-001",
status = PentestStatus.COMPLETED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
// persist test data in database
mongoTemplate.save(ProjectEntity(projectOne))

View File

@ -302,7 +302,8 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -311,7 +312,8 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = emptyList(),
commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f")
commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"),
timeSpent = 56
)
val pentestThree = Pentest(
id = "74eae112-f62c-11ec-b939-0242ac120002",
@ -320,7 +322,8 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
refNumber = "OTG-AUTHN-001",
status = PentestStatus.COMPLETED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 124
)
// Comment
val commentOne = Comment(

View File

@ -193,7 +193,8 @@ class CommentControllerIntTest : BaseIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -202,7 +203,8 @@ class CommentControllerIntTest : BaseIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = emptyList(),
commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f")
commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"),
timeSpent = 56
)
val pentestThree = Pentest(
id = "16vbc63c-f624-11ec-b939-0242ac120002",
@ -211,7 +213,8 @@ class CommentControllerIntTest : BaseIntTest() {
refNumber = "OTG-AUTHN-001",
status = PentestStatus.COMPLETED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 124
)
// Comment
val commentOne = Comment(

View File

@ -350,7 +350,8 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -359,7 +360,8 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 56
)
val pentestThree = Pentest(
id = "74eae112-f62c-11ec-b939-0242ac120002",
@ -368,7 +370,8 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
refNumber = "OTG-AUTHN-001",
status = PentestStatus.COMPLETED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 124
)
// Finding
val findingOne = Finding(

View File

@ -217,7 +217,8 @@ class FindingControllerIntTest: BaseIntTest() {
refNumber = "OTG-INFO-001",
status = PentestStatus.NOT_STARTED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 0
)
val pentestTwo = Pentest(
id = "43fbc63c-f624-11ec-b939-0242ac120002",
@ -226,7 +227,8 @@ class FindingControllerIntTest: BaseIntTest() {
refNumber = "OTG-INFO-002",
status = PentestStatus.IN_PROGRESS,
findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 56
)
val pentestThree = Pentest(
id = "16vbc63c-f624-11ec-b939-0242ac120002",
@ -235,7 +237,8 @@ class FindingControllerIntTest: BaseIntTest() {
refNumber = "OTG-AUTHN-001",
status = PentestStatus.COMPLETED,
findingIds = emptyList(),
commentIds = emptyList()
commentIds = emptyList(),
timeSpent = 124
)
// Finding
val findingOne = Finding(

View File

@ -1,54 +1,33 @@
[{
"_id": {
"$oid": "63a4530b9bb2aa2c3bc77b52"
"$oid": "6405dbf113ae975803a09901"
},
"lastModified": {
"$date": {
"$numberLong": "1671713547508"
"$numberLong": "1678105585081"
}
},
"data": {
"_id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment",
"description": "No related findings",
"relatedFindings": []
"_id": "85935303-e5b7-48ca-a504-910c1a94fb1f",
"title": "Uninteresting comment",
"description": "Nothing",
"attachments": []
},
"_class": "com.securityc4po.api.comment.CommentEntity"
"_class": "com.securityc4po.api.pentest.comment.CommentEntity"
},{
"_id": {
"$oid": "63a453e4377c3f53f86d27d8"
"$oid": "6405dc0513ae975803a09902"
},
"lastModified": {
"$date": {
"$numberLong": "1671713764781"
"$numberLong": "1678105605811"
}
},
"data": {
"_id": "df516de6-ca5e-44a6-ac50-db89bb17aac3",
"title": "New Test Comment",
"description": "Two related findings",
"relatedFindings": [
"0bda8950-94fa-4ec6-8fa7-e09f5a8cd3e8",
"4ddb84f6-068c-4319-a8ee-1000008bb75a"
]
"_id": "a785aaf0-1feb-429e-beb1-31bfcf70c404",
"title": "Interesting comment",
"description": "I know where your house lives",
"attachments": []
},
"_class": "com.securityc4po.api.comment.CommentEntity"
},{
"_id": {
"$oid": "63a454b5377c3f53f86d27d9"
},
"lastModified": {
"$date": {
"$numberLong": "1671713973541"
}
},
"data": {
"_id": "e55e943b-6a48-4d84-8d72-b48d7d9de5b7",
"title": "Another Test Comment",
"description": "One related findings",
"relatedFindings": [
"5e22d38f-a4f6-4809-84ea-a803b5f1f9fc"
]
},
"_class": "com.securityc4po.api.comment.CommentEntity"
"_class": "com.securityc4po.api.pentest.comment.CommentEntity"
}]

View File

@ -1,357 +1,106 @@
[{
"_id": {
"$oid": "6372223efea5724fd22bae8a"
"$oid": "6405d88b13ae975803a098fb"
},
"lastModified": {
"$date": {
"$numberLong": "1668424254533"
"$numberLong": "1678104715816"
}
},
"data": {
"_id": "ef31449d-71ec-4736-952f-8b20e53117d5",
"_id": "a343150a-91c9-4564-9638-d0377eecc7c9",
"severity": "LOW",
"title": "Test Title",
"description": "Test Description",
"impact": "Test Impact",
"affectedUrls": [
"https://akveo.github.io/nebular/docs/components/progress-bar/examples#nbprogressbarcomponent"
],
"reproduction": "Step 1: Test",
"mitigation": "Test Mitigatin"
"title": "Low Prio Finding",
"description": "This is Low Prio.",
"impact": "Impacts nothing.",
"affectedUrls": [],
"reproduction": "Open App.",
"mitigation": "",
"attachments": []
},
"_class": "com.securityc4po.api.finding.FindingEntity"
"_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{
"_id": {
"$oid": "63725fa6e612626e2c6956ee"
"$oid": "6405db8a13ae975803a098fe"
},
"lastModified": {
"$date": {
"$numberLong": "1668439974730"
"$numberLong": "1678105482494"
}
},
"data": {
"_id": "0bda8950-94fa-4ec6-8fa7-e09f5a8cd3e8",
"_id": "5bf1b2e1-69b7-463b-a1ca-4ac6ac66b10f",
"severity": "MEDIUM",
"title": "Medium Prio Finding",
"description": "Medium Description",
"impact": "Medium Impact",
"affectedUrls": [],
"reproduction": "1. Open App",
"mitigation": "",
"attachments": []
},
"_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{
"_id": {
"$oid": "6405dba513ae975803a098ff"
},
"lastModified": {
"$date": {
"$numberLong": "1678105509645"
}
},
"data": {
"_id": "f6e6c632-ab34-479e-9584-565f61c5862a",
"severity": "HIGH",
"title": "High Title",
"description": "High Description",
"title": "High Prio Finding",
"description": "High Prio Description",
"impact": "High Impact",
"affectedUrls": [
"https://angular.io/guide/routing-overview"
],
"reproduction": "Step 1: Not be High",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374c3c4e0136563b96187b8"
},
"lastModified": {
"$date": {
"$numberLong": "1668596676210"
}
},
"data": {
"_id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
"severity": "MEDIUM",
"title": "Medium Finding",
"description": "Medium",
"impact": "Medium",
"affectedUrls": [],
"reproduction": "Medium",
"mitigation": ""
"reproduction": "1. Open App\n2. Hack",
"mitigation": "",
"attachments": []
},
"_class": "com.securityc4po.api.finding.FindingEntity"
"_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{
"_id": {
"$oid": "6374c43be0136563b96187b9"
"$oid": "6405dbcc13ae975803a09900"
},
"lastModified": {
"$date": {
"$numberLong": "1668596795003"
"$numberLong": "1678105548815"
}
},
"data": {
"_id": "72886128-b2d9-4a92-bbfe-b54373441321",
"_id": "176f5d93-0fe3-40b1-8a25-f11a6f760148",
"severity": "CRITICAL",
"title": "Critical Issue",
"description": "Critical",
"impact": "Critical",
"title": "Critical Prio Finding",
"description": "Critical Description",
"impact": "Critical Impact",
"affectedUrls": [],
"reproduction": "Critical",
"mitigation": ""
"reproduction": "1. Open App\n2. Hack\n3. Break everything",
"mitigation": "",
"attachments": []
},
"_class": "com.securityc4po.api.finding.FindingEntity"
"_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{
"_id": {
"$oid": "6374c488e0136563b96187ba"
"$oid": "640854a01d5b385d85c60ba7"
},
"lastModified": {
"$date": {
"$numberLong": "1668596872152"
"$numberLong": "1678267552968"
}
},
"data": {
"_id": "4ddb84f6-068c-4319-a8ee-1000008bb75a",
"severity": "HIGH",
"title": "Anothe High Issues",
"description": "High",
"impact": "High",
"affectedUrls": [],
"reproduction": "High",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374c624e0136563b96187bb"
},
"lastModified": {
"$date": {
"$numberLong": "1668597284983"
}
},
"data": {
"_id": "42831151-51fd-4348-b829-6b18ddd14fe1",
"severity": "MEDIUM",
"title": "Another Medium FInding",
"description": "Medium",
"impact": "Medium",
"affectedUrls": [],
"reproduction": "Medium",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374cb33e0136563b96187bc"
},
"lastModified": {
"$date": {
"$numberLong": "1668598579443"
}
},
"data": {
"_id": "559cd0ac-9e64-41f9-892a-4c8a9dd30357",
"_id": "1ffc2215-b8ae-43b7-bbb7-bfcfb414d534",
"severity": "LOW",
"title": "Another Low One",
"description": "Low",
"impact": "Low",
"title": "Low Prio Title",
"description": "Low Prio Description",
"impact": "Low Prio Impact",
"affectedUrls": [],
"reproduction": "Low",
"mitigation": ""
"reproduction": "Do Nothing",
"mitigation": "",
"attachments": []
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374cb98e0136563b96187bd"
},
"lastModified": {
"$date": {
"$numberLong": "1668598680140"
}
},
"data": {
"_id": "5e22d38f-a4f6-4809-84ea-a803b5f1f9fc",
"severity": "LOW",
"title": "common",
"description": "common",
"impact": "common",
"affectedUrls": [],
"reproduction": "common",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374cc51e0136563b96187be"
},
"lastModified": {
"$date": {
"$numberLong": "1668598865728"
}
},
"data": {
"_id": "0bfa7511-fe33-4ab5-9af2-d4ed70c1b350",
"severity": "HIGH",
"title": "Highihihi",
"description": "High",
"impact": "High",
"affectedUrls": [],
"reproduction": "High",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374cd00e0136563b96187bf"
},
"lastModified": {
"$date": {
"$numberLong": "1668599040593"
}
},
"data": {
"_id": "70e413b9-d736-40d2-b7d6-236768b1230c",
"severity": "MEDIUM",
"title": "Medium Rare",
"description": "Medium",
"impact": "Medium",
"affectedUrls": [],
"reproduction": "Medium",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6374ec35e0136563b96187c8"
},
"lastModified": {
"$date": {
"$numberLong": "1668607029072"
}
},
"data": {
"_id": "672d9f87-fb3d-4fc5-8c6f-cadf97661ca5",
"severity": "HIGH",
"title": "Test",
"description": "Test",
"impact": "Test",
"affectedUrls": [],
"reproduction": "Test",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "637606830687d905ca60af1d"
},
"lastModified": {
"$date": {
"$numberLong": "1668679299814"
}
},
"data": {
"_id": "bddf810b-f20e-473e-a63d-34fcba7e48ef",
"severity": "CRITICAL",
"title": "Login SQL Injection ",
"description": "Inside Login Form using the ' or TRUE-- Syntax will enable the user to login as the Admin.",
"impact": "Active User Session with Admin priviledges can affect the whole application.",
"affectedUrls": [
"http://localhost:3000/#/login"
],
"reproduction": "Step 1:\nGo to login page.\n\nStep 2:\nEnter ' or TRUE-- in the username field and enter a random password.",
"mitigation": ""
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "637612280687d905ca60af20"
},
"lastModified": {
"$date": {
"$numberLong": "1668682280551"
}
},
"data": {
"_id": "d7c95af7-5434-4768-b62c-5b11f9396276",
"severity": "MEDIUM",
"title": "Searchbar XSS",
"description": "Adding <iframe> in the search bar of the header results in XSS Vuln.",
"impact": "This impacts the Webapplication",
"affectedUrls": [
"http://localhost:3000/#/search?q=%3Ciframe%20src%3D%22javascript:alert(%60xss%60)%22%3E"
],
"reproduction": "Step 1: \nClick on search bar of header.\n\nStep 2: \nEnter <iframe src=\"javascript:alert(`xss`)\">\n\nStep3: Press ENTER\n\nYou will now get a PopUp because the javascript code was executed inside the browser.",
"mitigation": "Sanitse Input Field."
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "63776860fcdda12bf2e51eb2"
},
"lastModified": {
"$date": {
"$numberLong": "1670489874240"
}
},
"data": {
"_id": "cb33fad4-7965-4654-a9f9-f007edaca35c",
"severity": "HIGH",
"title": "Searchbar XSS",
"description": "Adding <iframe src=\"javascript:alert('xss')\"> in the search bar of the header results in XSS Vuln.",
"impact": "This impacts the Webbapp.",
"affectedUrls": [
"http://localhost:3000/#/search?q=%3Ciframe%20src%3D%22javascript:alert('xss')%22%3E"
],
"reproduction": "Step1: \nClick on search field of the header\n\nStep 2: \nEnter <iframe src=\"javascript:alert('xss')\">\n\nStep 3: \nPress ENTER\n\nYou will now get a PopUp",
"mitigation": "Sanitise Input Fields."
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6391a364aceddd4dd1b32322"
},
"lastModified": {
"$date": {
"$numberLong": "1670488932489"
}
},
"data": {
"_id": "b6dfddde-9bc2-4658-8c18-668190053105",
"severity": "CRITICAL",
"title": "Searchbar XSS",
"description": "Adding <iframe src=\"javascript:alert('xss')\"> in the search bar of the header results in XSS Vuln.",
"impact": "This impacts the Webbapp.",
"affectedUrls": [
"http://localhost:3000/#/search?q=%3Ciframe%20src%3D%22javascript:alert('xss')%22%3E"
],
"reproduction": "Step1: \nClick on search field of the header\n\nStep 2: \nEnter <iframe src=\"javascript:alert('xss')\">\n\nStep 3: \nPress ENTER\n\nYou will now get a PopUp",
"mitigation": "Sanitise Input Fields."
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6391a39baceddd4dd1b32323"
},
"lastModified": {
"$date": {
"$numberLong": "1670488987700"
}
},
"data": {
"_id": "e9a50c5d-e9ea-4596-b1a9-8ad67eddef04",
"severity": "CRITICAL",
"title": "Searchbar XSS",
"description": "Adding <iframe src=\"javascript:alert('xss')\"> in the search bar of the header results in XSS Vuln.",
"impact": "This impacts the Webbapp.",
"affectedUrls": [
"http://localhost:3000/#/search?q=%3Ciframe%20src%3D%22javascript:alert('xss')%22%3E"
],
"reproduction": "Step1: \nClick on search field of the header\n\nStep 2: \nEnter <iframe src=\"javascript:alert('xss')\">\n\nStep 3: \nPress ENTER\n\nYou will now get a PopUp",
"mitigation": "Sanitise Input Fields."
},
"_class": "com.securityc4po.api.finding.FindingEntity"
},{
"_id": {
"$oid": "6391a3cbaceddd4dd1b32324"
},
"lastModified": {
"$date": {
"$numberLong": "1670489035954"
}
},
"data": {
"_id": "7f51e615-230f-4f90-a671-13e66e82370f",
"severity": "CRITICAL",
"title": "Searchbar XSS",
"description": "Adding <iframe src=\"javascript:alert('xss')\"> in the search bar of the header results in XSS Vuln.",
"impact": "This impacts the Webbapp.",
"affectedUrls": [
"http://localhost:3000/#/search?q=%3Ciframe%20src%3D%22javascript:alert('xss')%22%3E"
],
"reproduction": "Step1: \nClick on search field of the header\n\nStep 2: \nEnter <iframe src=\"javascript:alert('xss')\">\n\nStep 3: \nPress ENTER\n\nYou will now get a PopUp",
"mitigation": "Sanitise Input Fields."
},
"_class": "com.securityc4po.api.finding.FindingEntity"
}]
"_class": "com.securityc4po.api.pentest.finding.FindingEntity"
}]

File diff suppressed because it is too large Load Diff

View File

@ -1,351 +1,269 @@
[{
"_id": {
"$oid": "62f3c50c7acde34f740ba737"
"$oid": "6405d84a13ae975803a098fa"
},
"lastModified": {
"$date": {
"$numberLong": "1668425376081"
"$numberLong": "1678109612474"
}
},
"data": {
"_id": "5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
"_id": "575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2",
"client": "Dio Stonemask Inc.",
"title": "log4jj bizarre adventure",
"createdAt": "2022-08-10T14:47:40.140406Z",
"createdAt": "2023-03-06T12:10:50.835664Z",
"tester": "Jojo",
"summary": "This report includes an Executeive Summary, the rules in regards to the scope of the pentest and the choosen approach of the pentester.\nDio Stonemask Inc. contracted Jojo to perform a Penetration Test to identify security weaknesses,\ndetermine the impact to Dio Stonemask Inc., document all findings in a clear and repeatable manner,\nand provide remediation recommendations",
"projectPentests": [
{
"pentestId": "11601f51-bc17-47fd-847d-0c53df5405b5",
"status": "IN_PROGRESS"
"pentestId": "54f3ce12-784a-4e44-b9b3-0a986119ec50",
"status": "COMPLETED"
},
{
"pentestId": "9a073a08-e4fc-4450-8202-c902455b66ec",
"status": "OPEN"
"pentestId": "d724df75-e85a-4124-a5be-bccadc78beaf",
"status": "PAUSED"
},
{
"pentestId": "981c5e24-7276-47f8-a821-ff5976292ad4",
"status": "OPEN"
"pentestId": "c9c1c2f4-14dd-43f4-bc0d-bac03755f798",
"status": "PAUSED"
},
{
"pentestId": "2d46a183-8f11-4fbc-bbf1-e439f7282bb9",
"status": "OPEN"
"pentestId": "288599c2-c295-4825-b1ff-db20e99f45ba",
"status": "PAUSED"
},
{
"pentestId": "eb4f80f3-caac-4fef-a5dd-53616701f171",
"status": "OPEN"
"pentestId": "7c1c1d64-000d-461b-b60f-50bfc70868e6",
"status": "PAUSED"
},
{
"pentestId": "0ab8de31-9d5e-4b6b-a43c-12207c160863",
"status": "IN_PROGRESS"
"pentestId": "415528d1-a92c-4e14-adf1-2846b2ce0f70",
"status": "PAUSED"
},
{
"pentestId": "3ed9e894-58e8-46b9-9859-cde675fec17c",
"status": "OPEN"
"pentestId": "8d91e25f-eaeb-42f6-800c-4e7113656321",
"status": "PAUSED"
},
{
"pentestId": "53fdab75-ea52-4cea-85ed-df8b67f41b72",
"status": "OPEN"
"pentestId": "ed9595bb-cc80-4daa-873e-e7470fc0b7d1",
"status": "PAUSED"
},
{
"pentestId": "6270d4bc-5f39-4358-ad0a-fd5791191f28",
"status": "IN_PROGRESS"
"pentestId": "35481ca5-5672-4a11-a2b8-38ece069ca70",
"status": "PAUSED"
},
{
"pentestId": "1a90f468-470a-4b1e-9783-cc761b1770ee",
"status": "OPEN"
"pentestId": "538f8e15-8d0e-43ac-b7a6-d6b5959581eb",
"status": "PAUSED"
},
{
"pentestId": "6eb37869-baef-4a5b-9ac0-bf202a49874f",
"status": "OPEN"
"pentestId": "3bff597e-d680-4b87-8352-be32f40db074",
"status": "PAUSED"
},
{
"pentestId": "da89c933-1413-4186-ad2c-f1967cb8dbb4",
"status": "OPEN"
"pentestId": "27ca5852-aa9f-44ed-b2fe-c46c31b415f4",
"status": "PAUSED"
},
{
"pentestId": "b3682591-f6c3-4969-bf15-69f4d495ef18",
"status": "OPEN"
"pentestId": "60cf0cf9-f62a-4669-87a7-f519e7be0613",
"status": "PAUSED"
},
{
"pentestId": "9e8e2736-afc9-4f63-b29f-567f9f316c83",
"status": "OPEN"
"pentestId": "05251dfd-a382-47af-85d5-798dd1a6171a",
"status": "PAUSED"
},
{
"pentestId": "3405bdd6-1ae2-4876-9c18-443a791cec9c",
"status": "OPEN"
"pentestId": "be6780a2-b66e-42a6-a725-805633589921",
"status": "PAUSED"
},
{
"pentestId": "2fd387b3-b7a5-4297-9790-5d7845214c05",
"status": "OPEN"
"pentestId": "192b9fed-596b-4345-b33d-ca3882ba9bdd",
"status": "PAUSED"
},
{
"pentestId": "a61116c5-1859-4df3-8252-7788c31472d8",
"status": "OPEN"
"pentestId": "6d3f0b58-b311-465e-9f01-e3e45d165902",
"status": "PAUSED"
},
{
"pentestId": "47d8b39d-9fa7-4772-8605-84aa0531f49e",
"status": "OPEN"
"pentestId": "058dd5c7-63a5-40cb-a4ed-46e5cdcb87ff",
"status": "PAUSED"
},
{
"pentestId": "bd2b8899-0cd9-41fd-a975-257aac48b81f",
"status": "OPEN"
"pentestId": "36e1c198-d425-4a38-ad0b-2f9d6759931e",
"status": "PAUSED"
},
{
"pentestId": "b9bde632-c275-4566-b693-c57a3dad47f3",
"status": "OPEN"
"pentestId": "b3063d09-237f-493e-b0db-603a11829d88",
"status": "PAUSED"
},
{
"pentestId": "32cc5c4e-7234-42b7-8031-c2e231bc0404",
"status": "OPEN"
"pentestId": "6ae89321-678f-4191-b008-8abfc42401c3",
"status": "PAUSED"
},
{
"pentestId": "07e34e95-7dda-499a-8be8-0e8378f0e0d0",
"status": "OPEN"
"pentestId": "3334d254-87bf-4115-8d88-e2fed022ad06",
"status": "PAUSED"
},
{
"pentestId": "b70f6720-ee17-49d6-8838-bd776cd18d0a",
"status": "OPEN"
"pentestId": "8e97f1e0-b02c-4be2-b30e-372d09614038",
"status": "PAUSED"
},
{
"pentestId": "9fb260ea-333f-44c6-884b-e46352564e2a",
"status": "OPEN"
"pentestId": "e9c9eecb-116b-4a8c-ac8c-4a279f77e1f4",
"status": "PAUSED"
},
{
"pentestId": "87f492f7-991b-4e04-9531-5dba0bc34b1b",
"status": "OPEN"
"pentestId": "f0531d71-18d3-41a7-a37a-2c15f6b26dcb",
"status": "PAUSED"
},
{
"pentestId": "6d846445-d470-447a-96b3-8f4b57df3221",
"status": "OPEN"
"pentestId": "d73543ef-a66f-4878-9ecb-ab5207ed734f",
"status": "PAUSED"
},
{
"pentestId": "123c43ae-6870-4883-a1c5-2f99946e2c2d",
"status": "OPEN"
"pentestId": "22130f1e-53c2-404b-8f77-750e82d12768",
"status": "PAUSED"
},
{
"pentestId": "8be5b377-3eb0-4b54-81d2-8cfd5ea1f0f1",
"status": "OPEN"
"pentestId": "54db12f1-1fdc-48f9-9b1d-b6b1fb39bc07",
"status": "PAUSED"
},
{
"pentestId": "6b1d2b71-9e31-4e78-a82e-5325c699658c",
"status": "OPEN"
"pentestId": "7853a95c-7ee3-4b31-af18-401c104efc7e",
"status": "PAUSED"
},
{
"pentestId": "77e765ef-40fb-4b6e-9d80-1e06cae7d4a3",
"status": "OPEN"
"pentestId": "7ca78e39-7d4c-46c5-a9c3-ba58c7fba844",
"status": "PAUSED"
},
{
"pentestId": "5821cd2c-aa17-4339-b697-1b4089d3bf93",
"status": "OPEN"
"pentestId": "dca5b8b3-e994-4d5c-8740-b21ee806a4e5",
"status": "PAUSED"
},
{
"pentestId": "bb57b94f-c8bc-4dd9-b4bf-e14d0a97cc31",
"status": "OPEN"
"pentestId": "5e7b999c-e878-4d48-9ce8-9b65ef578dae",
"status": "PAUSED"
},
{
"pentestId": "a5e3aaba-268e-4a40-92f9-05c0dae4cc0f",
"status": "OPEN"
"pentestId": "8bc131f4-b9c8-4dd5-927b-0675dff6344e",
"status": "PAUSED"
},
{
"pentestId": "18ed1ddb-524a-4333-af90-7716bd51dc7b",
"status": "OPEN"
"pentestId": "ed134842-6578-4d22-af57-282161c5306b",
"status": "PAUSED"
},
{
"pentestId": "c2d19d1e-39e5-4862-82c9-d88c5d91f630",
"status": "OPEN"
"pentestId": "f35f30fb-f246-4a1f-ae26-ce864647a341",
"status": "PAUSED"
},
{
"pentestId": "728e294f-e27d-4bef-903b-d9eeb54cf086",
"status": "OPEN"
"pentestId": "47021e69-95ab-4d93-ac13-aac0379ca809",
"status": "PAUSED"
},
{
"pentestId": "91cd7aee-acda-4c95-ba35-16932448f29f",
"status": "OPEN"
"pentestId": "f19a5176-64bc-452b-aa63-8861aab75059",
"status": "PAUSED"
},
{
"pentestId": "e496d9ba-7775-479e-8904-864c04fec3f9",
"status": "OPEN"
"pentestId": "c60ac6e5-39e8-4fae-8d65-d71ea69a2404",
"status": "PAUSED"
},
{
"pentestId": "ee87e923-63d7-40bc-b41e-049fe087e1dd",
"status": "OPEN"
"pentestId": "2764e64b-0a7e-456c-9999-cdd05c5ef50b",
"status": "PAUSED"
},
{
"pentestId": "cbe94eaf-c734-4d6f-96ec-7d84a4a5b5cc",
"status": "IN_PROGRESS"
"pentestId": "1247dd20-2986-4887-9c17-74806ce56eef",
"status": "PAUSED"
},
{
"pentestId": "c9ecfc9f-23f1-4744-a578-54b0c96a9e87",
"status": "IN_PROGRESS"
"pentestId": "e01d1a34-15fa-4f29-8054-8209a422e505",
"status": "PAUSED"
},
{
"pentestId": "ca0c10a1-8fcc-4b0b-98c0-2403709d7e50",
"status": "OPEN"
"pentestId": "c55343b0-c99c-4bfd-8f30-b8464b442dad",
"status": "PAUSED"
},
{
"pentestId": "bce6f266-2c70-4e45-a1db-d767e4bcc1f8",
"status": "OPEN"
"pentestId": "47ff61bb-2e4f-45e3-9630-136f9d704882",
"status": "PAUSED"
},
{
"pentestId": "be0b07a3-64e4-4122-a362-dd657b8b6b0a",
"status": "OPEN"
"pentestId": "0b353e67-3092-4586-9558-172354beaf8b",
"status": "PAUSED"
},
{
"pentestId": "8f2230fb-bd5c-4047-9db6-74bc49be9cc1",
"status": "OPEN"
"pentestId": "5804e2ce-8c5b-4f3d-8674-433042e61a7f",
"status": "PAUSED"
},
{
"pentestId": "a1b00a90-cb14-475f-ba3a-5807a21df704",
"status": "OPEN"
"pentestId": "4fc1260b-8b5b-47a7-bdee-61261e23919d",
"status": "PAUSED"
},
{
"pentestId": "af2e7766-ecd1-4015-b4e1-c0b978643a0f",
"status": "OPEN"
"pentestId": "39dfbf25-e97d-4bd8-9943-a9eec183bfcf",
"status": "PAUSED"
},
{
"pentestId": "27b64044-b3ff-48bf-9220-837b420f3904",
"status": "OPEN"
"pentestId": "53668fb6-471d-4363-9e47-8f73e4f1a7d4",
"status": "PAUSED"
},
{
"pentestId": "b5eb1683-700a-4522-8b53-45809e665643",
"status": "OPEN"
"pentestId": "86637ffd-8e6e-4e00-9179-42f52780427a",
"status": "PAUSED"
},
{
"pentestId": "86b4d382-e433-4bac-ab6e-530a0dce299d",
"status": "OPEN"
"pentestId": "04f9532e-3c05-4eff-9e9f-b2d733a14a77",
"status": "PAUSED"
},
{
"pentestId": "7a118a29-f983-4219-834c-f01554231910",
"status": "OPEN"
"pentestId": "1e58f29e-81fb-48d2-94bf-7b89e227f590",
"status": "PAUSED"
},
{
"pentestId": "ac9bc697-a53f-4278-98b9-05d8ba19a50d",
"status": "OPEN"
"pentestId": "2c78589b-558e-4b99-a182-df4df3c1439b",
"status": "PAUSED"
},
{
"pentestId": "13cecebb-321a-4ef8-8116-f6814652f7d7",
"status": "OPEN"
"pentestId": "9383b9c1-6c2e-422b-b16f-31a9640d1647",
"status": "PAUSED"
},
{
"pentestId": "048287bc-c41b-49a1-aeb5-2cc98a5bad06",
"status": "OPEN"
"pentestId": "2f87faf9-611f-40ae-9c0e-412d0bfd0481",
"status": "PAUSED"
},
{
"pentestId": "4d1b424e-05ea-468c-9902-3626a79ccfe6",
"status": "OPEN"
"pentestId": "0f47fcbc-f567-4009-ae56-a894cf17cc46",
"status": "PAUSED"
},
{
"pentestId": "377d73b8-f8da-461e-909b-524a38a37ed6",
"status": "OPEN"
"pentestId": "ba0fa19c-5533-4be8-8169-9ffa7d449ab0",
"status": "PAUSED"
},
{
"pentestId": "16e10ad9-f49d-4a74-9de7-10a49e2401e2",
"status": "OPEN"
},
{
"pentestId": "4c68c22e-6073-4ec8-aebb-45ad2a3cc848",
"status": "OPEN"
},
{
"pentestId": "276e5823-b517-445c-b182-e6eda6478d44",
"status": "OPEN"
},
{
"pentestId": "84c661c0-2775-440a-97c5-ff35f345cabb",
"status": "OPEN"
},
{
"pentestId": "fb6d909c-8d16-48e3-b0e5-aba9bf3e8eae",
"status": "OPEN"
},
{
"pentestId": "0b211e22-dd63-46cc-a12f-be7ac73d7a64",
"status": "OPEN"
},
{
"pentestId": "63310549-e2a8-4dd0-a91a-9cfa06e2dc41",
"status": "OPEN"
},
{
"pentestId": "ac8d52d0-f0c8-47ec-ab13-24f40dc4f9e6",
"status": "OPEN"
},
{
"pentestId": "3ddc4950-f662-4ec1-9a04-b9c3591d8b06",
"status": "OPEN"
},
{
"pentestId": "4c11d176-2ec5-4ed9-9c8a-c1edd33b262c",
"status": "OPEN"
},
{
"pentestId": "b9a6f4ba-62e6-442b-a274-b3ffe209d248",
"status": "OPEN"
},
{
"pentestId": "705e28a2-b0a4-4b8c-9922-10c5c67faf65",
"status": "OPEN"
},
{
"pentestId": "4c59259d-4a24-43ef-8738-fe214e0b0673",
"status": "OPEN"
},
{
"pentestId": "a7ab3344-db7d-495a-8e55-dd572ea7c5e0",
"status": "OPEN"
},
{
"pentestId": "195e7f58-a7b2-4571-9c66-1e91a0dfca28",
"status": "OPEN"
},
{
"pentestId": "543a9768-4e5c-4c70-9aae-977afa542afa",
"status": "OPEN"
},
{
"pentestId": "a17516de-e92a-43b9-a415-203dce48fb0e",
"status": "OPEN"
"pentestId": "0f47ac3b-d19a-4115-9ddf-dc9b2f11abae",
"status": "PAUSED"
}
],
"createdBy": "3c4ae87f-0d56-4634-a824-b4883c403c8a"
"createdBy": "ce650edd-aebc-4478-9e17-40545ff66280"
},
"_class": "com.securityc4po.api.project.ProjectEntity"
},{
"_id": {
"$oid": "62f3c5317acde34f740ba738"
"$oid": "6405e92813ae975803a09905"
},
"lastModified": {
"$date": {
"$numberLong": "1660142897912"
"$numberLong": "1678108968564"
}
},
"data": {
"_id": "42b8c0df-b70e-4526-8ed0-7c022195fe85",
"_id": "d6e83738-4251-44ac-ad40-21b360780c98",
"client": "Allsafe",
"title": "CashMyData (iOS)",
"createdAt": "2022-08-10T14:48:17.912592Z",
"createdAt": "2023-03-06T13:22:48.564351Z",
"tester": "Elliot",
"projectPentests": [],
"createdBy": "6740ad72-8f42-486a-bcf3-e057e6afb0de"
},
"_class": "com.securityc4po.api.project.ProjectEntity"
},{
"_id": {
"$oid": "62ff7534ac2b4d14d86215c4"
},
"lastModified": {
"$date": {
"$numberLong": "1660908852340"
}
},
"data": {
"_id": "195809ed-9722-4ad5-a84b-0099a9a01652",
"client": "Novatec",
"title": "log4j pentest",
"createdAt": "2022-08-19T11:34:12.339990Z",
"tester": "Stipe",
"projectPentests": [],
"createdBy": "7fe49c8d-fee3-47e0-9224-94e0ac7436c6"
"createdBy": "5f104d76-bd8d-4258-852a-d000c7f0666d"
},
"_class": "com.securityc4po.api.project.ProjectEntity"
}]

View File

@ -106,7 +106,7 @@
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzY5ODY0NTgsImlhdCI6MTY3Njk4NjE1OCwianRpIjoiMzk2NjcwM2UtMTk4My00MWU4LTkyNjAtYjhmNDExOWRmYzYwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiIxZDBmMTk4YS03MTg5LTQ4ZmItYWQ0Ny00NTllNjVkOWU3ZjciLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiMWQwZjE5OGEtNzE4OS00OGZiLWFkNDctNDU5ZTY1ZDllN2Y3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.m1aOgFK_8ADYS6EdPLWT_wQNTsptSM9XipQQcttSd1lZKdZu6FgFVRSsW39fdgVLGurKxaglGoNgsywGDeomPS7hJuowzmYIEoDZr13MXCBqX9-YAsEDbwvrGcUI4jVXwKl8E-rTLpl3c5Ckj4tbDeUD3EuLk7yTYbkUnijqwsFlZpwJ_gbjZAfNiZCZpJlvh95cQKvFBbyzP7sfxkYikzpxY-1UHSUpoHBxJaOcJ6hoh-PIQVUw-8mvuoOyd5dMmavl5njvijr716_2loj6B6YHLueHIGlenI5Aob9DEOcL17SXivvcEyM5xTKyfqx3Jt1XY7jQ8mOIT4_iqxTc_w",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzgyNjg2NTksImlhdCI6MTY3ODI2ODM1OSwianRpIjoiNTkzODgzZWYtZDZkZS00MGJjLWE3MGQtMTU5MDE3YzBlY2Q4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiIxNGUwNzYwNy02YTUyLTRmZTItODA0MC00Zjc4NDc4ZjI3ZGQiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiMTRlMDc2MDctNmE1Mi00ZmUyLTgwNDAtNGY3ODQ3OGYyN2RkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.gHG1z-amLiv4mv6DujwEMPIjfn8ojROSmypOG4_A2GvF6ekmSi36WQO8I2IjPJZ4XB1amKgeG5pAI6I4AYU8DwYIOsDrqKgPSitDEyY9lCjUtxFHZpmemkQljdJw3UNjDKgDuyYpu-lPcapcEwN75BeSxmnkNVQ7ri3dLHf5AhLlQExlVD0ts4ux4kQ4X82cn_iPGRa869jZEWbVHKnhZaHufsA0444qgw45mBJu1ycG7_4VzKmoeprSV5RSH_DxMMZaDCUb0_Xf1QZ58VVEpjW1WRNvvAip5uVNnyduQe2SJ7TlZqNy5DzDyzBko8d4LnWjE_-qdo4JmwztApkOww",
"type": "string"
},
{
@ -124,7 +124,7 @@
}
],
"url": {
"raw": "http://localhost:8444/reports/5a4f126c-9471-43b8-80b9-6eb02b7c35d0/pdf",
"raw": "http://localhost:8444/reports/575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2/pdf",
"protocol": "http",
"host": [
"localhost"
@ -132,7 +132,7 @@
"port": "8444",
"path": [
"reports",
"5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
"575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2",
"pdf"
]
}

View File

@ -4,5 +4,7 @@ data class Comment (
val id: String,
val title: String,
val description: String,
val relatedFindings: List<String>? = emptyList()
val relatedFindings: List<String>? = emptyList(),
// List of attachment id's for file upload
val attachments: List<String>? = emptyList()
)

View File

@ -8,7 +8,9 @@ data class Finding (
val impact: String,
val affectedUrls: List<String>? = emptyList(),
val reproduction: String,
val mitigation: String?
val mitigation: String?,
// List of attachment id's for file upload
val attachments: List<String>? = emptyList()
)
enum class Severity {

View File

@ -3,7 +3,7 @@ package com.securityc4po.reporting.remote.model.api
enum class PentestStatus {
NOT_STARTED,
DISABLED,
OPEN,
PAUSED,
IN_PROGRESS,
COMPLETED
}

View File

@ -12,7 +12,7 @@ data class Project(
val title: String,
val createdAt: String,
val tester: String,
val summary: String? = null,
val summary: String? = "",
var projectPentests: List<ProjectPentest>? = emptyList(),
val createdBy: String
)

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.20.0.final using JasperReports Library version 6.20.0-2bc7ab61c56f459e8176eb05c7705e145cd400ad -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="CommentsSubreport" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="2d64b016-bc6c-4109-8b3a-f9f818c7397d">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="ProjectReportJasperData Template JSON Adapter"/>
<queryString language="json">
<![CDATA[projectReportData.projectPentestReport.comments]]>
</queryString>
<field name="id" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="id"/>
<fieldDescription><![CDATA[id]]></fieldDescription>
</field>
<field name="title" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="title"/>
<fieldDescription><![CDATA[title]]></fieldDescription>
</field>
<field name="description" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="description"/>
<fieldDescription><![CDATA[description]]></fieldDescription>
</field>
<field name="attachments" class="java.util.ArrayList"/>
<group name="id">
<groupExpression><![CDATA[$F{id}]]></groupExpression>
</group>
<group name="title">
<groupExpression><![CDATA[$F{title}]]></groupExpression>
</group>
<group name="description">
<groupExpression><![CDATA[$F{description}]]></groupExpression>
</group>
<group name="relatedFindings">
<groupExpression><![CDATA[$F{attachments}]]></groupExpression>
</group>
<background>
<band splitType="Stretch"/>
</background>
<detail>
<band height="280" splitType="Stretch">
<staticText>
<reportElement x="0" y="50" width="100" height="19" forecolor="#232B44" uuid="27e653a4-f25c-4e14-a2dd-98639beaa958"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Title:]]></text>
</staticText>
<staticText>
<reportElement positionType="Float" x="0" y="120" width="100" height="20" forecolor="#232B44" uuid="4f0f0eaf-177b-4af7-99d8-a46b0faa9357"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Description:]]></text>
</staticText>
<textField textAdjust="StretchHeight">
<reportElement x="0" y="70" width="530" height="30" uuid="62ff1e8e-7987-4cd0-a79e-536859418d32"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{title}]]></textFieldExpression>
</textField>
<textField textAdjust="StretchHeight">
<reportElement positionType="Float" x="0" y="140" width="530" height="40" uuid="7e9aa6ea-d9f7-4879-bbca-753e3d654996"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{description}]]></textFieldExpression>
</textField>
<rectangle>
<reportElement x="0" y="-12" width="575" height="33" forecolor="#35A4FE" backcolor="#35A4FE" uuid="6b56d210-7a17-4ac9-bab8-b27008e039e4"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
<ellipse>
<reportElement x="-19" y="-12" width="40" height="33" forecolor="#35A4FE" backcolor="#35A4FE" uuid="fa27cf34-9e98-4d59-9cd2-9f436f0b6c25"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</ellipse>
<textField textAdjust="StretchHeight">
<reportElement x="80" y="-12" width="480" height="32" forecolor="#FFFFFF" uuid="f3b19575-bca9-450c-93bf-f9abbfe15bde"/>
<textElement verticalAlignment="Middle">
<font size="14" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{id}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="-12" width="80" height="32" forecolor="#FFFFFF" uuid="06c9b872-ccec-44a6-854f-ef7b5dcedec9"/>
<textElement verticalAlignment="Middle">
<font size="14" isBold="true"/>
</textElement>
<text><![CDATA[Comment:]]></text>
</staticText>
</band>
</detail>
</jasperReport>

View File

@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.20.0.final using JasperReports Library version 6.20.0-2bc7ab61c56f459e8176eb05c7705e145cd400ad -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="FindingsSubreport" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="e998075b-1539-4178-93ab-d58a24d7a1a1">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="ProjectReportJasperData Template JSON Adapter"/>
<style name="SeverityStyles">
<conditionalStyle>
<conditionExpression><![CDATA[$F{severity}.equals("LOW")]]></conditionExpression>
<style forecolor="#01D68F" backcolor="#01D68F"/>
</conditionalStyle>
<conditionalStyle>
<conditionExpression><![CDATA[$F{severity}.equals("MEDIUM")]]></conditionExpression>
<style forecolor="#35A4FE" backcolor="#35A4FE"/>
</conditionalStyle>
<conditionalStyle>
<conditionExpression><![CDATA[$F{severity}.equals("HIGH")]]></conditionExpression>
<style forecolor="#FFAB00" backcolor="#FFAB00"/>
</conditionalStyle>
<conditionalStyle>
<conditionExpression><![CDATA[$F{severity}.equals("CRITICAL")]]></conditionExpression>
<style forecolor="#FF3D70" backcolor="#FF3D70"/>
</conditionalStyle>
</style>
<style name="URL">
<conditionalStyle>
<conditionExpression><![CDATA[$F{affectedUrls}.size() > 0]]></conditionExpression>
<style forecolor="#3267FE" backcolor="#3267FE" isUnderline="true"/>
</conditionalStyle>
</style>
<subDataset name="AffectedUrlsList" uuid="847d946c-60fb-4c27-bfef-875d197d89f9">
<queryString>
<![CDATA[]]>
</queryString>
<field name="_THIS" class="java.lang.String"/>
</subDataset>
<queryString language="json">
<![CDATA[projectReportData.projectPentestReport.findings]]>
</queryString>
<field name="id" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="id"/>
<fieldDescription><![CDATA[id]]></fieldDescription>
</field>
<field name="severity" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="severity"/>
<fieldDescription><![CDATA[severity]]></fieldDescription>
</field>
<field name="title" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="title"/>
<fieldDescription><![CDATA[title]]></fieldDescription>
</field>
<field name="description" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="description"/>
<fieldDescription><![CDATA[description]]></fieldDescription>
</field>
<field name="impact" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="impact"/>
<fieldDescription><![CDATA[impact]]></fieldDescription>
</field>
<field name="affectedUrls" class="java.util.ArrayList">
<property name="net.sf.jasperreports.json.field.expression" value="affectedUrls"/>
<fieldDescription><![CDATA[affectedUrls]]></fieldDescription>
</field>
<field name="reproduction" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="reproduction"/>
<fieldDescription><![CDATA[reproduction]]></fieldDescription>
</field>
<field name="mitigation" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="mitigation"/>
<fieldDescription><![CDATA[mitigation]]></fieldDescription>
</field>
<field name="attachments" class="java.util.ArrayList"/>
<background>
<band splitType="Stretch"/>
</background>
<detail>
<band height="600" splitType="Stretch">
<ellipse>
<reportElement key="" style="SeverityStyles" mode="Opaque" x="445" y="45" width="30" height="30" uuid="5dfe3093-6943-446e-8126-d7230d895b0c"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</ellipse>
<textField textAdjust="StretchHeight">
<reportElement positionType="Float" x="0" y="140" width="530" height="40" uuid="2882cfe1-475e-4ac3-a987-38cb01a462f4"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{description}]]></textFieldExpression>
</textField>
<staticText>
<reportElement positionType="Float" x="0" y="120" width="100" height="20" forecolor="#232B44" uuid="cb620215-7d9a-422e-a286-d1374c29469a"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Description:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="50" width="100" height="20" forecolor="#232B44" uuid="a8c19344-4ce8-4d15-919b-4cbb004aba79"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Title:]]></text>
</staticText>
<textField textAdjust="StretchHeight">
<reportElement x="0" y="70" width="430" height="30" uuid="1ac22e9e-2fe9-4fc7-94ca-5e30db7aa74f"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{title}]]></textFieldExpression>
</textField>
<staticText>
<reportElement positionType="Float" x="0" y="200" width="100" height="20" forecolor="#232B44" uuid="3d056116-a999-4c17-b3b0-94b26e69c9c6"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Impact:]]></text>
</staticText>
<textField textAdjust="StretchHeight">
<reportElement positionType="Float" x="0" y="220" width="530" height="40" uuid="d026c3e2-1431-4010-b0d7-bf07cb339ed7"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{impact}]]></textFieldExpression>
</textField>
<textField textAdjust="StretchHeight">
<reportElement positionType="Float" x="0" y="300" width="530" height="40" uuid="8d1f42e3-a821-475e-b862-126018ae2e95"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{reproduction}]]></textFieldExpression>
</textField>
<staticText>
<reportElement positionType="Float" x="0" y="280" width="180" height="20" forecolor="#232B44" uuid="5d0ab6e7-dd18-4da2-848b-95abc47936e8"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Reproduction Steps:]]></text>
</staticText>
<textField textAdjust="StretchHeight">
<reportElement positionType="Float" x="0" y="380" width="530" height="40" uuid="09dea210-c47a-486e-b461-c6fb7c27bb39"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[(($F{mitigation}.length() == 0) ? "No mitigation to avoid, minimize or compensate the finding found or needed." : $F{mitigation})]]></textFieldExpression>
</textField>
<staticText>
<reportElement positionType="Float" x="0" y="360" width="180" height="20" forecolor="#232B44" uuid="0aaec1e4-9ac0-48de-96ef-0ac5ad1e9be0"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Mitigation:]]></text>
</staticText>
<rectangle>
<reportElement x="0" y="-12" width="575" height="33" forecolor="#FF3D70" backcolor="#FF3D70" uuid="cb580eb9-9492-4fb6-8eb7-304e50612c9f"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
<ellipse>
<reportElement x="-19" y="-12" width="40" height="33" forecolor="#FF3D70" backcolor="#FF3D70" uuid="b7284920-9281-446c-a04f-0e5c1b433cd5"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</ellipse>
<textField textAdjust="StretchHeight">
<reportElement x="80" y="-12" width="480" height="32" forecolor="#FFFFFF" uuid="fd9a8e55-90df-468c-920e-f4154c0c0476"/>
<textElement verticalAlignment="Middle">
<font size="14" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{id}]]></textFieldExpression>
</textField>
<rectangle>
<reportElement style="SeverityStyles" mode="Opaque" x="460" y="45" width="70" height="30" uuid="6b40b4f8-4f4a-4e2e-9eb8-dcb32646353d"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
<ellipse>
<reportElement style="SeverityStyles" mode="Opaque" x="515" y="45" width="30" height="30" uuid="a9c34649-525e-40e7-b9c6-4132773009d3"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</ellipse>
<textField textAdjust="StretchHeight">
<reportElement x="460" y="50" width="70" height="20" forecolor="#FEFEFF" uuid="c2c6ef50-1470-41aa-80a1-44db4bbc1b64"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[$F{severity}]]></textFieldExpression>
</textField>
<textField textAdjust="StretchHeight">
<reportElement style="URL" positionType="Float" x="0" y="460" width="530" height="40" uuid="9d6be22b-6f8c-41bf-ab85-3d5901979a06"/>
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA[(($F{affectedUrls}.size() == 0) ? "No specific URL's affected." : $F{affectedUrls}.toString().substring(1, $F{affectedUrls}.toString().length() - 1))]]></textFieldExpression>
</textField>
<staticText>
<reportElement positionType="Float" x="0" y="440" width="180" height="20" forecolor="#232B44" uuid="003646c0-fc53-4add-84d2-b3816d52789d"/>
<textElement>
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Affected URL's:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="-12" width="80" height="32" forecolor="#FFFFFF" uuid="c6ec29f3-3687-4098-80b5-30fd33a7989a"/>
<textElement verticalAlignment="Middle">
<font size="14" isBold="true"/>
</textElement>
<text><![CDATA[Finding:]]></text>
</staticText>
</band>
</detail>
</jasperReport>

View File

@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.20.0.final using JasperReports Library version 6.20.0-2bc7ab61c56f459e8176eb05c7705e145cd400ad -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="SeverityRatingTableSubreport" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="6a3399a9-b8f7-4c58-a21d-8578e392dc84">
<style name="Table_TH" mode="Opaque" backcolor="#232B44">
<box>
<pen lineWidth="0.5" lineColor="#232B44"/>
<topPen lineWidth="0.5" lineColor="#232B44"/>
<leftPen lineWidth="0.5" lineColor="#232B44"/>
<bottomPen lineWidth="0.5" lineColor="#232B44"/>
<rightPen lineWidth="0.5" lineColor="#232B44"/>
</box>
</style>
<style name="Table_CH" mode="Opaque" backcolor="#FEFEFF">
<box>
<pen lineWidth="0.5" lineColor="#232B44"/>
<topPen lineWidth="0.5" lineColor="#232B44"/>
<leftPen lineWidth="0.5" lineColor="#232B44"/>
<bottomPen lineWidth="0.5" lineColor="#232B44"/>
<rightPen lineWidth="0.5" lineColor="#232B44"/>
</box>
</style>
<style name="Table_TD" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#232B44"/>
<topPen lineWidth="0.5" lineColor="#232B44"/>
<leftPen lineWidth="0.5" lineColor="#232B44"/>
<bottomPen lineWidth="0.5" lineColor="#232B44"/>
<rightPen lineWidth="0.5" lineColor="#232B44"/>
</box>
</style>
<subDataset name="SeverityRatingDefinition" uuid="adea3afd-3a9c-4386-b540-562fde7a2419">
<queryString>
<![CDATA[]]>
</queryString>
</subDataset>
<queryString>
<![CDATA[]]>
</queryString>
<background>
<band splitType="Stretch"/>
</background>
<detail>
<band height="620" splitType="Stretch">
<componentElement>
<reportElement positionType="Float" x="-20" y="-20" width="560" height="620" uuid="10d88177-713d-4c60-9266-5afb31684f19">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.components.autoresize.proportional" value="true"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd" whenNoDataType="AllSectionsNoDetail">
<datasetRun subDataset="SeverityRatingDefinition" uuid="297fe663-872d-43d4-a947-9106a126a556">
<dataSourceExpression><![CDATA[new net.sf.jasperreports.engine.JREmptyDataSource()]]></dataSourceExpression>
</datasetRun>
<jr:column width="112" uuid="dd4a463a-7807-42ae-9419-7a17135a2a58">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<jr:tableHeader style="Table_TH" height="30" rowSpan="1">
<staticText>
<reportElement x="0" y="0" width="112" height="30" forecolor="#FFFFFF" uuid="140fe665-f028-4805-a55f-4b4190be3140"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="12" isBold="true"/>
</textElement>
<text><![CDATA[Rating]]></text>
</staticText>
</jr:tableHeader>
<jr:tableFooter style="Table_CH" height="200" rowSpan="1">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="112" height="200" forecolor="#03C886" uuid="bb091668-8003-45fc-a4aa-e54a96f7fa87"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="16" isBold="true"/>
</textElement>
<text><![CDATA[Low]]></text>
</staticText>
</jr:tableFooter>
<jr:columnHeader style="Table_CH" height="90" rowSpan="1">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="112" height="90" forecolor="#FF3D70" uuid="d742063f-e537-400f-bb31-bb55fa9f20fd"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="16" isBold="true"/>
</textElement>
<text><![CDATA[Critical]]></text>
</staticText>
</jr:columnHeader>
<jr:columnFooter style="Table_CH" height="200" rowSpan="1">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="112" height="200" forecolor="#35A4FE" uuid="a8b12a40-8144-4226-870f-0e0a1e5ea752"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="16" isBold="true"/>
</textElement>
<text><![CDATA[Medium]]></text>
</staticText>
</jr:columnFooter>
<jr:detailCell style="Table_TD" height="100">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="112" height="100" forecolor="#FFAB00" uuid="4a1dd484-384f-45ca-a41a-8d1d2b5fb1e6"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="16" isBold="true"/>
</textElement>
<text><![CDATA[High]]></text>
</staticText>
</jr:detailCell>
</jr:column>
<jr:column width="448" uuid="ab851b91-c506-48d4-a4e6-77292e4c42ef">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<jr:tableHeader style="Table_TH" height="30" rowSpan="1">
<staticText>
<reportElement x="0" y="0" width="448" height="30" forecolor="#FFFFFF" uuid="7c3f036d-ed06-4aa2-bba2-f45c7eb91316">
<property name="com.jaspersoft.studio.unit.leftIndent" value="px"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font size="12" isBold="true"/>
<paragraph leftIndent="2"/>
</textElement>
<text><![CDATA[Severity Rating Definition]]></text>
</staticText>
</jr:tableHeader>
<jr:tableFooter style="Table_CH" height="200" rowSpan="1">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="448" height="200" uuid="9e066b11-0381-46ce-a05b-99b2293ce66c">
<property name="com.jaspersoft.studio.unit.leftIndent" value="px"/>
</reportElement>
<textElement>
<font size="12"/>
<paragraph leftIndent="2"/>
</textElement>
<text><![CDATA[Exploitation of the technical or procedural vulnerability will cause minimal impact to operations. The
Confidentiality, Integrity and Availability (CIA) of sensitive information are not at risk of compromise.
Exploitation of the vulnerability may cause slight financial loss or public embarrassment. The threat exposure is
moderate-to-low. Security controls are in place to contain the severity of impact if the vulnerability were
exploited, such that further political, financial, or legal damage will not occur.
- OR -
The vulnerability is such that it would otherwise be considered Medium Risk, but the threat exposure is so limited
that the likelihood of occurrence is minimal.]]></text>
</staticText>
</jr:tableFooter>
<jr:columnHeader style="Table_CH" height="90" rowSpan="1">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="448" height="90" uuid="628591b9-4f90-45cf-896c-e38355ed9de8">
<property name="com.jaspersoft.studio.unit.firstLineIndent" value="pixel"/>
<property name="com.jaspersoft.studio.unit.leftIndent" value="px"/>
</reportElement>
<textElement textAlignment="Left">
<font size="12"/>
<paragraph lineSpacingSize="1.0" leftIndent="2"/>
</textElement>
<text><![CDATA[Exploitation of the technical or procedural vulnerability will cause substantial harm. Significant political,
financial, and/or legal damage is likely to result. The threat exposure is critical, and a publicly available mechanism exists to exploit the vulnerability. Security controls are not effectively implemented to reduce the severity of impact if the vulnerability were exploited.]]></text>
</staticText>
</jr:columnHeader>
<jr:columnFooter style="Table_CH" height="200" rowSpan="1">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="448" height="200" uuid="b9e7a6f9-946f-4824-8a9e-24bc3279589a">
<property name="com.jaspersoft.studio.unit.leftIndent" value="px"/>
</reportElement>
<textElement>
<font size="12"/>
<paragraph leftIndent="2"/>
</textElement>
<text><![CDATA[Exploitation of the technical or procedural vulnerability will significantly impact the confidentiality, integrity,
and/or availability of the system, application, or data. Exploitation of the vulnerability may cause moderate
financial loss or public embarrassment. The threat exposure is moderate-to-high, thereby increasing the
likelihood of occurrence. Security controls are in place to contain the severity of impact if the vulnerability were
exploited, such that further political, financial, or legal damage will not occur.
- OR -
The vulnerability is such that it would otherwise be considered High Risk, but the threat exposure is so limited
that the likelihood of occurrence is minimal.]]></text>
</staticText>
</jr:columnFooter>
<jr:detailCell style="Table_TD" height="100">
<staticText>
<reportElement positionType="Float" stretchType="ContainerHeight" x="0" y="0" width="448" height="100" uuid="8a292592-81b2-49a1-bfda-e9540ecb3aa9">
<property name="com.jaspersoft.studio.unit.leftIndent" value="px"/>
</reportElement>
<textElement>
<font size="12"/>
<paragraph leftIndent="2"/>
</textElement>
<text><![CDATA[Exploitation of the technical or procedural vulnerability will cause substantial harm. Significant political,
financial, and/or legal damage is likely to result. The threat exposure is high, thereby increasing the likelihood of
occurrence. Security controls are not effectively implemented to reduce the severity of impact if the vulnerability
were exploited.]]></text>
</staticText>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
</detail>
</jasperReport>

View File

@ -14,7 +14,8 @@
"https://akveo.github.io/nebular/docs/components/progress-bar/examples#nbprogressbarcomponent"
],
"reproduction": "Step 1: Test",
"mitigation": "Test Mitigation"
"mitigation": "Test Mitigation",
"attachments": []
},
{
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -24,7 +25,8 @@
"impact": "Medium",
"affectedUrls": [],
"reproduction": "Medium",
"mitigation": ""
"mitigation": "",
"attachments": []
}
],
"comments": [
@ -32,7 +34,7 @@
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment",
"description": "No related findings",
"relatedFindings": []
"attachments": []
}
],
"status": "COMPLETED"
@ -50,7 +52,8 @@
"impact": "High",
"affectedUrls": [],
"reproduction": "High",
"mitigation": ""
"mitigation": "",
"attachments": []
}
],
"comments": [],

View File

@ -22,7 +22,8 @@
"https://www.google.de/"
],
"reproduction": "Step 1: Test",
"mitigation": ""
"mitigation": "",
"attachments": []
},
{
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -32,7 +33,8 @@
"impact": "Medium Impact",
"affectedUrls": [],
"reproduction": "Step 1: Medium",
"mitigation": "Fusce sit amet massa efficitur, egestas neque vitae, elementum orci. Nullam accumsan nunc id risus volutpat, quis sagittis lorem sollicitudin. Nam finibus justo non arcu vulputate dignissim. Etiam libero felis, dignissim non feugiat at, ornare non elit. Sed hendrerit risus id magna porttitor interdum."
"mitigation": "Fusce sit amet massa efficitur, egestas neque vitae, elementum orci. Nullam accumsan nunc id risus volutpat, quis sagittis lorem sollicitudin. Nam finibus justo non arcu vulputate dignissim. Etiam libero felis, dignissim non feugiat at, ornare non elit. Sed hendrerit risus id magna porttitor interdum.",
"attachments": []
},
{
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -42,7 +44,8 @@
"impact": "High Impact",
"affectedUrls": [],
"reproduction": "Step 1: High",
"mitigation": ""
"mitigation": "",
"attachments": []
},
{
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -54,7 +57,8 @@
"https://community.jaspersoft.com/system/files/restricted-docs/jaspersoft-studio-user-guide.pdf"
],
"reproduction": "Step 1: Critical",
"mitigation": "Fusce sit amet massa efficitur, egestas neque vitae, elementum orci. Nullam accumsan nunc id risus volutpat, quis sagittis lorem sollicitudin. Nam finibus justo non arcu vulputate dignissim. Etiam libero felis, dignissim non feugiat at, ornare non elit. Sed hendrerit risus id magna porttitor interdum."
"mitigation": "Fusce sit amet massa efficitur, egestas neque vitae, elementum orci. Nullam accumsan nunc id risus volutpat, quis sagittis lorem sollicitudin. Nam finibus justo non arcu vulputate dignissim. Etiam libero felis, dignissim non feugiat at, ornare non elit. Sed hendrerit risus id magna porttitor interdum.",
"attachments": []
}
],
"comments": [
@ -62,15 +66,13 @@
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment",
"description": "Not related to findings",
"relatedFindings": [
"58f63b4e-97fb-4fe8-8527-7996896089d2"
]
"attachments": []
},
{
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment",
"description": "Test Decription",
"relatedFindings": []
"attachments": []
}
],
"status": "COMPLETED"
@ -88,7 +90,8 @@
"impact": "High",
"affectedUrls": [],
"reproduction": "High",
"mitigation": ""
"mitigation": "",
"attachments": []
}
],
"comments": [],

View File

@ -23,7 +23,8 @@
"https://akveo.github.io/nebular/docs/components/progress-bar/examples#nbprogressbarcomponent"
],
"reproduction": "Step 1: Test",
"mitigation": "Test Mitigation"
"mitigation": "Test Mitigation",
"attachments": []
},
{
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -33,7 +34,8 @@
"impact": "Medium",
"affectedUrls": [],
"reproduction": "Medium",
"mitigation": ""
"mitigation": "",
"attachments": []
}
],
"comments": [
@ -41,7 +43,7 @@
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment",
"description": "No related findings",
"relatedFindings": []
"attachments": []
}
],
"status": "COMPLETED"
@ -59,7 +61,8 @@
"impact": "High",
"affectedUrls": [],
"reproduction": "High",
"mitigation": ""
"mitigation": "",
"attachments": []
}
],
"comments": [],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 224 KiB

File diff suppressed because one or more lines are too long