feat: As an user I want to add the pentest status of a project

This commit is contained in:
Marcel Haag 2023-04-04 15:44:01 +02:00 committed by Cel
parent 2c7ac85f6e
commit 9e4fa27b92
45 changed files with 823 additions and 204 deletions

View File

@ -9,6 +9,7 @@
</button> </button>
</div> </div>
<app-report-state-tag class="state-tag" [currentReportState]="selectedProject$.getValue()?.state"></app-report-state-tag>
<h4>{{selectedProject$.getValue().title}}</h4> <h4>{{selectedProject$.getValue().title}}</h4>
<div class="button-container"> <div class="button-container">

View File

@ -24,6 +24,7 @@ import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model'; import {PentestStatus} from '@shared/models/pentest-status.model';
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service'; import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock'; import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -33,6 +34,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -75,21 +75,16 @@ export class ObjectiveHeaderComponent implements OnInit {
closeOnBackdropClick: false closeOnBackdropClick: false
} }
).pipe( ).pipe(
filter(value => !!value),
mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(this.selectedProject$.getValue().id, value)),
untilDestroyed(this) untilDestroyed(this)
).subscribe({ ).subscribe({
next: (project: Project) => { next: (project) => {
this.store.dispatch(new InitProjectState( this.store.dispatch(new InitProjectState(
project, project,
[], [],
[] []
)).pipe(untilDestroyed(this)).subscribe(); )).pipe(
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS); untilDestroyed(this)
}, ).subscribe();
error: error => {
console.error(error);
this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
} }
}); });
} }

View File

@ -25,6 +25,7 @@ import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.modul
import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/export-report-dialog.module'; import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/export-report-dialog.module';
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module'; import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module'; import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -32,32 +33,33 @@ import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget
ObjectiveCategoriesComponent, ObjectiveCategoriesComponent,
ObjectiveTableComponent, ObjectiveTableComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
CommonAppModule, CommonAppModule,
NbLayoutModule, NbLayoutModule,
NbCardModule, NbCardModule,
NbButtonModule, NbButtonModule,
// nbTooltip crashes app right now if used in component, // nbTooltip crashes app right now if used in component,
// workaround: use title in html for now // workaround: use title in html for now
NbTooltipModule, NbTooltipModule,
NbTreeGridModule, NbTreeGridModule,
TranslateModule, TranslateModule,
StatusTagModule, StatusTagModule,
RouterModule, RouterModule,
FormsModule, FormsModule,
NbListModule, NbListModule,
FontAwesomeModule, FontAwesomeModule,
FlexLayoutModule, FlexLayoutModule,
NbActionsModule, NbActionsModule,
ExportReportDialogModule, ExportReportDialogModule,
ProjectDialogModule, ProjectDialogModule,
ObjectiveOverviewRoutingModule, ObjectiveOverviewRoutingModule,
// Table Widgets // Table Widgets
FindigWidgetModule, FindigWidgetModule,
CommentWidgetModule, CommentWidgetModule,
NbMenuModule NbMenuModule,
], ReportStateTagModule
],
exports: [ exports: [
ObjectiveHeaderComponent, ObjectiveHeaderComponent,
ObjectiveCategoriesComponent, ObjectiveCategoriesComponent,

View File

@ -22,6 +22,7 @@ import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock'; import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service'; import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
import {CommentDialogServiceMock} from '@shared/modules/comment-dialog/service/comment-dialog.service.mock'; import {CommentDialogServiceMock} from '@shared/modules/comment-dialog/service/comment-dialog.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -31,6 +32,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -13,6 +13,7 @@ import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model'; import {PentestStatus} from '@shared/models/pentest-status.model';
import {NotificationService} from '@shared/services/toaster-service/notification.service'; import {NotificationService} from '@shared/services/toaster-service/notification.service';
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -22,6 +23,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -22,6 +22,7 @@ import {FindingDialogService} from '@shared/modules/finding-dialog/service/findi
import {FindingDialogServiceMock} from '@shared/modules/finding-dialog/service/finding-dialog.service.mock'; import {FindingDialogServiceMock} from '@shared/modules/finding-dialog/service/finding-dialog.service.mock';
import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock'; import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -31,6 +32,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -13,6 +13,7 @@ import {NgxsModule, Store} from '@ngxs/store';
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state'; import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
import {Category} from '@shared/models/category.model'; import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model'; import {PentestStatus} from '@shared/models/pentest-status.model';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -22,6 +23,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -13,6 +13,7 @@ import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model'; import {PentestStatus} from '@shared/models/pentest-status.model';
import {NotificationService} from '@shared/services/toaster-service/notification.service'; import {NotificationService} from '@shared/services/toaster-service/notification.service';
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -22,6 +23,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -1,87 +1,131 @@
<div fxLayout="row" fxLayoutGap="2rem"> <div fxFlex="0 1 max-content" fxLayout="column" class="pentest-overview">
<div *ngFor="let project of projects$ | async"> <nb-layout fxFlex>
<nb-card class="project-card" accent="success"> <!--Header-->
<nb-card-header fxLayoutAlign="start center" <nb-layout-header class="pentest-overview-header">
routerLink="id" <div fxLayout="row" fxLayoutGap="2rem" fxLayoutAlign="space-between center">
fragment="{{project.id}}" <!--Filter-->
class="project-link project-header" <div fxLayout="row" fxLayoutGap="1rem" class="header-filer">
(click)="onClickRouteToProject(project)"> <!--Actions-->
<h4>{{project?.title}}</h4> <!--ToDo: Add searchbar that filters for title, client, tester-->
</nb-card-header> <form class="project-filter-input">
<nb-card-body class="project-link" <nb-form-field>
routerLink="id" <fa-icon nbPrefix class ="search-prefix-icon" [icon]="fa.faSearch"></fa-icon>
fragment="{{project.id}}" <input type="text" required
(click)="onClickRouteToProject(project)"> fullWidth nbInput
<p class="project-subheader"> [formControl]="projectSearch"
{{'project.client' | translate}}: placeholder="{{ 'project.filter.placeholder' | translate }}"
</p> shape="semi-round"
<span class="project-paragraph"> fieldSize="medium"
status="basic">
</nb-form-field>
</form>
<!--ToDo: Add dropdown to filter for specific state-->
<button nbButton
status="danger"
size="medium"
shape="semi-round"
class="reset-filter-btn"
(click)="onClickResetFilter()">
<fa-icon [icon]="fa.faFilterCircleXmark" class="btn-icon"></fa-icon>
{{'global.action.reset' | translate}}
</button>
</div>
<!--Button-->
<div class="header-project-button">
<button nbButton hero
status="info"
size="medium"
shape="round"
class="add-project-button"
(click)="onClickAddProject()">
<fa-icon [icon]="fa.faPlus" class="btn-icon"></fa-icon>
{{'project.overview.add.project' | translate}}
</button>
</div>
</div>
</nb-layout-header>
<!--Column-->
<nb-layout-column class="pentest-overview-column">
<div fxLayout="row" fxLayoutGap="2rem">
<div *ngFor="let project of projects$ | async">
<nb-card class="project-card" accent="{{getProjectAccentFillStatus(project?.state)}}">
<nb-card-header fxLayoutAlign="start center"
routerLink="id"
fragment="{{project.id}}"
class="project-link project-header"
(click)="onClickRouteToProject(project)">
<div fxLayout="row" fxLayoutAlign="space-between center">
<h4 class="header-title">{{project?.title}}</h4>
<app-report-state-tag class="state-tag" [currentReportState]="project?.state"></app-report-state-tag>
</div>
</nb-card-header>
<nb-card-body class="project-link"
routerLink="id"
fragment="{{project.id}}"
(click)="onClickRouteToProject(project)">
<p class="project-subheader">
{{'project.client' | translate}}:
</p>
<span class="project-paragraph">
{{project?.client}} {{project?.client}}
</span> </span>
<p class="project-subheader"> <p class="project-subheader">
{{'project.tester' | translate}}: {{'project.tester' | translate}}:
</p> </p>
<span class="project-paragraph"> <span class="project-paragraph">
{{project?.tester}} {{project?.tester}}
</span> </span>
<p class="project-subheader"> <p class="project-subheader">
{{'project.createdAt' | translate}}: {{'project.createdAt' | translate}}:
</p> </p>
<span class="project-paragraph"> <span class="project-paragraph">
{{project?.createdAt | dateTimeFormat}} {{project?.createdAt | dateTimeFormat}}
</span> </span>
</nb-card-body> </nb-card-body>
<nb-card-footer> <nb-card-footer>
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end"> <div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
<div class="project-progress"> <div class="project-progress">
<nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar" <nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar"
status="warning" status="warning"
[value]="project.testingProgress" [value]="project.testingProgress"
[displayValue]="true"> [displayValue]="true">
</nb-progress-bar> </nb-progress-bar>
<ng-template #altProgressBar> <ng-template #altProgressBar>
{{'popup.info' | translate}} {{'global.no.progress' | translate}} {{'popup.info' | translate}} {{'global.no.progress' | translate}}
</ng-template> </ng-template>
</div> </div>
<button nbButton <button nbButton
status="primary" status="primary"
size="small" size="small"
class="project-button" class="project-button"
(click)="onClickEditProject(project)"> (click)="onClickEditProject(project)">
<fa-icon [icon]="fa.faPencilAlt"></fa-icon> <fa-icon [icon]="fa.faPencilAlt"></fa-icon>
</button> </button>
<button nbButton <button nbButton
status="danger" status="danger"
size="small" size="small"
class="project-button" class="project-button"
(click)="onClickDeleteProject(project)"> (click)="onClickDeleteProject(project)">
<fa-icon [icon]="fa.faTrash"></fa-icon> <fa-icon [icon]="fa.faTrash"></fa-icon>
</button> </button>
</div>
</nb-card-footer>
</nb-card>
</div> </div>
</nb-card-footer> </div>
</nb-card> <!--Error Text-->
</div> <div *ngIf="projects$.getValue() == null || projects$.getValue().length === 0 && loading$.getValue() === false"
</div> fxLayout="row" fxLayoutAlign="center center">
<p class="error-text">
{{'project.overview.no.projects' | translate}}
</p>
</div>
<!--Loading Spinner-->
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
<div *ngIf="projects$.getValue() == null || projects$.getValue().length === 0 && loading$.getValue() === false" fxLayout="row" fxLayoutAlign="center center"> </nb-layout-column>
<p class="error-text"> </nb-layout>
{{'project.overview.no.projects' | translate}}
</p>
</div> </div>
<div fxLayoutAlign="end end">
<button nbButton hero
status="info"
size="large"
shape="round"
class="add-project-button"
(click)="onClickAddProject()">
<fa-icon [icon]="fa.faPlus" class="new-project-icon"></fa-icon>
{{'project.overview.add.project' | translate}}
</button>
</div>
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>

View File

@ -1,62 +1,118 @@
@import '../../assets/@theme/styles/themes'; @import '../../assets/@theme/styles/themes';
@import '../../assets/@theme/styles/variables';
.project-card { .pentest-overview {
max-width: 22rem; width: 100vw;
width: 22rem; height: 80vh;
min-width: 20rem; // ToDo: Disable and fix scrolling
max-height: 100%; overflow: hidden;
height: 100%;
min-height: 100%;
.project-header { .pentest-overview-header {
max-height: 8rem; width: 100vw;
height: 8rem;
min-height: 6rem; .header-filer {
.project-filter-input {
width: 24rem;
.search-prefix-icon {
color:nb-theme(color-info-default);
}
}
.state-dialog {
margin-left: auto;
margin-right: 0;
.states {
width: 14rem !important;
}
}
.reset-filter-btn {
.btn-icon {
padding-right: 0.5rem;
}
}
}
.header-project-button {
position: fixed;
right: 1.5rem;
.add-project-button {
// align-content: flex-end;
margin: 6rem 2rem 6rem 0;
.btn-icon {
padding-right: 0.5rem;
}
}
}
} }
.project-subheader { .pentest-overview-column {
font-size: 1.25rem; width: 100vw;
font-weight: bold;
}
.project-paragraph { .project-card {
font-size: 1.15rem; max-width: 22rem;
font-style: italic; width: 22rem;
} min-width: 20rem;
max-height: 100%;
height: 100%;
min-height: 100%;
.project-progress { .project-header {
max-width: 65%; max-height: 8rem;
width: 65%; height: 8rem;
min-width: 65%; min-height: 6rem;
}
.project-button { .header-title {
height: 1.425rem; width: 12.5rem;
}
.state-tag {
width: 1rem;
}
}
.project-subheader {
font-size: 1.25rem;
font-weight: bold;
}
.project-paragraph {
font-size: 1.15rem;
font-style: italic;
}
.project-progress {
max-width: 65%;
width: 65%;
min-width: 65%;
}
.project-button {
height: 1.425rem;
}
}
.project-card:hover {
background-color: nb-theme(color-basic-transparent-focus);
// Increases element size on hover
// Decreases usability which is why it is commented out
/*
margin-top: +0.625rem;
transform: scale(1.025);
*/
}
.project-link:hover {
cursor: pointer !important;
}
.error-text {
font-size: 1.25rem;
font-weight: bold;
}
} }
} }
.project-card:hover {
background-color: nb-theme(color-basic-transparent-focus);
// Increases element size on hover
// Decreases usability which is why it is commented out
/*
margin-top: +0.625rem;
transform: scale(1.025);
*/
}
.project-link:hover {
cursor: pointer !important;
}
.add-project-button {
margin: 6rem 2rem 6rem 0;
.new-project-icon {
padding-right: 0.5rem;
}
}
.error-text {
font-size: 1.25rem;
font-weight: bold;
}

View File

@ -5,7 +5,7 @@ import {BehaviorSubject, Observable} from 'rxjs';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {ProjectService} from '@shared/services/api/project.service'; import {ProjectService} from '@shared/services/api/project.service';
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service'; import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
import {filter, tap} from 'rxjs/operators'; import {filter, startWith, tap} from 'rxjs/operators';
import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component'; import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service'; import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
@ -13,6 +13,8 @@ import {Router} from '@angular/router';
import {Route} from '@shared/models/route.enum'; import {Route} from '@shared/models/route.enum';
import {InitProjectState} from '@shared/stores/project-state/project-state.actions'; import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
import {Store} from '@ngxs/store'; import {Store} from '@ngxs/store';
import {ReportState} from '@shared/models/state.enum';
import {FormControl} from '@angular/forms';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -26,6 +28,11 @@ export class ProjectOverviewComponent implements OnInit {
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true); loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
projects$: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]); projects$: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
allProjects$: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
// Search
projectSearch: FormControl;
protected filter$: Observable<string>;
constructor( constructor(
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
@ -38,6 +45,9 @@ export class ProjectOverviewComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.loadProjects(); this.loadProjects();
// Setup Search
this.projectSearch = new FormControl({value: '', disabled: !this.allProjects$.getValue()});
this.setFilterObserverForProjects();
} }
loadProjects(): void { loadProjects(): void {
@ -49,6 +59,7 @@ export class ProjectOverviewComponent implements OnInit {
.subscribe({ .subscribe({
next: (projects: Project[]) => { next: (projects: Project[]) => {
this.projects$.next(projects); this.projects$.next(projects);
this.allProjects$.next(projects);
this.loading$.next(false); this.loading$.next(false);
}, },
error: err => { error: err => {
@ -156,6 +167,74 @@ export class ProjectOverviewComponent implements OnInit {
return this.loading$.asObservable(); return this.loading$.asObservable();
} }
/**
* HTML only
* @return the correct nb-accent for current report state of the project
*/
getProjectAccentFillStatus(value: any): string {
let reportStateFillStatus;
const statusValue = typeof value !== 'number' ? ReportState[value] : value;
// Check for correct accent color of status
switch (statusValue) {
case 6:
case 7: {
reportStateFillStatus = 'success';
break;
}
case 0: {
reportStateFillStatus = 'info';
break;
}
case 8:
case 9:
case 11:
case 12: {
reportStateFillStatus = 'warning';
break;
}
case 1:
case 10: {
reportStateFillStatus = 'danger';
break;
}
default: {
reportStateFillStatus = 'control';
break;
}
}
return reportStateFillStatus;
}
onClickResetFilter(): void {
this.projectSearch.reset('');
this.projects$.next(this.allProjects$.getValue());
}
private setFilterObserverForProjects(): void {
this.filter$ = this.projectSearch.valueChanges.pipe(startWith(''));
this.filter$.subscribe(
(filterString: string) => {
if (filterString.length === 0) {
this.projects$.next(this.allProjects$.getValue());
} else {
const matchingProjects: Project[] = [];
this.allProjects$.getValue().forEach(project => {
// Project attributes that the user can filter through
if (
project.title.toLowerCase().includes(filterString.toLowerCase())
|| project.client.toLowerCase().includes(filterString.toLowerCase())
|| project.tester.toLowerCase().includes(filterString.toLowerCase())
|| project.state.toString().toLowerCase().includes(filterString.toLowerCase())
) {
matchingProjects.push(project);
}
});
this.projects$.next(matchingProjects);
}
}
);
}
private deleteProject(project: Project): void { private deleteProject(project: Project): void {
this.projectService.deleteProjectById(project.id).pipe( this.projectService.deleteProjectById(project.id).pipe(
untilDestroyed(this) untilDestroyed(this)

View File

@ -2,7 +2,15 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {ProjectOverviewComponent} from './project-overview.component'; import {ProjectOverviewComponent} from './project-overview.component';
import {ProjectOverviewRoutingModule} from './project-overview-routing.module'; import {ProjectOverviewRoutingModule} from './project-overview-routing.module';
import {NbButtonModule, NbCardModule, NbProgressBarModule} from '@nebular/theme'; import {
NbButtonModule,
NbCardModule,
NbFormFieldModule,
NbInputModule,
NbLayoutModule,
NbProgressBarModule,
NbSelectModule
} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout'; import {FlexLayoutModule} from '@angular/flex-layout';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core'; import {TranslateModule} from '@ngx-translate/core';
@ -12,6 +20,8 @@ import {CommonAppModule} from '../common-app.module';
import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module'; import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module';
import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module'; import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module';
import {RouterModule} from '@angular/router'; import {RouterModule} from '@angular/router';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -34,7 +44,13 @@ import {RouterModule} from '@angular/router';
TranslateModule, TranslateModule,
ProjectDialogModule, ProjectDialogModule,
ConfirmDialogModule, ConfirmDialogModule,
SecurityConfirmDialogModule ReportStateTagModule,
SecurityConfirmDialogModule,
NbLayoutModule,
NbInputModule,
NbFormFieldModule,
ReactiveFormsModule,
NbSelectModule
] ]
}) })
export class ProjectOverviewModule { export class ProjectOverviewModule {

View File

@ -8,7 +8,6 @@ import {HttpLoaderFactory} from '../../common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http'; import {HttpClient, HttpClientModule} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
import {NgxsModule, Store} from '@ngxs/store'; import {NgxsModule, Store} from '@ngxs/store';
import {SessionState} from '@shared/stores/session-state/session-state';
import {HttpClientTestingModule} from '@angular/common/http/testing'; import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NbCardModule, NbDialogRef, NbLayoutModule} from '@nebular/theme'; import {NbCardModule, NbDialogRef, NbLayoutModule} from '@nebular/theme';
import {KeycloakService} from 'keycloak-angular'; import {KeycloakService} from 'keycloak-angular';
@ -27,6 +26,7 @@ import {PentestStatus} from '@shared/models/pentest-status.model';
import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.component.spec'; import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.component.spec';
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service'; import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock'; import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -36,6 +36,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -52,6 +52,21 @@
"failed": "Benutzername oder Passwort falsch", "failed": "Benutzername oder Passwort falsch",
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen" "unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
}, },
"state": {
"new": "Neu",
"needs_more_info": "Benötigt mehr Informationen",
"pre_submission": "Voranmeldung",
"pending": "Pending",
"triaged": "Ausstehend",
"retesting": "Erneutes Testen",
"resolved": "Aufgeklärt",
"informative": "Informatif",
"duplicate": "Duplikat",
"not_applicable": "Unzutreffend",
"spam": "Spam",
"out_of_scope": "Außerhalb Anwendungsbereich",
"accepted_risk": "Akzeptiertes Risiko"
},
"report": { "report": {
"dialog": { "dialog": {
"header": "Penetrationstestbereicht exportieren", "header": "Penetrationstestbereicht exportieren",
@ -69,17 +84,22 @@
"title.label": "Projekt Titel", "title.label": "Projekt Titel",
"client.label": "Name des Auftraggebers", "client.label": "Name des Auftraggebers",
"tester.label": "Name des Pentester", "tester.label": "Name des Pentester",
"state.label": "Penteststatus",
"summary.label": "Zusammenfassung", "summary.label": "Zusammenfassung",
"summary.placeholder": "Sollte eine Zusammenfassung, einen Ansatz, einen Umfang und eine Bewertungsübersicht sowie allgemeine Empfehlungen enthalten", "summary.placeholder": "Sollte eine Zusammenfassung, einen Ansatz, einen Umfang und eine Bewertungsübersicht sowie allgemeine Empfehlungen enthalten",
"title": "Titel", "title": "Titel",
"client": "Klient", "client": "Klient",
"tester": "Tester", "tester": "Tester",
"state": "Penteststatus",
"summary": "Zusammenfassung", "summary": "Zusammenfassung",
"createdAt": "Erstellt am", "createdAt": "Erstellt am",
"overview": { "overview": {
"add.project": "Projekt hinzufügen", "add.project": "Projekt hinzufügen",
"no.projects": "Keine Projekte verfügbar" "no.projects": "Keine Projekte verfügbar"
}, },
"filter": {
"placeholder": "Projekt suchen"
},
"create": { "create": {
"header": "Neues Projekt erstellen" "header": "Neues Projekt erstellen"
}, },
@ -96,6 +116,7 @@
"titleRequired": "Titel ist erforderlich.", "titleRequired": "Titel ist erforderlich.",
"clientRequired": "Klient ist erforderlich.", "clientRequired": "Klient ist erforderlich.",
"testerRequired": "Tester ist erforderlich.", "testerRequired": "Tester ist erforderlich.",
"stateRequired": "Status ist erforderlich.",
"summaryRequired": "Zusammenfassung ist erforderlich." "summaryRequired": "Zusammenfassung ist erforderlich."
}, },
"popup": { "popup": {

View File

@ -52,6 +52,21 @@
"failed": "Wrong username or password", "failed": "Wrong username or password",
"unauthorized": "User not found. Please register and try again" "unauthorized": "User not found. Please register and try again"
}, },
"state": {
"new": "New",
"needs_more_info": "Needs More Info",
"pre_submission": "Pre-submission",
"pending": "Pending",
"triaged": "Triaged",
"retesting": "Retesting",
"resolved": "Resolved",
"informative": "Informative",
"duplicate": "Duplicate",
"not_applicable": "Not Applicable",
"spam": "Spam",
"out_of_scope": "Out Of Scope",
"accepted_risk": "Accepted Risk"
},
"report": { "report": {
"dialog": { "dialog": {
"header": "Export Penetrationtest Report", "header": "Export Penetrationtest Report",
@ -69,17 +84,22 @@
"title.label": "Project Title", "title.label": "Project Title",
"client.label": "Name of Client", "client.label": "Name of Client",
"tester.label": "Name of Pentester", "tester.label": "Name of Pentester",
"state.label": "State of Pentest",
"summary.label": "Summary", "summary.label": "Summary",
"summary.placeholder": "Should include Executive Summary, Approach, Scope and Assessment Overview as well as General Recommendations", "summary.placeholder": "Should include Executive Summary, Approach, Scope and Assessment Overview as well as General Recommendations",
"title": "Title", "title": "Title",
"client": "Client", "client": "Client",
"tester": "Tester", "tester": "Tester",
"state": "State of Pentest",
"summary": "Summary", "summary": "Summary",
"createdAt": "Created at", "createdAt": "Created at",
"overview": { "overview": {
"add.project": "Add Project", "add.project": "Add Project",
"no.projects": "No projects available" "no.projects": "No projects available"
}, },
"filter": {
"placeholder": "Search for project"
},
"create": { "create": {
"header": "Create New Project" "header": "Create New Project"
}, },
@ -96,6 +116,7 @@
"titleRequired": "Title is required.", "titleRequired": "Title is required.",
"clientRequired": "Client is required.", "clientRequired": "Client is required.",
"testerRequired": "Tester is required.", "testerRequired": "Tester is required.",
"stateRequired": "State is required.",
"summaryRequired": "Summary is required." "summaryRequired": "Summary is required."
}, },
"popup": { "popup": {

View File

@ -1,4 +1,5 @@
import {PentestStatus} from '@shared/models/pentest-status.model'; import {PentestStatus} from '@shared/models/pentest-status.model';
import {ReportState} from '@shared/models/state.enum';
export class Project { export class Project {
id: string; id: string;
@ -7,6 +8,7 @@ export class Project {
createdAt: Date; createdAt: Date;
tester: string; tester: string;
summary: string; summary: string;
state: ReportState;
projectPentests?: Array<ProjectPentests>; projectPentests?: Array<ProjectPentests>;
testingProgress?: number; testingProgress?: number;
createdBy: string; createdBy: string;
@ -16,6 +18,7 @@ export class Project {
title: string, title: string,
createdAt: Date, createdAt: Date,
tester: string, tester: string,
state: ReportState,
projectPentests?: Array<ProjectPentests>, projectPentests?: Array<ProjectPentests>,
testingProgress?: number, testingProgress?: number,
summary?: string, summary?: string,
@ -28,14 +31,28 @@ export class Project {
this.projectPentests = projectPentests; this.projectPentests = projectPentests;
this.testingProgress = testingProgress; this.testingProgress = testingProgress;
this.summary = summary; this.summary = summary;
this.state = state;
this.createdBy = createdBy; this.createdBy = createdBy;
} }
} }
export function transformProjectToRequestBody(project: ProjectDialogBody | Project): ProjectDialogBody {
const transformedProject = {
...project,
title: project.title,
client: project.client,
tester: project.tester,
state: typeof project.state === 'number' ? ReportState[project.state] : project.state,
summary: project.summary,
} as unknown as ProjectDialogBody;
return transformedProject;
}
export interface ProjectDialogBody { export interface ProjectDialogBody {
title: string; title: string;
client: string; client: string;
tester: string; tester: string;
state: ReportState;
summary: string; summary: string;
} }

View File

@ -0,0 +1,40 @@
export enum ReportState {
NEW,
NEEDS_MORE_INFO,
// Report states depending on customer feedback
PRE_SUBMISSION,
PENDING,
TRIAGED,
RETESTING,
// Report states for closed submissions
RESOLVED,
INFORMATIVE,
DUPLICATE,
NOT_APPLICABLE,
SPAM,
OUT_OF_SCOPE,
ACCEPTED_RISK
}
export const reportStateTexts: Array<ReportStateText> = [
{value: ReportState.NEW, translationText: 'state.new'},
{value: ReportState.NEEDS_MORE_INFO, translationText: 'state.needs_more_info'},
// Report states depending on customer feedback
{value: ReportState.PRE_SUBMISSION, translationText: 'state.pre_submission'},
{value: ReportState.PENDING, translationText: 'state.pending'},
{value: ReportState.TRIAGED, translationText: 'state.triaged'},
{value: ReportState.RETESTING, translationText: 'state.retesting'},
// Report states for closed submissions
{value: ReportState.RESOLVED, translationText: 'state.resolved'},
{value: ReportState.INFORMATIVE, translationText: 'state.informative'},
{value: ReportState.DUPLICATE, translationText: 'state.duplicate'},
{value: ReportState.NOT_APPLICABLE, translationText: 'state.not_applicable'},
{value: ReportState.SPAM, translationText: 'state.spam'},
{value: ReportState.OUT_OF_SCOPE, translationText: 'state.out_of_scope'},
{value: ReportState.ACCEPTED_RISK, translationText: 'state.accepted_risk'}
];
export interface ReportStateText {
value: ReportState;
translationText: string;
}

View File

@ -32,6 +32,7 @@ import Mock = jest.Mock;
import {Finding} from '@shared/models/finding.model'; import {Finding} from '@shared/models/finding.model';
import {Severity} from '@shared/models/severity.enum'; import {Severity} from '@shared/models/severity.enum';
import {Comment} from '@shared/models/comment.model'; import {Comment} from '@shared/models/comment.model';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -41,6 +42,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -28,6 +28,7 @@ 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 {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {ReportState} from '@shared/models/state.enum';
describe('ExportReportDialogComponent', () => { describe('ExportReportDialogComponent', () => {
let component: ExportReportDialogComponent; let component: ExportReportDialogComponent;
@ -102,6 +103,7 @@ const mockProject: Project = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
projectPentests: mockedPentests, projectPentests: mockedPentests,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'

View File

@ -30,6 +30,7 @@ import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model'; import {PentestStatus} from '@shared/models/pentest-status.model';
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state'; import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
import {NgxsModule, Store} from '@ngxs/store'; import {NgxsModule, Store} from '@ngxs/store';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: { selectedProject: {
@ -39,6 +40,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },
@ -97,13 +99,13 @@ describe('FindingDialogComponent', () => {
{provide: NotificationService, useValue: new NotificationServiceMock()}, {provide: NotificationService, useValue: new NotificationServiceMock()},
{provide: DialogService, useClass: DialogServiceMock}, {provide: DialogService, useClass: DialogServiceMock},
{provide: NbDialogRef, useValue: dialogSpy}, {provide: NbDialogRef, useValue: dialogSpy},
{provide: NB_DIALOG_CONFIG, useValue: mockedCommentDialogData} {provide: NB_DIALOG_CONFIG, useValue: mockedFindingDialogData}
] ]
}).compileComponents(); }).compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedCommentDialogData}); TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedFindingDialogData});
fixture = TestBed.createComponent(FindingDialogComponent); fixture = TestBed.createComponent(FindingDialogComponent);
store = TestBed.inject(Store); store = TestBed.inject(Store);
store.reset({ store.reset({
@ -138,7 +140,7 @@ export const mockFinding: Finding = {
mitigation: 'Mitigation Test' mitigation: 'Mitigation Test'
}; };
export const mockedCommentDialogData = { export const mockedFindingDialogData = {
form: { form: {
findingTitle: { findingTitle: {
fieldName: 'findingTitle', fieldName: 'findingTitle',

View File

@ -1,6 +1,18 @@
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="project-dialog"> <nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="project-dialog">
<nb-card-header fxLayoutAlign="start center" class="dialog-header"> <nb-card-header fxLayoutAlign="start center" class="dialog-header">
{{ dialogData?.options[0].headerLabelKey | translate }} {{ dialogData?.options[0].headerLabelKey | translate }}
<!-- Pents State Dropdown -->
<div class="state-dialog">
<nb-select class="states"
type="state-select"
[disabled]="!dialogData.options[0].additionalData"
[(selected)]="formArray[4].controlsConfig[0].value"
shape="round" status="{{getReportStateFillStatus(formArray[4].controlsConfig[0].value)}}" filled>
<nb-option *ngFor="let reportState of reportStateTexts" [value]="reportState.value">
{{ reportState.translationText | translate }}
</nb-option>
</nb-select>
</div>
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
<form *ngIf="formArray" [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem" <form *ngIf="formArray" [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem"

View File

@ -2,11 +2,11 @@
@import '../../../assets/@theme/styles/themes'; @import '../../../assets/@theme/styles/themes';
.project-dialog { .project-dialog {
width: 34rem !important; width: 36rem !important;
height: 43.5rem; height: 43.5rem;
.project-dialog-header { .project-dialog-header {
height: 8vh; height: 10vh;
font-size: 1.5rem; font-size: 1.5rem;
} }
@ -21,19 +21,19 @@
} }
.form-field { .form-field {
width: 26.75rem; width: 32.75rem;
// width: 30rem !important; // width: 30rem !important;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.form-textarea { .form-textarea {
width: 26.75rem !important; width: 32.75rem !important;
// width: 30rem !important; // width: 30rem !important;
height: 8rem; height: 8rem;
} }
.form-textarea:disabled { .form-textarea:disabled {
width: 26.75rem !important; width: 32.75rem !important;
// width: 30rem !important; // width: 30rem !important;
background-color: nb-theme(color-basic-transparent-focus); background-color: nb-theme(color-basic-transparent-focus);
height: 8rem; height: 8rem;
@ -43,4 +43,14 @@
float: left; float: left;
color: nb-theme(color-danger-default); color: nb-theme(color-danger-default);
} }
.state-dialog {
margin-left: auto;
margin-right: 0;
// padding: 0.5rem 0 0.75rem;
.states {
width: 14rem !important;
}
}
} }

View File

@ -8,7 +8,7 @@ import {
NbDialogRef, NbDialogRef,
NbFormFieldModule, NbFormFieldModule,
NbInputModule, NbInputModule,
NbLayoutModule NbLayoutModule, NbSelectModule
} from '@nebular/theme'; } from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout'; import {FlexLayoutModule} from '@angular/flex-layout';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@ -24,6 +24,8 @@ import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.
import {ReactiveFormsModule, Validators} from '@angular/forms'; import {ReactiveFormsModule, Validators} from '@angular/forms';
import {Project} from '@shared/models/project.model'; import {Project} from '@shared/models/project.model';
import Mock = jest.Mock; import Mock = jest.Mock;
import {ReportState} from '@shared/models/state.enum';
import {GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
describe('ProjectDialogComponent', () => { describe('ProjectDialogComponent', () => {
let component: ProjectDialogComponent; let component: ProjectDialogComponent;
@ -43,6 +45,7 @@ describe('ProjectDialogComponent', () => {
NbButtonModule, NbButtonModule,
FlexLayoutModule, FlexLayoutModule,
NbInputModule, NbInputModule,
NbSelectModule,
NbFormFieldModule, NbFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
BrowserAnimationsModule, BrowserAnimationsModule,
@ -70,7 +73,8 @@ describe('ProjectDialogComponent', () => {
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedProjectDialogData}); TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedProjectDialogData});
fixture = TestBed.createComponent(ProjectDialogComponent); fixture = TestBed.createComponent(ProjectDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); // ToDo: fix detectChanges() when controlsConfig is defined
// fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
@ -91,7 +95,8 @@ export const mockProject: Project = {
title: 'Test Project', title: 'Test Project',
client: 'Testclient', client: 'Testclient',
tester: 'Testpentester', tester: 'Testpentester',
summary: '', summary: 'Test',
state: ReportState.NEW,
createdAt: new Date(), createdAt: new Date(),
testingProgress: 0, testingProgress: 0,
createdBy: 'UID-11-12-13' createdBy: 'UID-11-12-13'
@ -137,13 +142,27 @@ export const mockedProjectDialogData = {
errors: [ errors: [
{errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'} {errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'}
] ]
} },
pentestState: {
fieldName: 'pentestState',
type: 'state-select',
labelKey: 'project.state.label',
placeholder: 'project.state',
controlsConfig: [
{value: mockProject ? mockProject.state : ReportState.NEW, disabled: false},
[Validators.required]
],
errors: [
{errorCode: 'required', translationKey: 'project.validationMessage.stateRequired'}
]
},
}, },
options: [ options: [
{ {
headerLabelKey: 'project.edit.header', headerLabelKey: 'project.edit.header',
buttonKey: 'global.action.update', buttonKey: 'global.action.update',
accentColor: 'warning' accentColor: 'warning',
additionalData: mockProject
}, },
] ]
}; };

View File

@ -7,6 +7,9 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectService} from '@shared/services/api/project.service'; import {ProjectService} from '@shared/services/api/project.service';
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service'; import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
import * as FA from '@fortawesome/free-solid-svg-icons';
import {ReportState, reportStateTexts} from '@shared/models/state.enum';
import {Project, transformProjectToRequestBody} from '@shared/models/project.model';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -21,6 +24,11 @@ export class ProjectDialogComponent implements OnInit {
dialogData: GenericDialogData; dialogData: GenericDialogData;
// HTML only
readonly fa = FA;
state: ReportState = ReportState.NEW;
readonly reportStateTexts = reportStateTexts;
constructor( constructor(
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData, @Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
private fb: FormBuilder, private fb: FormBuilder,
@ -63,6 +71,43 @@ export class ProjectDialogComponent implements OnInit {
return this.projectFormGroup.valid && this.projectDataChanged(); return this.projectFormGroup.valid && this.projectDataChanged();
} }
/**
* HTML only
* @return the correct nb-status for current report state of the project
*/
getReportStateFillStatus(value: number): string {
let reportStateFillStatus;
switch (value) {
case 6:
case 7: {
reportStateFillStatus = 'success';
break;
}
case 0:
{
reportStateFillStatus = 'info';
break;
}
case 8:
case 9:
case 11:
case 12: {
reportStateFillStatus = 'warning';
break;
}
case 1:
case 10: {
reportStateFillStatus = 'danger';
break;
}
default: {
reportStateFillStatus = 'basic';
break;
}
}
return reportStateFillStatus;
}
/** /**
* @return true if project data is different from initial value * @return true if project data is different from initial value
*/ */
@ -98,9 +143,12 @@ export class ProjectDialogComponent implements OnInit {
title: value.projectTitle, title: value.projectTitle,
client: value.projectClient, client: value.projectClient,
tester: value.projectTester, tester: value.projectTester,
state: this.formArray[4].controlsConfig[0].value,
summary: value.projectSummary summary: value.projectSummary
}; };
this.projectService.saveProject(dialogRes).pipe( this.projectService.saveProject(
transformProjectToRequestBody(dialogRes)
).pipe(
untilDestroyed(this) untilDestroyed(this)
).subscribe( ).subscribe(
{ {
@ -122,15 +170,19 @@ export class ProjectDialogComponent implements OnInit {
title: value.projectTitle, title: value.projectTitle,
client: value.projectClient, client: value.projectClient,
tester: value.projectTester, tester: value.projectTester,
state: this.formArray[4].controlsConfig[0].value,
summary: value.projectSummary summary: value.projectSummary
}; };
this.projectService.updateProject(this.dialogData.options[0].additionalData.id, dialogRes).pipe( this.projectService.updateProject(
this.dialogData.options[0].additionalData.id,
transformProjectToRequestBody(dialogRes)
).pipe(
untilDestroyed(this) untilDestroyed(this)
).subscribe( ).subscribe(
{ {
next: () => { next: (project: Project) => {
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS); this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
this.dialogRef.close(); this.dialogRef.close(project);
}, },
error: err => { error: err => {
console.error(err); console.error(err);

View File

@ -1,7 +1,7 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component'; import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule} from '@nebular/theme'; import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbSelectModule} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout'; import {FlexLayoutModule} from '@angular/flex-layout';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core'; import {TranslateModule} from '@ngx-translate/core';
@ -13,18 +13,19 @@ import {ReactiveFormsModule} from '@angular/forms';
declarations: [ declarations: [
ProjectDialogComponent ProjectDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
CommonAppModule, CommonAppModule,
NbCardModule, NbCardModule,
NbButtonModule, NbButtonModule,
NbFormFieldModule, NbFormFieldModule,
NbInputModule, NbInputModule,
FlexLayoutModule, FlexLayoutModule,
FontAwesomeModule, FontAwesomeModule,
TranslateModule, TranslateModule,
ReactiveFormsModule, ReactiveFormsModule,
], NbSelectModule,
],
providers: [ providers: [
ProjectDialogService, ProjectDialogService,
], ],

View File

@ -6,6 +6,7 @@ import {Project} from '@shared/models/project.model';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component'; import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {Validators} from '@angular/forms'; import {Validators} from '@angular/forms';
import {GenericDialogData} from '@shared/models/generic-dialog-data'; import {GenericDialogData} from '@shared/models/generic-dialog-data';
import {ReportState} from '@shared/models/state.enum';
@Injectable() @Injectable()
export class ProjectDialogService { export class ProjectDialogService {
@ -33,6 +34,11 @@ export class ProjectDialogService {
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;
let state;
// transform severity of finding if existing
if (project) {
state = typeof project.state !== 'number' ? ReportState[project.state] : project.state;
}
// Setup ProjectDialogData // Setup ProjectDialogData
dialogData = { dialogData = {
form: { form: {
@ -87,7 +93,20 @@ export class ProjectDialogService {
errors: [ errors: [
{errorCode: 'required', translationKey: 'project.validationMessage.summaryRequired'} {errorCode: 'required', translationKey: 'project.validationMessage.summaryRequired'}
] ]
} },
pentestState: {
fieldName: 'pentestState',
type: 'state-select',
labelKey: 'project.state.label',
placeholder: 'project.state',
controlsConfig: [
{value: project ? state : ReportState.NEW, disabled: false},
[Validators.required]
],
errors: [
{errorCode: 'required', translationKey: 'project.validationMessage.stateRequired'}
]
},
}, },
options: [] options: []
}; };

View File

@ -7,6 +7,7 @@ import {KeycloakService} from 'keycloak-angular';
import {Project, ProjectDialogBody} from '@shared/models/project.model'; import {Project, ProjectDialogBody} from '@shared/models/project.model';
import {environment} from '../../../environments/environment'; import {environment} from '../../../environments/environment';
import {throwError} from 'rxjs'; import {throwError} from 'rxjs';
import {ReportState} from '@shared/models/state.enum';
describe('ProjectService', () => { describe('ProjectService', () => {
let service: ProjectService; let service: ProjectService;
@ -42,6 +43,7 @@ describe('ProjectService', () => {
createdAt: dummyDate, createdAt: dummyDate,
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}; };
@ -83,6 +85,7 @@ describe('ProjectService', () => {
client: 'E Corp', client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
tester: 'Novatester', tester: 'Novatester',
state: ReportState.NEW,
summary: '' summary: ''
}; };
@ -93,6 +96,7 @@ describe('ProjectService', () => {
createdAt: dummyDate, createdAt: dummyDate,
tester: 'Novatester', tester: 'Novatester',
summary: '', summary: '',
state: ReportState.NEW,
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}; };

View File

@ -0,0 +1,29 @@
<ng-container [ngSwitch]="reportState">
<nb-tag-list>
<!--Success Tags-->
<nb-tag *ngSwitchCase="state.RESOLVED" status="success" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase=" state.INFORMATIVE" status="success" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<!--Info Tags-->
<nb-tag *ngSwitchCase="state.NEW" status="info" appearance="filled"
text=" {{getTranslationKey() | translate}}"></nb-tag>
<!--Warning Tags-->
<nb-tag *ngSwitchCase="state.DUPLICATE" status="warning" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase="state.NOT_APPLICABLE" status="warning" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase="state.OUT_OF_SCOPE" status="warning" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase="state.ACCEPTED_RISK" status="warning" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<!--Danger Tags-->
<nb-tag *ngSwitchCase="state.NEEDS_MORE_INFO" status="danger" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase="state.SPAM" status="danger" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<!--Default Tags-->
<nb-tag *ngSwitchDefault status="basic" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
</nb-tag-list>
</ng-container>

View File

@ -0,0 +1,45 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReportStateTagComponent } from './report-state-tag.component';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NbCardModule, NbTagModule} from '@nebular/theme';
import {MockModule} from 'ng-mocks';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
describe('ReportStateTagComponent', () => {
let component: ReportStateTagComponent;
let fixture: ComponentFixture<ReportStateTagComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
ReportStateTagComponent
],
imports: [
HttpClientTestingModule,
NbCardModule,
MockModule(NbTagModule),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ReportStateTagComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,29 @@
import {ChangeDetectionStrategy, Component, Input, OnChanges, OnInit} from '@angular/core';
import {ReportState, ReportStateText, reportStateTexts} from '@shared/models/state.enum';
import {Severity} from '@shared/models/severity.enum';
@Component({
selector: 'app-report-state-tag',
templateUrl: './report-state-tag.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReportStateTagComponent implements OnChanges {
@Input() currentReportState: ReportState = ReportState.NEW;
// HTML only
state = ReportState;
reportState: any = 0;
readonly reportStateTexts: Array<ReportStateText> = reportStateTexts;
constructor() { }
ngOnChanges(): void {
this.reportState = typeof this.currentReportState !== 'number' ? ReportState[this.currentReportState] : this.currentReportState;
}
getTranslationKey(): string {
const index = this.reportStateTexts.findIndex(statusText => statusText.value === this.reportState);
return this.reportStateTexts[index].translationText;
}
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {NbTagModule} from '@nebular/theme';
import {TranslateModule} from '@ngx-translate/core';
import {ReportStateTagComponent} from '@shared/widgets/report-state-tag/report-state-tag.component';
@NgModule({
declarations: [
ReportStateTagComponent
],
imports: [
CommonModule,
NbTagModule,
TranslateModule
],
exports: [
ReportStateTagComponent
]
})
export class ReportStateTagModule { }

View File

@ -8,13 +8,13 @@ import {TranslateModule} from '@ngx-translate/core';
declarations: [ declarations: [
SeverityTagComponent SeverityTagComponent
], ],
exports: [
SeverityTagComponent
],
imports: [ imports: [
CommonModule, CommonModule,
NbTagModule, NbTagModule,
TranslateModule TranslateModule
],
exports: [
SeverityTagComponent
] ]
}) })
export class SeverityTagModule { } export class SeverityTagModule { }

View File

@ -21,6 +21,7 @@ data class Project(
val createdAt: String = Instant.now().toString(), val createdAt: String = Instant.now().toString(),
val tester: String, val tester: String,
val summary: String? = null, val summary: String? = null,
val state: PentestState,
var projectPentests: List<ProjectPentest> = emptyList(), var projectPentests: List<ProjectPentest> = emptyList(),
val createdBy: String val createdBy: String
) )
@ -33,6 +34,7 @@ fun buildProject(body: ProjectRequestBody, projectEntity: ProjectEntity): Projec
createdAt = projectEntity.data.createdAt, createdAt = projectEntity.data.createdAt,
tester = body.tester, tester = body.tester,
summary = body.summary, summary = body.summary,
state = body.state,
projectPentests = projectEntity.data.projectPentests, projectPentests = projectEntity.data.projectPentests,
createdBy = projectEntity.data.createdBy createdBy = projectEntity.data.createdBy
) )
@ -46,6 +48,7 @@ fun Project.toProjectResponseBody(): ResponseBody {
"createdAt" to createdAt, "createdAt" to createdAt,
"tester" to tester, "tester" to tester,
"summary" to summary, "summary" to summary,
"state" to state,
/* ToDo: Calculate percentage in BE type: float */ /* ToDo: Calculate percentage in BE type: float */
"testingProgress" to calculateProgress(), "testingProgress" to calculateProgress(),
"createdBy" to createdBy "createdBy" to createdBy
@ -119,6 +122,7 @@ data class ProjectRequestBody(
val client: String, val client: String,
val title: String, val title: String,
val tester: String, val tester: String,
val state: PentestState,
val summary: String? val summary: String?
) )
@ -132,6 +136,7 @@ fun ProjectRequestBody.isValid(): Boolean {
this.client.isBlank() -> false this.client.isBlank() -> false
this.title.isBlank() -> false this.title.isBlank() -> false
this.tester.isBlank() -> false this.tester.isBlank() -> false
this.state.toString().isBlank() -> false
else -> true else -> true
} }
} }
@ -144,6 +149,7 @@ fun ProjectRequestBody.toProject(): Project {
createdAt = Instant.now().toString(), createdAt = Instant.now().toString(),
tester = this.tester, tester = this.tester,
summary = this.summary, summary = this.summary,
state = this.state,
// ToDo: Should be changed to SUB from Token after adding AUTH Header // ToDo: Should be changed to SUB from Token after adding AUTH Header
createdBy = UUID.randomUUID().toString() createdBy = UUID.randomUUID().toString()
) )

View File

@ -19,6 +19,7 @@ fun ProjectEntity.toProject() : Project {
this.data.createdAt, this.data.createdAt,
this.data.tester, this.data.tester,
this.data.summary, this.data.summary,
this.data.state,
this.data.projectPentests, this.data.projectPentests,
this.data.createdBy this.data.createdBy
) )

View File

@ -0,0 +1,19 @@
package com.securityc4po.api.project
enum class PentestState {
NEW,
NEEDS_MORE_INFO,
// Report states depending on customer feedback
PRE_SUBMISSION,
PENDING,
TRIAGED,
RETESTING,
// Report states for closed submissions
RESOLVED,
INFORMATIVE,
DUPLICATE,
NOT_APPLICABLE,
SPAM,
OUT_OF_SCOPE,
ACCEPTED_RISK
}

View File

@ -6,6 +6,7 @@ import com.securityc4po.api.BaseDocumentationIntTest
import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR
import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC
import com.securityc4po.api.project.PentestState
import com.securityc4po.api.project.Project import com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity import com.securityc4po.api.project.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -256,6 +257,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
projectPentests = emptyList(), projectPentests = emptyList(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// Pentests // Pentests

View File

@ -5,6 +5,7 @@ import com.securityc4po.api.BaseIntTest
import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR
import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC
import com.securityc4po.api.project.PentestState
import com.securityc4po.api.project.Project import com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity import com.securityc4po.api.project.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -171,6 +172,7 @@ class PentestControllerIntTest : BaseIntTest() {
title = "Some Mock API (v1.0) Scanning", title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// pentests // pentests

View File

@ -10,6 +10,7 @@ import com.securityc4po.api.pentest.Pentest
import com.securityc4po.api.pentest.PentestCategory import com.securityc4po.api.pentest.PentestCategory
import com.securityc4po.api.pentest.PentestEntity import com.securityc4po.api.pentest.PentestEntity
import com.securityc4po.api.pentest.PentestStatus import com.securityc4po.api.pentest.PentestStatus
import com.securityc4po.api.project.PentestState
import com.securityc4po.api.project.Project import com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity import com.securityc4po.api.project.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -282,6 +283,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
projectPentests = emptyList(), projectPentests = emptyList(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// Pentests // Pentests

View File

@ -9,6 +9,7 @@ import com.securityc4po.api.pentest.Pentest
import com.securityc4po.api.pentest.PentestCategory import com.securityc4po.api.pentest.PentestCategory
import com.securityc4po.api.pentest.PentestEntity import com.securityc4po.api.pentest.PentestEntity
import com.securityc4po.api.pentest.PentestStatus import com.securityc4po.api.pentest.PentestStatus
import com.securityc4po.api.project.PentestState
import com.securityc4po.api.project.Project import com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity import com.securityc4po.api.project.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -179,6 +180,7 @@ class CommentControllerIntTest : BaseIntTest() {
title = "Some Mock API (v1.0) Scanning", title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// pentests // pentests

View File

@ -10,6 +10,7 @@ import com.securityc4po.api.pentest.Pentest
import com.securityc4po.api.pentest.PentestCategory import com.securityc4po.api.pentest.PentestCategory
import com.securityc4po.api.pentest.PentestEntity import com.securityc4po.api.pentest.PentestEntity
import com.securityc4po.api.pentest.PentestStatus import com.securityc4po.api.pentest.PentestStatus
import com.securityc4po.api.project.PentestState
import com.securityc4po.api.project.Project import com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity import com.securityc4po.api.project.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -340,6 +341,7 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
projectPentests = emptyList(), projectPentests = emptyList(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// Pentests // Pentests

View File

@ -9,6 +9,7 @@ import com.securityc4po.api.pentest.Pentest
import com.securityc4po.api.pentest.PentestCategory import com.securityc4po.api.pentest.PentestCategory
import com.securityc4po.api.pentest.PentestEntity import com.securityc4po.api.pentest.PentestEntity
import com.securityc4po.api.pentest.PentestStatus import com.securityc4po.api.pentest.PentestStatus
import com.securityc4po.api.project.PentestState
import com.securityc4po.api.project.Project import com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity import com.securityc4po.api.project.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -207,6 +208,7 @@ class FindingControllerIntTest: BaseIntTest() {
title = "Some Mock API (v1.0) Scanning", title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// pentests // pentests

View File

@ -75,6 +75,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.description("The user that is assigned as a tester in the project"), .description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("[].summary").type(JsonFieldType.STRING) PayloadDocumentation.fieldWithPath("[].summary").type(JsonFieldType.STRING)
.description("The summary of the requested project"), .description("The summary of the requested project"),
PayloadDocumentation.fieldWithPath("[].state").type(JsonFieldType.STRING)
.description("The state of the requested project pentest"),
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING) PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project"), .description("The id of the user that created the project"),
PayloadDocumentation.fieldWithPath("[].testingProgress").type(JsonFieldType.NUMBER) PayloadDocumentation.fieldWithPath("[].testingProgress").type(JsonFieldType.NUMBER)
@ -92,6 +94,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Novatester", tester = "Novatester",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
projectPentests = emptyList<ProjectPentest>(), projectPentests = emptyList<ProjectPentest>(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
val projectTwo = Project( val projectTwo = Project(
@ -102,6 +105,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Elliot", tester = "Elliot",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
projectPentests = emptyList<ProjectPentest>(), projectPentests = emptyList<ProjectPentest>(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
@ -144,6 +148,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.description("The date where the project was created at"), .description("The date where the project was created at"),
PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING) PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING)
.description("The user that is assigned as a tester in the project"), .description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
.description("The state of the requested project pentest"),
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING) PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project"), .description("The id of the user that created the project"),
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER) PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
@ -157,6 +163,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
client = "Novatec", client = "Novatec",
title = "log4j Pentest", title = "log4j Pentest",
tester = "Stipe", tester = "Stipe",
state = PentestState.NEW,
summary = "" summary = ""
) )
} }
@ -230,6 +237,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
projectPentests = emptyList<ProjectPentest>(), projectPentests = emptyList<ProjectPentest>(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
} }
@ -269,6 +277,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.description("The updated user that is assigned as a tester in the project"), .description("The updated user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("summary").type(JsonFieldType.STRING) PayloadDocumentation.fieldWithPath("summary").type(JsonFieldType.STRING)
.description("The summary of the requested project"), .description("The summary of the requested project"),
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
.description("The state of the requested project pentest"),
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING) PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project"), .description("The id of the user that created the project"),
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER) PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
@ -282,6 +292,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
client = "Novatec_updated", client = "Novatec_updated",
title = "log4j Pentest_updated", title = "log4j Pentest_updated",
tester = "Stipe_updated", tester = "Stipe_updated",
state = PentestState.NEW,
summary = "" summary = ""
) )
@ -292,6 +303,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Stipe_updated", tester = "Stipe_updated",
summary = "", summary = "",
state = PentestState.NEW,
projectPentests = emptyList<ProjectPentest>(), projectPentests = emptyList<ProjectPentest>(),
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
@ -306,6 +318,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
state = PentestState.NEW,
projectPentests = emptyList<ProjectPentest>(), projectPentests = emptyList<ProjectPentest>(),
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
@ -316,6 +329,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot", tester = "Elliot",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
state = PentestState.NEW,
projectPentests = emptyList<ProjectPentest>(), projectPentests = emptyList<ProjectPentest>(),
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )

View File

@ -72,6 +72,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
val projectTwo = Project( val projectTwo = Project(
@ -81,6 +82,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot", tester = "Elliot",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
@ -116,6 +118,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-04-10T18:05:00Z", createdAt = "2021-04-10T18:05:00Z",
tester = "Stipe", tester = "Stipe",
summary = "", summary = "",
state = PentestState.NEW,
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c" createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
) )
} }
@ -149,6 +152,7 @@ class ProjectControllerIntTest : BaseIntTest() {
title = "CashMyData (iOS)", title = "CashMyData (iOS)",
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot", tester = "Elliot",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
} }
@ -175,6 +179,7 @@ class ProjectControllerIntTest : BaseIntTest() {
title = "log4j Pentest_updated", title = "log4j Pentest_updated",
createdAt = "2021-04-10T18:05:00Z", createdAt = "2021-04-10T18:05:00Z",
tester = "Stipe_updated", tester = "Stipe_updated",
state = PentestState.NEW,
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c" createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
) )
} }
@ -188,6 +193,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester", tester = "Novatester",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
val projectTwo = Project( val projectTwo = Project(
@ -197,6 +203,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z", createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot", tester = "Elliot",
summary = "Lorem Ipsum", summary = "Lorem Ipsum",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
) )
// persist test data in database // persist test data in database

View File

@ -3,7 +3,7 @@
"$oid": "6405d84a13ae975803a098fa" "$oid": "6405d84a13ae975803a098fa"
}, },
"lastModified": { "lastModified": {
"$date": "2023-03-24T12:18:06.619Z" "$date": "2023-04-04T13:39:00.146Z"
}, },
"data": { "data": {
"_id": "575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2", "_id": "575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2",
@ -12,6 +12,7 @@
"createdAt": "2023-03-06T12:10:50.835664Z", "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", "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",
"state": "TRIAGED",
"projectPentests": [ "projectPentests": [
{ {
"pentestId": "54f3ce12-784a-4e44-b9b3-0a986119ec50", "pentestId": "54f3ce12-784a-4e44-b9b3-0a986119ec50",
@ -250,7 +251,7 @@
"$oid": "6405e92813ae975803a09905" "$oid": "6405e92813ae975803a09905"
}, },
"lastModified": { "lastModified": {
"$date": "2023-03-06T13:22:48.564Z" "$date": "2023-03-29T19:04:32.771Z"
}, },
"data": { "data": {
"_id": "d6e83738-4251-44ac-ad40-21b360780c98", "_id": "d6e83738-4251-44ac-ad40-21b360780c98",
@ -258,8 +259,14 @@
"title": "CashMyData (iOS)", "title": "CashMyData (iOS)",
"createdAt": "2023-03-06T13:22:48.564351Z", "createdAt": "2023-03-06T13:22:48.564351Z",
"tester": "Elliot", "tester": "Elliot",
"projectPentests": [], "projectPentests": [
"createdBy": "5f104d76-bd8d-4258-852a-d000c7f0666d" {
"pentestId": "a666322d-688c-45b2-bf34-dd7020ee71ac",
"status": "COMPLETED"
}
],
"createdBy": "5f104d76-bd8d-4258-852a-d000c7f0666d",
"state": "NEW"
}, },
"_class": "com.securityc4po.api.project.ProjectEntity" "_class": "com.securityc4po.api.project.ProjectEntity"
}] }]