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==" "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
}, },
"@fortawesome/fontawesome-svg-core": { "@fortawesome/fontawesome-svg-core": {
"version": "1.2.36", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", "integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
"requires": { "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": { "@fortawesome/free-regular-svg-icons": {
"version": "5.15.4", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", "integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
"requires": { "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": { "@fortawesome/free-solid-svg-icons": {
"version": "5.15.4", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", "integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
"requires": { "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": { "@istanbuljs/load-nyc-config": {

View File

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

View File

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

View File

@ -5,17 +5,8 @@
class="comment-cell" class="comment-cell"
fragment="{{comment.data['commentId']}}"> fragment="{{comment.data['commentId']}}">
</tr> </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 --> <!-- Title -->
<ng-container [nbTreeGridColumnDef]="columns[1]"> <ng-container [nbTreeGridColumnDef]="columns[0]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef> <th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.title' | translate }} {{ 'comment.title' | translate }}
</th> </th>
@ -24,7 +15,7 @@
</td> </td>
</ng-container> </ng-container>
<!-- Description --> <!-- Description -->
<ng-container [nbTreeGridColumnDef]="columns[2]"> <ng-container [nbTreeGridColumnDef]="columns[1]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef> <th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.description' | translate }} {{ 'comment.description' | translate }}
</th> </th>
@ -32,29 +23,15 @@
{{ comment.data['description'] }} {{ comment.data['description'] }}
</td> </td>
</ng-container> </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 --> <!-- Actions -->
<ng-container [nbTreeGridColumnDef]="columns[4]"> <ng-container [nbTreeGridColumnDef]="columns[2]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions"> <th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">
<button nbButton hero <button nbButton hero
status="info" status="info"
size="small" size="small"
shape="round" shape="round"
class="add-comment-button" class="add-comment-button"
[disabled]="pentestInfo$.getValue().status === notStartedStatus" [disabled]="pentestInfo$.getValue().status !== inProgressStatus"
(click)="onClickAddComment()"> (click)="onClickAddComment()">
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon> <fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
{{'comment.add' | translate}} {{'comment.add' | translate}}

View File

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

View File

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

View File

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

View File

@ -13,42 +13,23 @@
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4> <h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
<div class="pentest-status-container"> <div class="pentest-status-container" fxLayout="row" fxLayoutGap="2.5rem" fxLayoutAlign="end center">
<!--ToDo: Add changing dialog here--> <!-- Pentest Timer-->
<!--<app-status-tag [currentStatus]="pentest$.getValue().status"></app-status-tag>--> <div class="timer-component">
<app-timer></app-timer>
<!-- 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> </div>
<div *ngIf="!pentest$.getValue().id; else updatePentest"> <!-- Complete Pentest -->
<div>
<button nbButton <button nbButton
class="save-pentest-button" class="save-pentest-button"
status="primary" status="success"
[disabled]="!pentestStatusChanged()" [disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
title="{{ 'global.action.save' | translate }}" title="{{ 'global.action.save' | translate }}"
(click)="onClickSavePentest()"> (click)="onClickCompletePentestAndRouteBack()">
<span class="exit-element-text"> {{ 'global.action.save' | translate }} </span> <fa-icon [icon]="fa.faSquare"></fa-icon>
<span class="action-element-text"> {{ 'global.action.complete' | translate }} </span>
</button> </button>
</div> </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>
</div> </div>

View File

@ -11,10 +11,18 @@
} }
.pentest-status-container { .pentest-status-container {
display: flex; // display: flex;
align-content: flex-end; // align-content: flex-end;
// margin-right: 0.5rem;
// height: 5%; .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 { .pentest-status-dialog {
margin: 1rem 2.25rem 1rem 0; margin: 1rem 2.25rem 1rem 0;
@ -41,6 +49,7 @@
} }
.save-pentest-button { .save-pentest-button {
// height: 1rem !important;
margin: 1rem 0 1rem 0; 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 * as FA from '@fortawesome/free-solid-svg-icons';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {Route} from '@shared/models/route.enum'; 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', templateUrl: './pentest-header.component.html',
styleUrls: ['./pentest-header.component.scss'] styleUrls: ['./pentest-header.component.scss']
}) })
export class PentestHeaderComponent implements OnInit { export class PentestHeaderComponent implements OnInit, OnDestroy {
// HTML only // HTML only
readonly fa = FA; readonly fa = FA;
@ -28,16 +28,21 @@ export class PentestHeaderComponent implements OnInit {
selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>(''); selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('');
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
// Pentest Timer Handler
currentTimeSpent = 0;
private initialTimeSpent: number;
// Pentest Status Handler // Pentest Status Handler
status = PentestStatus; status = PentestStatus;
currentStatus: PentestStatus = PentestStatus.NOT_STARTED; currentStatus: PentestStatus = PentestStatus.NOT_STARTED;
private initialPentestStatus: PentestStatus; private initialPentestStatus: PentestStatus;
// Status Text Translation Texts // Status Text Translation Texts
readonly statusTexts: Array<StatusText> = [ readonly statusTexts: Array<StatusText> = [
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'}, {value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
/* ToDo: Disabled not needed inside pentest */ /* ToDo: Disabled not needed inside pentest */
/*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/ /*{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.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'} {value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
]; ];
@ -51,7 +56,7 @@ export class PentestHeaderComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.store.select(ProjectState.project).pipe( this.store.selectOnce(ProjectState.project).pipe(
untilDestroyed(this) untilDestroyed(this)
).subscribe({ ).subscribe({
next: (selectedProject: Project) => { next: (selectedProject: Project) => {
@ -68,17 +73,20 @@ export class PentestHeaderComponent implements OnInit {
).subscribe({ ).subscribe({
next: (selectedPentest: Pentest) => { next: (selectedPentest: Pentest) => {
this.currentStatus = selectedPentest.status; this.currentStatus = selectedPentest.status;
this.initialPentestStatus = selectedPentest.status; this.currentTimeSpent = selectedPentest.timeSpent ? selectedPentest.timeSpent : 0;
this.pentest$.next(selectedPentest); this.pentest$.next(selectedPentest);
}, },
error: err => { error: err => {
console.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 { onClickRouteBack(): void {
// ToDo: Change to Objective Overview after routing is fixed // Route back to overview
this.router.navigate([Route.OBJECTIVE_OVERVIEW]) this.router.navigate([Route.OBJECTIVE_OVERVIEW])
.then( .then(
() => { () => {
@ -87,27 +95,18 @@ export class PentestHeaderComponent implements OnInit {
).finally(); ).finally();
} }
onClickSavePentest(): void { onClickCompletePentestAndRouteBack(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus}); // Update existing Pentest
this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentest$.getValue())) this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.COMPLETED, timeSpent: this.currentTimeSpent});
.subscribe({ this.updatePentest();
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);
}
});
} }
onClickUpdatePentest(): void { private updatePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue())) this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({ .subscribe({
next: (pentest: Pentest) => { next: (pentest: Pentest) => {
this.store.dispatch(new ChangePentest(pentest)); this.store.dispatch(new ChangePentest(pentest));
this.initialTimeSpent = pentest.timeSpent;
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS); this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
}, },
error: err => { error: err => {
@ -121,7 +120,7 @@ export class PentestHeaderComponent implements OnInit {
* @return true if initial pentest Status is different from current pentest status * @return true if initial pentest Status is different from current pentest status
*/ */
pentestStatusChanged(): boolean { pentestStatusChanged(): boolean {
if (this.initialPentestStatus !== this.currentStatus) { if (this.initialTimeSpent !== this.currentTimeSpent && this.currentTimeSpent !== 0) {
this.pentestChanged$.next(true); this.pentestChanged$.next(true);
} else { } else {
this.pentestChanged$.next(false); this.pentestChanged$.next(false);
@ -129,6 +128,15 @@ export class PentestHeaderComponent implements OnInit {
return this.pentestChanged$.getValue(); 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 * @return the correct nb-status for current pentest-status
*/ */
@ -139,7 +147,7 @@ export class PentestHeaderComponent implements OnInit {
pentestFillStatus = 'basic'; pentestFillStatus = 'basic';
break; break;
} }
case PentestStatus.OPEN: { case PentestStatus.PAUSED: {
pentestFillStatus = 'info'; pentestFillStatus = 'info';
break; break;
} }
@ -158,4 +166,12 @@ export class PentestHeaderComponent implements OnInit {
} }
return pentestFillStatus; 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 {FindingDialogModule} from '@shared/modules/finding-dialog/finding-dialog.module';
import {CommentDialogModule} from '@shared/modules/comment-dialog/comment-dialog.module'; import {CommentDialogModule} from '@shared/modules/comment-dialog/comment-dialog.module';
import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.module'; import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.module';
import {TimerModule} from '@shared/modules/timer/timer.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -49,6 +50,8 @@ import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.mo
FindingDialogModule, FindingDialogModule,
CommentDialogModule, CommentDialogModule,
FindigWidgetModule, FindigWidgetModule,
// Modules
TimerModule,
] ]
}) })
export class PentestModule { export class PentestModule {

View File

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

View File

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

View File

@ -4,16 +4,17 @@ export class Comment {
id?: string; id?: string;
title: string; title: string;
description?: string; description?: string;
relatedFindings?: Array<string>; // List of attachment id's for file upload
attachments?: Array<string>;
constructor(title: string, constructor(title: string,
description: string, description: string,
id?: string, id?: string,
relatedFindings?: Array<string>) { attachments?: Array<string>) {
this.id = id ? id : UUID(); this.id = id ? id : UUID();
this.title = title; this.title = title;
this.description = description; this.description = description;
this.relatedFindings = relatedFindings; this.attachments = attachments;
} }
} }
@ -21,7 +22,7 @@ export interface CommentEntry {
commentId: string; commentId: string;
title: string; title: string;
description: string; description: string;
relatedFindings: Array<string>; attachments: Array<string>;
kind?: string; kind?: string;
childEntries?: []; childEntries?: [];
expanded?: boolean; expanded?: boolean;
@ -34,7 +35,7 @@ export function transformCommentsToObjectiveEntries(findings: Comment[]): Commen
commentId: value.id, commentId: value.id,
title: value.title, title: value.title,
description: value.description, description: value.description,
relatedFindings: value.relatedFindings, attachments: value.attachments,
kind: 'cell', kind: 'cell',
childEntries: null, childEntries: null,
expanded: false expanded: false
@ -48,8 +49,7 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme
...comment, ...comment,
title: comment.title, title: comment.title,
description: comment.description, description: comment.description,
// Transforms related findings from RelatedFindingOption to list of finding ids attachments: comment.attachments,
relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.id) : [],
/* Remove Table Entry Object Properties */ /* Remove Table Entry Object Properties */
childEntries: undefined, childEntries: undefined,
kind: undefined, kind: undefined,
@ -62,10 +62,6 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme
export interface CommentDialogBody { export interface CommentDialogBody {
title: string; title: string;
description: 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>; affectedUrls?: Array<string>;
reproduction: string; reproduction: string;
mitigation?: string; mitigation?: string;
// List of attachment id's for file upload
attachments?: Array<string>;
constructor(title: string, constructor(title: string,
severity: Severity, severity: Severity,
@ -18,7 +20,8 @@ export class Finding {
reproduction: string, reproduction: string,
id?: string, id?: string,
affectedUrls?: Array<string>, affectedUrls?: Array<string>,
mitigation?: string) { mitigation?: string,
attachments?: Array<string>) {
this.id = id ? id : UUID(); this.id = id ? id : UUID();
this.severity = severity; this.severity = severity;
this.title = title; this.title = title;
@ -27,13 +30,15 @@ export class Finding {
this.affectedUrls = affectedUrls ? affectedUrls : null; this.affectedUrls = affectedUrls ? affectedUrls : null;
this.reproduction = reproduction; this.reproduction = reproduction;
this.mitigation = mitigation ? mitigation : null; this.mitigation = mitigation ? mitigation : null;
this.attachments = attachments ? attachments : null;
} }
} }
export interface FindingEntry { export interface FindingEntry {
findingId: string; findingId: string;
severity: Severity;
title: string; title: string;
severity: Severity;
description: string;
impact: string; impact: string;
kind?: string; kind?: string;
childEntries?: []; childEntries?: [];
@ -45,8 +50,9 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin
findings.forEach((value: Finding) => { findings.forEach((value: Finding) => {
findingEntries.push({ findingEntries.push({
findingId: value.id, findingId: value.id,
severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity,
title: value.title, title: value.title,
severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity,
description: value.description,
impact: value.impact, impact: value.impact,
kind: 'cell', kind: 'cell',
childEntries: null, childEntries: null,

View File

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

View File

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

View File

@ -50,25 +50,6 @@
</nb-form-field> </nb-form-field>
</div> </div>
</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> </form>
</nb-card-body> </nb-card-body>
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end"> <nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
.project-dialog { .project-dialog {
width: 34rem !important; width: 34rem !important;
height: 42.5rem; height: 43.5rem;
.project-dialog-header { .project-dialog-header {
height: 8vh; 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 {Project} from '@shared/models/project.model';
import {Category} from '@shared/models/category.model'; import {Category} from '@shared/models/category.model';
import {Pentest} from '@shared/models/pentest.model'; import {Pentest} from '@shared/models/pentest.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
export class InitProjectState { 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 { export class UpdatePentestFindings {
static readonly type = '[ProjectState] UpdatePentestFindings'; static readonly type = '[ProjectState] UpdatePentestFindings';

View File

@ -5,11 +5,14 @@ import {
ChangeCategory, ChangeCategory,
ChangePentest, ChangePentest,
ChangeProject, ChangeProject,
InitProjectState, UpdatePentestComments, InitProjectState,
UpdatePentestFindings UpdatePentestComments,
UpdatePentestFindings, UpdatePentestStatus,
UpdatePentestTime
} from '@shared/stores/project-state/project-state.actions'; } from '@shared/stores/project-state/project-state.actions';
import {Category} from '@shared/models/category.model'; import {Category} from '@shared/models/category.model';
import {Pentest} from '@shared/models/pentest.model'; import {Pentest} from '@shared/models/pentest.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
export const PROJECT_STATE_NAME = 'project'; 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) @Action(UpdatePentestFindings)
updatePentestFindings(ctx: StateContext<ProjectStateModel>, {findingId}: UpdatePentestFindings): void { updatePentestFindings(ctx: StateContext<ProjectStateModel>, {findingId}: UpdatePentestFindings): void {
const state = ctx.getState(); const state = ctx.getState();
@ -109,7 +146,7 @@ export class ProjectState {
...stateSelectedPentest, ...stateSelectedPentest,
findingIds: updatedFindingIds findingIds: updatedFindingIds
}; };
// path project state // patch project state
ctx.patchState({ ctx.patchState({
selectedPentest: stateSelectedPentest selectedPentest: stateSelectedPentest
}); });
@ -133,7 +170,7 @@ export class ProjectState {
...stateSelectedPentest, ...stateSelectedPentest,
commentIds: updatedCommentIds commentIds: updatedCommentIds
}; };
// path project state // patch project state
ctx.patchState({ ctx.patchState({
selectedPentest: stateSelectedPentest selectedPentest: stateSelectedPentest
}); });

View File

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

View File

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

View File

@ -550,7 +550,7 @@
"header": [], "header": [],
"body": { "body": {
"mode": "raw", "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": { "options": {
"raw": { "raw": {
"language": "json" "language": "json"
@ -700,7 +700,7 @@
"header": [], "header": [],
"body": { "body": {
"mode": "raw", "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": { "options": {
"raw": { "raw": {
"language": "json" "language": "json"

View File

@ -14,7 +14,8 @@ data class Pentest(
val refNumber: String, val refNumber: String,
val status: PentestStatus, val status: PentestStatus,
var findingIds: List<String> = emptyList(), 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 { fun buildPentest(body: PentestRequestBody, pentestEntity: PentestEntity): Pentest {
@ -25,7 +26,8 @@ fun buildPentest(body: PentestRequestBody, pentestEntity: PentestEntity): Pentes
refNumber = body.refNumber, refNumber = body.refNumber,
status = PentestStatus.valueOf(body.status), status = PentestStatus.valueOf(body.status),
findingIds = body.findingIds, findingIds = body.findingIds,
commentIds = body.commentIds commentIds = body.commentIds,
timeSpent = body.timeSpent
) )
} }
@ -49,7 +51,8 @@ fun Pentest.toPentestResponseBody(): ResponseBody {
"refNumber" to refNumber, "refNumber" to refNumber,
"status" to status, "status" to status,
"findingIds" to findingIds, "findingIds" to findingIds,
"commentIds" to commentIds "commentIds" to commentIds,
"timeSpent" to timeSpent
) )
} }
@ -81,7 +84,8 @@ data class PentestRequestBody(
val category: String, val category: String,
val status: String, val status: String,
val findingIds: List<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, refNumber = this.refNumber,
status = PentestStatus.valueOf(this.status), status = PentestStatus.valueOf(this.status),
findingIds = this.findingIds, 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.refNumber,
this.data.status, this.data.status,
this.data.findingIds, 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 { enum class PentestStatus {
NOT_STARTED, NOT_STARTED,
DISABLED, DISABLED,
OPEN, PAUSED,
IN_PROGRESS, IN_PROGRESS,
COMPLETED COMPLETED
} }

View File

@ -10,30 +10,28 @@ data class Comment (
val id: String = UUID.randomUUID().toString(), val id: String = UUID.randomUUID().toString(),
val title: String, val title: String,
val description: 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 { fun buildComment(body: CommentRequestBody, commentEntity: CommentEntity): Comment {
return Comment( return Comment(
id = commentEntity.data.id, id = commentEntity.data.id,
title = body.title, title = body.title,
description = body.description, description = body.description
relatedFindings = body.relatedFindings
) )
} }
data class CommentRequestBody( data class CommentRequestBody(
val title: String, val title: String,
val description: String, val description: String
val relatedFindings: List<String>? = emptyList()
) )
fun Comment.toCommentResponseBody(): ResponseBody { fun Comment.toCommentResponseBody(): ResponseBody {
return mapOf( return mapOf(
"id" to id, "id" to id,
"title" to title, "title" to title,
"description" to description, "description" to description
"relatedFindings" to relatedFindings
) )
} }
@ -60,7 +58,6 @@ fun CommentRequestBody.toComment(): Comment {
return Comment( return Comment(
id = UUID.randomUUID().toString(), id = UUID.randomUUID().toString(),
title = this.title, title = this.title,
description = this.description, description = this.description
relatedFindings = this.relatedFindings
) )
} }

View File

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

View File

@ -13,7 +13,9 @@ data class Finding (
val impact: String, val impact: String,
val affectedUrls: List<String>? = emptyList(), val affectedUrls: List<String>? = emptyList(),
val reproduction: String, 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 { fun buildFinding(body: FindingRequestBody, findingEntity: FindingEntity): Finding {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,357 +1,106 @@
[{ [{
"_id": { "_id": {
"$oid": "6372223efea5724fd22bae8a" "$oid": "6405d88b13ae975803a098fb"
}, },
"lastModified": { "lastModified": {
"$date": { "$date": {
"$numberLong": "1668424254533" "$numberLong": "1678104715816"
} }
}, },
"data": { "data": {
"_id": "ef31449d-71ec-4736-952f-8b20e53117d5", "_id": "a343150a-91c9-4564-9638-d0377eecc7c9",
"severity": "LOW", "severity": "LOW",
"title": "Test Title", "title": "Low Prio Finding",
"description": "Test Description", "description": "This is Low Prio.",
"impact": "Test Impact", "impact": "Impacts nothing.",
"affectedUrls": [ "affectedUrls": [],
"https://akveo.github.io/nebular/docs/components/progress-bar/examples#nbprogressbarcomponent" "reproduction": "Open App.",
], "mitigation": "",
"reproduction": "Step 1: Test", "attachments": []
"mitigation": "Test Mitigatin"
}, },
"_class": "com.securityc4po.api.finding.FindingEntity" "_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{ },{
"_id": { "_id": {
"$oid": "63725fa6e612626e2c6956ee" "$oid": "6405db8a13ae975803a098fe"
}, },
"lastModified": { "lastModified": {
"$date": { "$date": {
"$numberLong": "1668439974730" "$numberLong": "1678105482494"
} }
}, },
"data": { "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", "severity": "HIGH",
"title": "High Title", "title": "High Prio Finding",
"description": "High Description", "description": "High Prio Description",
"impact": "High Impact", "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": [], "affectedUrls": [],
"reproduction": "Medium", "reproduction": "1. Open App\n2. Hack",
"mitigation": "" "mitigation": "",
"attachments": []
}, },
"_class": "com.securityc4po.api.finding.FindingEntity" "_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{ },{
"_id": { "_id": {
"$oid": "6374c43be0136563b96187b9" "$oid": "6405dbcc13ae975803a09900"
}, },
"lastModified": { "lastModified": {
"$date": { "$date": {
"$numberLong": "1668596795003" "$numberLong": "1678105548815"
} }
}, },
"data": { "data": {
"_id": "72886128-b2d9-4a92-bbfe-b54373441321", "_id": "176f5d93-0fe3-40b1-8a25-f11a6f760148",
"severity": "CRITICAL", "severity": "CRITICAL",
"title": "Critical Issue", "title": "Critical Prio Finding",
"description": "Critical", "description": "Critical Description",
"impact": "Critical", "impact": "Critical Impact",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "Critical", "reproduction": "1. Open App\n2. Hack\n3. Break everything",
"mitigation": "" "mitigation": "",
"attachments": []
}, },
"_class": "com.securityc4po.api.finding.FindingEntity" "_class": "com.securityc4po.api.pentest.finding.FindingEntity"
},{ },{
"_id": { "_id": {
"$oid": "6374c488e0136563b96187ba" "$oid": "640854a01d5b385d85c60ba7"
}, },
"lastModified": { "lastModified": {
"$date": { "$date": {
"$numberLong": "1668596872152" "$numberLong": "1678267552968"
} }
}, },
"data": { "data": {
"_id": "4ddb84f6-068c-4319-a8ee-1000008bb75a", "_id": "1ffc2215-b8ae-43b7-bbb7-bfcfb414d534",
"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",
"severity": "LOW", "severity": "LOW",
"title": "Another Low One", "title": "Low Prio Title",
"description": "Low", "description": "Low Prio Description",
"impact": "Low", "impact": "Low Prio Impact",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "Low", "reproduction": "Do Nothing",
"mitigation": "" "mitigation": "",
"attachments": []
}, },
"_class": "com.securityc4po.api.finding.FindingEntity" "_class": "com.securityc4po.api.pentest.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"
}] }]

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -106,7 +106,7 @@
"bearer": [ "bearer": [
{ {
"key": "token", "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" "type": "string"
}, },
{ {
@ -124,7 +124,7 @@
} }
], ],
"url": { "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", "protocol": "http",
"host": [ "host": [
"localhost" "localhost"
@ -132,7 +132,7 @@
"port": "8444", "port": "8444",
"path": [ "path": [
"reports", "reports",
"5a4f126c-9471-43b8-80b9-6eb02b7c35d0", "575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2",
"pdf" "pdf"
] ]
} }

View File

@ -4,5 +4,7 @@ data class Comment (
val id: String, val id: String,
val title: String, val title: String,
val description: 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 impact: String,
val affectedUrls: List<String>? = emptyList(), val affectedUrls: List<String>? = emptyList(),
val reproduction: String, 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 { enum class Severity {

View File

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

View File

@ -12,7 +12,7 @@ data class Project(
val title: String, val title: String,
val createdAt: String, val createdAt: String,
val tester: String, val tester: String,
val summary: String? = null, val summary: String? = "",
var projectPentests: List<ProjectPentest>? = emptyList(), var projectPentests: List<ProjectPentest>? = emptyList(),
val createdBy: String 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" "https://akveo.github.io/nebular/docs/components/progress-bar/examples#nbprogressbarcomponent"
], ],
"reproduction": "Step 1: Test", "reproduction": "Step 1: Test",
"mitigation": "Test Mitigation" "mitigation": "Test Mitigation",
"attachments": []
}, },
{ {
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2", "id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -24,7 +25,8 @@
"impact": "Medium", "impact": "Medium",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "Medium", "reproduction": "Medium",
"mitigation": "" "mitigation": "",
"attachments": []
} }
], ],
"comments": [ "comments": [
@ -32,7 +34,7 @@
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe", "id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment", "title": "Test Comment",
"description": "No related findings", "description": "No related findings",
"relatedFindings": [] "attachments": []
} }
], ],
"status": "COMPLETED" "status": "COMPLETED"
@ -50,7 +52,8 @@
"impact": "High", "impact": "High",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "High", "reproduction": "High",
"mitigation": "" "mitigation": "",
"attachments": []
} }
], ],
"comments": [], "comments": [],

View File

@ -22,7 +22,8 @@
"https://www.google.de/" "https://www.google.de/"
], ],
"reproduction": "Step 1: Test", "reproduction": "Step 1: Test",
"mitigation": "" "mitigation": "",
"attachments": []
}, },
{ {
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2", "id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -32,7 +33,8 @@
"impact": "Medium Impact", "impact": "Medium Impact",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "Step 1: Medium", "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", "id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -42,7 +44,8 @@
"impact": "High Impact", "impact": "High Impact",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "Step 1: High", "reproduction": "Step 1: High",
"mitigation": "" "mitigation": "",
"attachments": []
}, },
{ {
"id": "58f63b4e-97fb-4fe8-8527-7996896089d2", "id": "58f63b4e-97fb-4fe8-8527-7996896089d2",
@ -54,7 +57,8 @@
"https://community.jaspersoft.com/system/files/restricted-docs/jaspersoft-studio-user-guide.pdf" "https://community.jaspersoft.com/system/files/restricted-docs/jaspersoft-studio-user-guide.pdf"
], ],
"reproduction": "Step 1: Critical", "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": [ "comments": [
@ -62,15 +66,13 @@
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe", "id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment", "title": "Test Comment",
"description": "Not related to findings", "description": "Not related to findings",
"relatedFindings": [ "attachments": []
"58f63b4e-97fb-4fe8-8527-7996896089d2"
]
}, },
{ {
"id": "89703b19-16c7-49e5-8e33-0c706313e5fe", "id": "89703b19-16c7-49e5-8e33-0c706313e5fe",
"title": "Test Comment", "title": "Test Comment",
"description": "Test Decription", "description": "Test Decription",
"relatedFindings": [] "attachments": []
} }
], ],
"status": "COMPLETED" "status": "COMPLETED"
@ -88,7 +90,8 @@
"impact": "High", "impact": "High",
"affectedUrls": [], "affectedUrls": [],
"reproduction": "High", "reproduction": "High",
"mitigation": "" "mitigation": "",
"attachments": []
} }
], ],
"comments": [], "comments": [],

View File

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