Compare commits

...

1 Commits

Author SHA1 Message Date
Marcel Haag 99154bf680 feat: As an user I want to add the pentest status of a project 2023-04-05 18:29:26 +02:00
45 changed files with 823 additions and 204 deletions

View File

@ -9,6 +9,7 @@
</button>
</div>
<app-report-state-tag class="state-tag" [currentReportState]="selectedProject$.getValue()?.state"></app-report-state-tag>
<h4>{{selectedProject$.getValue().title}}</h4>
<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 {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 {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -33,6 +34,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

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

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 {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
@NgModule({
declarations: [
@ -32,32 +33,33 @@ import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget
ObjectiveCategoriesComponent,
ObjectiveTableComponent,
],
imports: [
CommonModule,
CommonAppModule,
NbLayoutModule,
NbCardModule,
NbButtonModule,
// nbTooltip crashes app right now if used in component,
// workaround: use title in html for now
NbTooltipModule,
NbTreeGridModule,
TranslateModule,
StatusTagModule,
RouterModule,
FormsModule,
NbListModule,
FontAwesomeModule,
FlexLayoutModule,
NbActionsModule,
ExportReportDialogModule,
ProjectDialogModule,
ObjectiveOverviewRoutingModule,
// Table Widgets
FindigWidgetModule,
CommentWidgetModule,
NbMenuModule
],
imports: [
CommonModule,
CommonAppModule,
NbLayoutModule,
NbCardModule,
NbButtonModule,
// nbTooltip crashes app right now if used in component,
// workaround: use title in html for now
NbTooltipModule,
NbTreeGridModule,
TranslateModule,
StatusTagModule,
RouterModule,
FormsModule,
NbListModule,
FontAwesomeModule,
FlexLayoutModule,
NbActionsModule,
ExportReportDialogModule,
ProjectDialogModule,
ObjectiveOverviewRoutingModule,
// Table Widgets
FindigWidgetModule,
CommentWidgetModule,
NbMenuModule,
ReportStateTagModule
],
exports: [
ObjectiveHeaderComponent,
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 {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
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 = {
selectedProject: {
@ -31,6 +32,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
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 {NotificationService} from '@shared/services/toaster-service/notification.service';
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -22,6 +23,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
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 {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -31,6 +32,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
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 {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -22,6 +23,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
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 {NotificationService} from '@shared/services/toaster-service/notification.service';
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -22,6 +23,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -1,87 +1,131 @@
<div fxLayout="row" fxLayoutGap="2rem">
<div *ngFor="let project of projects$ | async">
<nb-card class="project-card" accent="success">
<nb-card-header fxLayoutAlign="start center"
routerLink="id"
fragment="{{project.id}}"
class="project-link project-header"
(click)="onClickRouteToProject(project)">
<h4>{{project?.title}}</h4>
</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">
<div fxFlex="0 1 max-content" fxLayout="column" class="pentest-overview">
<nb-layout fxFlex>
<!--Header-->
<nb-layout-header class="pentest-overview-header">
<div fxLayout="row" fxLayoutGap="2rem" fxLayoutAlign="space-between center">
<!--Filter-->
<div fxLayout="row" fxLayoutGap="1rem" class="header-filer">
<!--Actions-->
<!--ToDo: Add searchbar that filters for title, client, tester-->
<form class="project-filter-input">
<nb-form-field>
<fa-icon nbPrefix class ="search-prefix-icon" [icon]="fa.faSearch"></fa-icon>
<input type="text" required
fullWidth nbInput
[formControl]="projectSearch"
placeholder="{{ 'project.filter.placeholder' | translate }}"
shape="semi-round"
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}}
</span>
<p class="project-subheader">
{{'project.tester' | translate}}:
</p>
<span class="project-paragraph">
<p class="project-subheader">
{{'project.tester' | translate}}:
</p>
<span class="project-paragraph">
{{project?.tester}}
</span>
<p class="project-subheader">
{{'project.createdAt' | translate}}:
</p>
<span class="project-paragraph">
<p class="project-subheader">
{{'project.createdAt' | translate}}:
</p>
<span class="project-paragraph">
{{project?.createdAt | dateTimeFormat}}
</span>
</nb-card-body>
<nb-card-footer>
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
<div class="project-progress">
<nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar"
status="warning"
[value]="project.testingProgress"
[displayValue]="true">
</nb-progress-bar>
<ng-template #altProgressBar>
{{'popup.info' | translate}} {{'global.no.progress' | translate}}
</ng-template>
</div>
</nb-card-body>
<nb-card-footer>
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
<div class="project-progress">
<nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar"
status="warning"
[value]="project.testingProgress"
[displayValue]="true">
</nb-progress-bar>
<ng-template #altProgressBar>
{{'popup.info' | translate}} {{'global.no.progress' | translate}}
</ng-template>
</div>
<button nbButton
status="primary"
size="small"
class="project-button"
(click)="onClickEditProject(project)">
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
</button>
<button nbButton
status="danger"
size="small"
class="project-button"
(click)="onClickDeleteProject(project)">
<fa-icon [icon]="fa.faTrash"></fa-icon>
</button>
<button nbButton
status="primary"
size="small"
class="project-button"
(click)="onClickEditProject(project)">
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
</button>
<button nbButton
status="danger"
size="small"
class="project-button"
(click)="onClickDeleteProject(project)">
<fa-icon [icon]="fa.faTrash"></fa-icon>
</button>
</div>
</nb-card-footer>
</nb-card>
</div>
</nb-card-footer>
</nb-card>
</div>
</div>
</div>
<!--Error Text-->
<div *ngIf="projects$.getValue() == null || projects$.getValue().length === 0 && loading$.getValue() === false"
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">
<p class="error-text">
{{'project.overview.no.projects' | translate}}
</p>
</nb-layout-column>
</nb-layout>
</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/variables';
.project-card {
max-width: 22rem;
width: 22rem;
min-width: 20rem;
max-height: 100%;
height: 100%;
min-height: 100%;
.pentest-overview {
width: 100vw;
height: 80vh;
// ToDo: Disable and fix scrolling
overflow: hidden;
.project-header {
max-height: 8rem;
height: 8rem;
min-height: 6rem;
.pentest-overview-header {
width: 100vw;
.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 {
font-size: 1.25rem;
font-weight: bold;
}
.pentest-overview-column {
width: 100vw;
.project-paragraph {
font-size: 1.15rem;
font-style: italic;
}
.project-card {
max-width: 22rem;
width: 22rem;
min-width: 20rem;
max-height: 100%;
height: 100%;
min-height: 100%;
.project-progress {
max-width: 65%;
width: 65%;
min-width: 65%;
}
.project-header {
max-height: 8rem;
height: 8rem;
min-height: 6rem;
.project-button {
height: 1.425rem;
.header-title {
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 {ProjectService} from '@shared/services/api/project.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 {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
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 {InitProjectState} from '@shared/stores/project-state/project-state.actions';
import {Store} from '@ngxs/store';
import {ReportState} from '@shared/models/state.enum';
import {FormControl} from '@angular/forms';
@UntilDestroy()
@Component({
@ -26,6 +28,11 @@ export class ProjectOverviewComponent implements OnInit {
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
projects$: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
allProjects$: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
// Search
projectSearch: FormControl;
protected filter$: Observable<string>;
constructor(
private readonly notificationService: NotificationService,
@ -38,6 +45,9 @@ export class ProjectOverviewComponent implements OnInit {
ngOnInit(): void {
this.loadProjects();
// Setup Search
this.projectSearch = new FormControl({value: '', disabled: !this.allProjects$.getValue()});
this.setFilterObserverForProjects();
}
loadProjects(): void {
@ -49,6 +59,7 @@ export class ProjectOverviewComponent implements OnInit {
.subscribe({
next: (projects: Project[]) => {
this.projects$.next(projects);
this.allProjects$.next(projects);
this.loading$.next(false);
},
error: err => {
@ -156,6 +167,74 @@ export class ProjectOverviewComponent implements OnInit {
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 {
this.projectService.deleteProjectById(project.id).pipe(
untilDestroyed(this)

View File

@ -2,7 +2,15 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ProjectOverviewComponent} from './project-overview.component';
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 {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
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 {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module';
import {RouterModule} from '@angular/router';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
@ -34,7 +44,13 @@ import {RouterModule} from '@angular/router';
TranslateModule,
ProjectDialogModule,
ConfirmDialogModule,
SecurityConfirmDialogModule
ReportStateTagModule,
SecurityConfirmDialogModule,
NbLayoutModule,
NbInputModule,
NbFormFieldModule,
ReactiveFormsModule,
NbSelectModule
]
})
export class ProjectOverviewModule {

View File

@ -8,7 +8,6 @@ import {HttpLoaderFactory} from '../../common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing';
import {NgxsModule, Store} from '@ngxs/store';
import {SessionState} from '@shared/stores/session-state/session-state';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NbCardModule, NbDialogRef, NbLayoutModule} from '@nebular/theme';
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 {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 {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -36,6 +36,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -52,6 +52,21 @@
"failed": "Benutzername oder Passwort falsch",
"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": {
"dialog": {
"header": "Penetrationstestbereicht exportieren",
@ -69,17 +84,22 @@
"title.label": "Projekt Titel",
"client.label": "Name des Auftraggebers",
"tester.label": "Name des Pentester",
"state.label": "Penteststatus",
"summary.label": "Zusammenfassung",
"summary.placeholder": "Sollte eine Zusammenfassung, einen Ansatz, einen Umfang und eine Bewertungsübersicht sowie allgemeine Empfehlungen enthalten",
"title": "Titel",
"client": "Klient",
"tester": "Tester",
"state": "Penteststatus",
"summary": "Zusammenfassung",
"createdAt": "Erstellt am",
"overview": {
"add.project": "Projekt hinzufügen",
"no.projects": "Keine Projekte verfügbar"
},
"filter": {
"placeholder": "Projekt suchen"
},
"create": {
"header": "Neues Projekt erstellen"
},
@ -96,6 +116,7 @@
"titleRequired": "Titel ist erforderlich.",
"clientRequired": "Klient ist erforderlich.",
"testerRequired": "Tester ist erforderlich.",
"stateRequired": "Status ist erforderlich.",
"summaryRequired": "Zusammenfassung ist erforderlich."
},
"popup": {

View File

@ -52,6 +52,21 @@
"failed": "Wrong username or password",
"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": {
"dialog": {
"header": "Export Penetrationtest Report",
@ -69,17 +84,22 @@
"title.label": "Project Title",
"client.label": "Name of Client",
"tester.label": "Name of Pentester",
"state.label": "State of Pentest",
"summary.label": "Summary",
"summary.placeholder": "Should include Executive Summary, Approach, Scope and Assessment Overview as well as General Recommendations",
"title": "Title",
"client": "Client",
"tester": "Tester",
"state": "State of Pentest",
"summary": "Summary",
"createdAt": "Created at",
"overview": {
"add.project": "Add Project",
"no.projects": "No projects available"
},
"filter": {
"placeholder": "Search for project"
},
"create": {
"header": "Create New Project"
},
@ -96,6 +116,7 @@
"titleRequired": "Title is required.",
"clientRequired": "Client is required.",
"testerRequired": "Tester is required.",
"stateRequired": "State is required.",
"summaryRequired": "Summary is required."
},
"popup": {

View File

@ -1,4 +1,5 @@
import {PentestStatus} from '@shared/models/pentest-status.model';
import {ReportState} from '@shared/models/state.enum';
export class Project {
id: string;
@ -7,6 +8,7 @@ export class Project {
createdAt: Date;
tester: string;
summary: string;
state: ReportState;
projectPentests?: Array<ProjectPentests>;
testingProgress?: number;
createdBy: string;
@ -16,6 +18,7 @@ export class Project {
title: string,
createdAt: Date,
tester: string,
state: ReportState,
projectPentests?: Array<ProjectPentests>,
testingProgress?: number,
summary?: string,
@ -28,14 +31,28 @@ export class Project {
this.projectPentests = projectPentests;
this.testingProgress = testingProgress;
this.summary = summary;
this.state = state;
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 {
title: string;
client: string;
tester: string;
state: ReportState;
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 {Severity} from '@shared/models/severity.enum';
import {Comment} from '@shared/models/comment.model';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -41,6 +42,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
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 {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {ReportState} from '@shared/models/state.enum';
describe('ExportReportDialogComponent', () => {
let component: ExportReportDialogComponent;
@ -102,6 +103,7 @@ const mockProject: Project = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
projectPentests: mockedPentests,
testingProgress: 0,
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 {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
import {NgxsModule, Store} from '@ngxs/store';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
@ -39,6 +40,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},
@ -97,13 +99,13 @@ describe('FindingDialogComponent', () => {
{provide: NotificationService, useValue: new NotificationServiceMock()},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NbDialogRef, useValue: dialogSpy},
{provide: NB_DIALOG_CONFIG, useValue: mockedCommentDialogData}
{provide: NB_DIALOG_CONFIG, useValue: mockedFindingDialogData}
]
}).compileComponents();
});
beforeEach(() => {
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedCommentDialogData});
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedFindingDialogData});
fixture = TestBed.createComponent(FindingDialogComponent);
store = TestBed.inject(Store);
store.reset({
@ -138,7 +140,7 @@ export const mockFinding: Finding = {
mitigation: 'Mitigation Test'
};
export const mockedCommentDialogData = {
export const mockedFindingDialogData = {
form: {
findingTitle: {
fieldName: 'findingTitle',

View File

@ -1,6 +1,18 @@
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="project-dialog">
<nb-card-header fxLayoutAlign="start center" class="dialog-header">
{{ 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-body>
<form *ngIf="formArray" [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem"

View File

@ -2,11 +2,11 @@
@import '../../../assets/@theme/styles/themes';
.project-dialog {
width: 34rem !important;
width: 36rem !important;
height: 43.5rem;
.project-dialog-header {
height: 8vh;
height: 10vh;
font-size: 1.5rem;
}
@ -21,19 +21,19 @@
}
.form-field {
width: 26.75rem;
width: 32.75rem;
// width: 30rem !important;
margin-bottom: 0.5rem;
}
.form-textarea {
width: 26.75rem !important;
width: 32.75rem !important;
// width: 30rem !important;
height: 8rem;
}
.form-textarea:disabled {
width: 26.75rem !important;
width: 32.75rem !important;
// width: 30rem !important;
background-color: nb-theme(color-basic-transparent-focus);
height: 8rem;
@ -43,4 +43,14 @@
float: left;
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,
NbFormFieldModule,
NbInputModule,
NbLayoutModule
NbLayoutModule, NbSelectModule
} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
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 {Project} from '@shared/models/project.model';
import Mock = jest.Mock;
import {ReportState} from '@shared/models/state.enum';
import {GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
describe('ProjectDialogComponent', () => {
let component: ProjectDialogComponent;
@ -43,6 +45,7 @@ describe('ProjectDialogComponent', () => {
NbButtonModule,
FlexLayoutModule,
NbInputModule,
NbSelectModule,
NbFormFieldModule,
ReactiveFormsModule,
BrowserAnimationsModule,
@ -70,7 +73,8 @@ describe('ProjectDialogComponent', () => {
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedProjectDialogData});
fixture = TestBed.createComponent(ProjectDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// ToDo: fix detectChanges() when controlsConfig is defined
// fixture.detectChanges();
});
it('should create', () => {
@ -91,7 +95,8 @@ export const mockProject: Project = {
title: 'Test Project',
client: 'Testclient',
tester: 'Testpentester',
summary: '',
summary: 'Test',
state: ReportState.NEW,
createdAt: new Date(),
testingProgress: 0,
createdBy: 'UID-11-12-13'
@ -137,13 +142,27 @@ export const mockedProjectDialogData = {
errors: [
{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: [
{
headerLabelKey: 'project.edit.header',
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 {ProjectService} from '@shared/services/api/project.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()
@Component({
@ -21,6 +24,11 @@ export class ProjectDialogComponent implements OnInit {
dialogData: GenericDialogData;
// HTML only
readonly fa = FA;
state: ReportState = ReportState.NEW;
readonly reportStateTexts = reportStateTexts;
constructor(
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
private fb: FormBuilder,
@ -63,6 +71,43 @@ export class ProjectDialogComponent implements OnInit {
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
*/
@ -98,9 +143,12 @@ export class ProjectDialogComponent implements OnInit {
title: value.projectTitle,
client: value.projectClient,
tester: value.projectTester,
state: this.formArray[4].controlsConfig[0].value,
summary: value.projectSummary
};
this.projectService.saveProject(dialogRes).pipe(
this.projectService.saveProject(
transformProjectToRequestBody(dialogRes)
).pipe(
untilDestroyed(this)
).subscribe(
{
@ -122,15 +170,19 @@ export class ProjectDialogComponent implements OnInit {
title: value.projectTitle,
client: value.projectClient,
tester: value.projectTester,
state: this.formArray[4].controlsConfig[0].value,
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)
).subscribe(
{
next: () => {
next: (project: Project) => {
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
this.dialogRef.close();
this.dialogRef.close(project);
},
error: err => {
console.error(err);

View File

@ -1,7 +1,7 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
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 {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core';
@ -13,18 +13,19 @@ import {ReactiveFormsModule} from '@angular/forms';
declarations: [
ProjectDialogComponent
],
imports: [
CommonModule,
CommonAppModule,
NbCardModule,
NbButtonModule,
NbFormFieldModule,
NbInputModule,
FlexLayoutModule,
FontAwesomeModule,
TranslateModule,
ReactiveFormsModule,
],
imports: [
CommonModule,
CommonAppModule,
NbCardModule,
NbButtonModule,
NbFormFieldModule,
NbInputModule,
FlexLayoutModule,
FontAwesomeModule,
TranslateModule,
ReactiveFormsModule,
NbSelectModule,
],
providers: [
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 {Validators} from '@angular/forms';
import {GenericDialogData} from '@shared/models/generic-dialog-data';
import {ReportState} from '@shared/models/state.enum';
@Injectable()
export class ProjectDialogService {
@ -33,6 +34,11 @@ export class ProjectDialogService {
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
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
dialogData = {
form: {
@ -87,7 +93,20 @@ export class ProjectDialogService {
errors: [
{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: []
};

View File

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

View File

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

View File

@ -19,6 +19,7 @@ fun ProjectEntity.toProject() : Project {
this.data.createdAt,
this.data.tester,
this.data.summary,
this.data.state,
this.data.projectPentests,
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.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
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.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -256,6 +257,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
projectPentests = emptyList(),
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// 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.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
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.ProjectEntity
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
@ -171,6 +172,7 @@ class PentestControllerIntTest : BaseIntTest() {
title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
state = PentestState.NEW,
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// pentests

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
"$oid": "6405d84a13ae975803a098fa"
},
"lastModified": {
"$date": "2023-03-24T12:18:06.619Z"
"$date": "2023-04-04T13:39:00.146Z"
},
"data": {
"_id": "575dd9d4-cb3c-4df3-981e-8a18bf8dc1d2",
@ -12,6 +12,7 @@
"createdAt": "2023-03-06T12:10:50.835664Z",
"tester": "Jojo",
"summary": "This report includes an Executeive Summary, the rules in regards to the scope of the pentest and the choosen approach of the pentester.\nDio Stonemask Inc. contracted Jojo to perform a Penetration Test to identify security weaknesses,\ndetermine the impact to Dio Stonemask Inc., document all findings in a clear and repeatable manner,\nand provide remediation recommendations",
"state": "TRIAGED",
"projectPentests": [
{
"pentestId": "54f3ce12-784a-4e44-b9b3-0a986119ec50",
@ -250,7 +251,7 @@
"$oid": "6405e92813ae975803a09905"
},
"lastModified": {
"$date": "2023-03-06T13:22:48.564Z"
"$date": "2023-03-29T19:04:32.771Z"
},
"data": {
"_id": "d6e83738-4251-44ac-ad40-21b360780c98",
@ -258,8 +259,14 @@
"title": "CashMyData (iOS)",
"createdAt": "2023-03-06T13:22:48.564351Z",
"tester": "Elliot",
"projectPentests": [],
"createdBy": "5f104d76-bd8d-4258-852a-d000c7f0666d"
"projectPentests": [
{
"pentestId": "a666322d-688c-45b2-bf34-dd7020ee71ac",
"status": "COMPLETED"
}
],
"createdBy": "5f104d76-bd8d-4258-852a-d000c7f0666d",
"state": "NEW"
},
"_class": "com.securityc4po.api.project.ProjectEntity"
}]