fix: Bugfixes and QoL Improvements

This commit is contained in:
Marcel Haag 2022-11-16 12:49:53 +01:00
parent e9aec4ec3e
commit e406886863
16 changed files with 332 additions and 266 deletions

View File

@ -9,7 +9,8 @@
.export-button-container {
display: flex;
align-content: flex-end;
margin-right: 0.5rem;
// ToDo: Fix so that longer / shorter name won't change needed margin
margin-right: 2.25rem;
.export-element-icon {
}

View File

@ -49,6 +49,7 @@
size="small"
shape="round"
class="add-comment-button"
[disabled]="pentestInfo$.getValue().status === notStartedStatus"
(click)="onClickAddComment()">
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
{{'comment.add' | translate}}

View File

@ -6,6 +6,8 @@
.comment-cell {
// Add style here
height: 4.5rem !important;
max-height: 4.5rem !important;
}
.comment-cell:hover {

View File

@ -1,4 +1,4 @@
import {Component, Input, OnInit} from '@angular/core';
import {Component, OnInit} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs';
import {Pentest} from '@shared/models/pentest.model';
import * as FA from '@fortawesome/free-solid-svg-icons';
@ -9,6 +9,9 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {filter, tap} from 'rxjs/operators';
import {Comment, CommentEntry, transformCommentsToObjectiveEntries} from '@shared/models/comment.model';
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {Store} from '@ngxs/store';
import {PentestStatus} from '@shared/models/pentest-status.model';
@UntilDestroy()
@Component({
@ -18,11 +21,11 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
})
export class PentestCommentsComponent implements OnInit {
@Input()
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
// HTML only
readonly fa = FA;
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
@ -41,24 +44,39 @@ export class PentestCommentsComponent implements OnInit {
constructor(private readonly pentestService: PentestService,
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
private store: Store,
private notificationService: NotificationService) {
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
}
ngOnInit(): void {
this.loadCommentsData();
this.store.select(ProjectState.pentest).pipe(
untilDestroyed(this)
).subscribe({
next: (selectedPentest: Pentest) => {
this.pentestInfo$.next(selectedPentest);
this.loadCommentsData();
},
error: err => {
console.error(err);
}
});
}
loadCommentsData(): void {
this.pentestService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
.pipe(
untilDestroyed(this),
filter(isNotNullOrUndefined),
/*filter(isNotNullOrUndefined),*/
tap(() => this.loading$.next(true))
)
.subscribe({
next: (comments: Comment[]) => {
this.data = transformCommentsToObjectiveEntries(comments);
if (comments) {
this.data = transformCommentsToObjectiveEntries(comments);
} else {
this.data = [];
}
this.dataSource.setData(this.data, this.getters);
this.loading$.next(false);
},

View File

@ -6,48 +6,17 @@
</nb-tab>
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.findings' | translate }}"
badgeText="{{currentNumberOfFindings$.getValue()}}" badgeStatus="danger">
<app-pentest-findings [pentestInfo$]=pentest$></app-pentest-findings>
<app-pentest-findings></app-pentest-findings>
</nb-tab>
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
<app-pentest-comments [pentestInfo$]=pentest$></app-pentest-comments>
<app-pentest-comments></app-pentest-comments>
</nb-tab>
</nb-tabset>
</div>
<div fxLayoutAlign="end end" class="content-footer">
<!-- Pentest Status Selection -->
<div class="pentest-status-dialog">
<nb-select class="status"
[(selected)]="currentStatus"
shape="round" filled
status="{{getPentestFillStatus(currentStatus)}}">
<nb-option *ngFor="let status of statusTexts" [value]="status.value">
{{ status.translationText | translate }}
</nb-option>
</nb-select>
</div>
<div *ngIf="!pentest$.getValue().id; else updatePentest">
<button nbButton
class="save-pentest-button"
status="primary"
[disabled]="!pentestStatusChanged()"
title="{{ 'global.action.save' | translate }}"
(click)="onClickSavePentest()">
<span class="exit-element-text"> {{ 'global.action.save' | translate }} </span>
</button>
</div>
<ng-template #updatePentest>
<button nbButton
class="save-pentest-button"
status="primary"
[disabled]="!pentestStatusChanged()"
title="{{ 'global.action.update' | translate }}"
(click)="onClickUpdatePentest()">
<span class="exit-element-text"> {{ 'global.action.update' | translate }} </span>
</button>
</ng-template>
<!--ToDo: Use to put element in bottom-right corner -->
</div>
</div>

View File

@ -10,33 +10,9 @@
/*nb-tab {
position: fixed;
}*/
}
.content-footer {
height: 5%;
.pentest-status-dialog {
margin: 1rem 2.25rem 1rem 0;
.status {
width: 12rem;
}
.basic {
background-color: nb-theme(color-basic-default);
}
.info {
background-color: nb-theme(color-info-default);
}
.warning {
background-color: nb-theme(color-warning-default);
}
.success {
background-color: nb-theme(color-success-default);
}
}
.save-pentest-button {
.content-footer {
height: 5%;
margin: 1rem 6rem 1rem 0;
}
}

View File

@ -1,17 +1,12 @@
import {Component, OnInit} from '@angular/core';
import * as FA from '@fortawesome/free-solid-svg-icons';
import {BehaviorSubject, Observable} from 'rxjs';
import {Select, Store} from '@ngxs/store';
import {BehaviorSubject} from 'rxjs';
import {Store} from '@ngxs/store';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
import {Pentest} from '@shared/models/pentest.model';
import {PentestService} from '@shared/services/pentest.service';
import {NotificationService, PopupType} from '@shared/services/notification.service';
import {Project} from '@shared/models/project.model';
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
import {filter} from 'rxjs/operators';
import {NotificationService} from '@shared/services/notification.service';
@UntilDestroy()
@Component({
@ -23,28 +18,10 @@ export class PentestContentComponent implements OnInit {
// HTML only
readonly fa = FA;
@Select(ProjectState.project)
selectedProject$: Observable<Project>;
selectedProjectId: string;
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
currentNumberOfFindings$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
currentNumberOfComments$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
// Pentest Status Handler
currentStatus: PentestStatus = PentestStatus.NOT_STARTED;
private initialPentestStatus: PentestStatus;
status = PentestStatus;
readonly statusTexts: Array<StatusText> = [
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
/* ToDo: Disabled not needed inside pentest */
/*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/
{value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'},
{value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
];
constructor(
private readonly pentestService: PentestService,
private notificationService: NotificationService,
@ -52,26 +29,11 @@ export class PentestContentComponent implements OnInit {
}
ngOnInit(): void {
this.selectedProject$.pipe(
filter(isNotNullOrUndefined),
untilDestroyed(this)
).subscribe({
next: (project) => {
this.selectedProjectId = project.id;
},
error: (err) => {
console.error(err);
}
});
this.store.select(ProjectState.pentest).pipe(
untilDestroyed(this)
).subscribe({
next: (selectedPentest: Pentest) => {
console.warn(selectedPentest);
this.pentest$.next(selectedPentest);
this.currentStatus = selectedPentest.status;
this.initialPentestStatus = selectedPentest.status;
const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0;
this.currentNumberOfFindings$.next(findings);
const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0;
@ -82,78 +44,4 @@ export class PentestContentComponent implements OnInit {
}
});
}
onClickSavePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
this.pentestService.savePentest(this.selectedProjectId, transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.pentest$.next(pentest);
this.initialPentestStatus = pentest.status;
this.notificationService.showPopup('pentest.popup.save.success', PopupType.SUCCESS);
},
error: err => {
console.log(err);
this.notificationService.showPopup('pentest.popup.save.failed', PopupType.FAILURE);
}
});
}
onClickUpdatePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.pentest$.next(pentest);
this.initialPentestStatus = pentest.status;
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
},
error: err => {
console.log(err);
this.notificationService.showPopup('pentest.popup.update.failed', PopupType.FAILURE);
}
});
}
/**
* @return true if initial pentest Status is different from current pentest status
*/
pentestStatusChanged(): boolean {
if (this.initialPentestStatus !== this.currentStatus) {
this.pentestChanged$.next(true);
} else {
this.pentestChanged$.next(false);
}
return this.pentestChanged$.getValue();
}
/**
* @return the correct nb-status for current pentest-status
*/
getPentestFillStatus(value: PentestStatus): string {
let pentestFillStatus;
switch (value) {
case PentestStatus.NOT_STARTED: {
pentestFillStatus = 'basic';
break;
}
case PentestStatus.OPEN: {
pentestFillStatus = 'info';
break;
}
case PentestStatus.IN_PROGRESS: {
pentestFillStatus = 'warning';
break;
}
case PentestStatus.COMPLETED: {
pentestFillStatus = 'success';
break;
}
default: {
pentestFillStatus = 'basic';
break;
}
}
return pentestFillStatus;
}
}

View File

@ -1,7 +1,7 @@
import {Component, Input, OnInit} from '@angular/core';
import {Component, OnInit} from '@angular/core';
import {PentestService} from '@shared/services/pentest.service';
import {BehaviorSubject, Observable} from 'rxjs';
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
import {Pentest} from '@shared/models/pentest.model';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {filter, mergeMap, tap} from 'rxjs/operators';
import {NotificationService, PopupType} from '@shared/services/notification.service';
@ -18,6 +18,9 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
import {FindingDialogService} from '@shared/modules/finding-dialog/service/finding-dialog.service';
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {Store} from '@ngxs/store';
import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
import {ProjectState} from '@shared/stores/project-state/project-state';
@UntilDestroy()
@Component({
@ -30,16 +33,17 @@ export class PentestFindingsComponent implements OnInit {
constructor(private readonly pentestService: PentestService,
private dataSourceBuilder: NbTreeGridDataSourceBuilder<FindingEntry>,
private notificationService: NotificationService,
private findingDialogService: FindingDialogService) {
private findingDialogService: FindingDialogService,
private store: Store) {
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
}
@Input()
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
// HTML only
readonly fa = FA;
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
columns: Array<FindingColumns> = [
FindingColumns.FINDING_ID, FindingColumns.SEVERITY, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.ACTIONS
@ -54,27 +58,40 @@ export class PentestFindingsComponent implements OnInit {
expandedGetter: (node: FindingEntry) => !!node.expanded,
};
// HTML only
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
ngOnInit(): void {
this.loadFindingsData();
this.store.select(ProjectState.pentest).pipe(
untilDestroyed(this)
).subscribe({
next: (selectedPentest: Pentest) => {
this.pentestInfo$.next(selectedPentest);
this.loadFindingsData();
},
error: err => {
console.error(err);
}
});
}
loadFindingsData(): void {
this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
.pipe(
untilDestroyed(this),
filter(isNotNullOrUndefined),
/*filter(isNotNullOrUndefined),*/
tap(() => this.loading$.next(true))
)
.subscribe({
next: (findings: Finding[]) => {
this.data = transformFindingsToObjectiveEntries(findings);
// ToDo: Handle this case before in pipe
if (findings) {
this.data = transformFindingsToObjectiveEntries(findings);
} else {
this.data = [];
}
this.dataSource.setData(this.data, this.getters);
this.loading$.next(false);
},
error: err => {
console.warn('Error');
console.error(err);
// ToDo: Implement again after proper lazy loading and routing
// this.notificationService.showPopup('findings.popup.not.found', PopupType.FAILURE);
@ -104,8 +121,8 @@ export class PentestFindingsComponent implements OnInit {
),
untilDestroyed(this)
).subscribe({
next: () => {
// ToDo: Parse new Counter to overview / -> dispatch to store maybe already update it
next: (finding) => {
this.store.dispatch(new UpdatePentestFindings(finding.id));
this.loadFindingsData();
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
},

View File

@ -14,7 +14,41 @@
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
<div class="pentest-status-container">
<app-status-tag [currentStatus]="pentest$.getValue().status"></app-status-tag>
<!--ToDo: Add changing dialog here-->
<!--<app-status-tag [currentStatus]="pentest$.getValue().status"></app-status-tag>-->
<!-- Pentest Status Selection -->
<div class="pentest-status-dialog">
<nb-select class="status"
[(selected)]="currentStatus"
shape="round" filled
status="{{getPentestFillStatus(currentStatus)}}">
<nb-option *ngFor="let status of statusTexts" [value]="status.value">
{{ status.translationText | translate }}
</nb-option>
</nb-select>
</div>
<div *ngIf="!pentest$.getValue().id; else updatePentest">
<button nbButton
class="save-pentest-button"
status="primary"
[disabled]="!pentestStatusChanged()"
title="{{ 'global.action.save' | translate }}"
(click)="onClickSavePentest()">
<span class="exit-element-text"> {{ 'global.action.save' | translate }} </span>
</button>
</div>
<ng-template #updatePentest>
<button nbButton
class="save-pentest-button"
status="primary"
[disabled]="!pentestStatusChanged()"
title="{{ 'global.action.update' | translate }}"
(click)="onClickUpdatePentest()">
<span class="exit-element-text"> {{ 'global.action.update' | translate }} </span>
</button>
</ng-template>
</div>
</div>

View File

@ -4,6 +4,7 @@
.exit-button-container {
.exit-element-icon {
}
.exit-element-text {
padding-left: 0.5rem;
}
@ -12,6 +13,35 @@
.pentest-status-container {
display: flex;
align-content: flex-end;
margin-right: 0.5rem;
// margin-right: 0.5rem;
// height: 5%;
.pentest-status-dialog {
margin: 1rem 2.25rem 1rem 0;
.status {
width: 12rem;
}
.basic {
background-color: nb-theme(color-basic-default);
}
.info {
background-color: nb-theme(color-info-default);
}
.warning {
background-color: nb-theme(color-warning-default);
}
.success {
background-color: nb-theme(color-success-default);
}
}
.save-pentest-button {
margin: 1rem 0 1rem 0;
}
}
}

View File

@ -11,8 +11,10 @@ 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 {NotificationService} from '@shared/services/notification.service';
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -58,6 +60,9 @@ describe('PentestHeaderComponent', () => {
}),
RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([ProjectState])
],
providers: [
{provide: NotificationService, useValue: new NotificationServiceMock()},
]
})
.compileComponents();

View File

@ -6,10 +6,13 @@ import {Store} from '@ngxs/store';
import {Router} from '@angular/router';
import {ChangePentest} from '@shared/stores/project-state/project-state.actions';
import {BehaviorSubject} from 'rxjs';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {Project} from '@shared/models/project.model';
import {Pentest} from '@shared/models/pentest.model';
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
import {NotificationService, PopupType} from '@shared/services/notification.service';
import {PentestStatus} from '@shared/models/pentest-status.model';
import {PentestService} from '@shared/services/pentest.service';
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
@UntilDestroy()
@Component({
@ -23,8 +26,27 @@ export class PentestHeaderComponent implements OnInit {
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('');
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
// Pentest Status Handler
status = PentestStatus;
currentStatus: PentestStatus = PentestStatus.NOT_STARTED;
private initialPentestStatus: PentestStatus;
// Status Text Translation Texts
readonly statusTexts: Array<StatusText> = [
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
/* ToDo: Disabled not needed inside pentest */
/*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/
{value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'},
{value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
];
selectedProjectId$: BehaviorSubject<string> = new BehaviorSubject<string>('');
constructor(private store: Store,
private pentestService: PentestService,
private notificationService: NotificationService,
private readonly router: Router) {
}
@ -33,6 +55,7 @@ export class PentestHeaderComponent implements OnInit {
untilDestroyed(this)
).subscribe({
next: (selectedProject: Project) => {
this.selectedProjectId$.next(selectedProject.id);
this.selectedProjectTitle$.next(selectedProject?.title);
},
error: err => {
@ -44,6 +67,8 @@ export class PentestHeaderComponent implements OnInit {
untilDestroyed(this)
).subscribe({
next: (selectedPentest: Pentest) => {
this.currentStatus = selectedPentest.status;
this.initialPentestStatus = selectedPentest.status;
this.pentest$.next(selectedPentest);
},
error: err => {
@ -61,4 +86,76 @@ export class PentestHeaderComponent implements OnInit {
}
).finally();
}
onClickSavePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.store.dispatch(new ChangePentest(pentest));
this.notificationService.showPopup('pentest.popup.save.success', PopupType.SUCCESS);
},
error: err => {
console.log(err);
this.notificationService.showPopup('pentest.popup.save.failed', PopupType.FAILURE);
}
});
}
onClickUpdatePentest(): void {
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue()))
.subscribe({
next: (pentest: Pentest) => {
this.store.dispatch(new ChangePentest(pentest));
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
},
error: err => {
console.log(err);
this.notificationService.showPopup('pentest.popup.update.failed', PopupType.FAILURE);
}
});
}
/**
* @return true if initial pentest Status is different from current pentest status
*/
pentestStatusChanged(): boolean {
if (this.initialPentestStatus !== this.currentStatus) {
this.pentestChanged$.next(true);
} else {
this.pentestChanged$.next(false);
}
return this.pentestChanged$.getValue();
}
/**
* @return the correct nb-status for current pentest-status
*/
getPentestFillStatus(value: PentestStatus): string {
let pentestFillStatus;
switch (value) {
case PentestStatus.NOT_STARTED: {
pentestFillStatus = 'basic';
break;
}
case PentestStatus.OPEN: {
pentestFillStatus = 'info';
break;
}
case PentestStatus.IN_PROGRESS: {
pentestFillStatus = 'warning';
break;
}
case PentestStatus.COMPLETED: {
pentestFillStatus = 'success';
break;
}
default: {
pentestFillStatus = 'basic';
break;
}
}
return pentestFillStatus;
}
}

View File

@ -8,8 +8,7 @@ import {Store} from '@ngxs/store';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {catchError, map, switchMap} from 'rxjs/operators';
import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function';
import {Finding, FindingDialogBody} from '@shared/models/finding.model';
import {Severity} from '@shared/models/severity.enum';
import {Finding} from '@shared/models/finding.model';
import {Comment} from '@shared/models/comment.model';
import {v4 as UUID} from 'uuid';
@ -85,46 +84,43 @@ export class PentestService {
* @param pentestId the id of the project
*/
public getFindingsByPentestId(pentestId: string): Observable<Finding[]> {
if (pentestId) {
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
} else {
// return of([]);
// Todo: Remove mocked Findings
return of([
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This impacts only the UI',
severity: Severity.LOW,
reproduction: ''
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This is impacts some things',
severity: Severity.MEDIUM,
reproduction: ''
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This is impacts a lot',
severity: Severity.HIGH,
reproduction: ''
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This is impacts a lot',
severity: Severity.CRITICAL,
reproduction: ''
}
]);
}
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
// return of([]);
/*Todo: Remove mocked Findings?
return of([
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This impacts only the UI',
severity: Severity.LOW,
reproduction: ''
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This is impacts some things',
severity: Severity.MEDIUM,
reproduction: ''
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This is impacts a lot',
severity: Severity.HIGH,
reproduction: ''
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'test',
impact: 'This is impacts a lot',
severity: Severity.CRITICAL,
reproduction: ''
}
]);*/
}
/**
@ -141,26 +137,22 @@ export class PentestService {
* @param pentestId the id of the project
*/
public getCommentsByPentestId(pentestId: string): Observable<Comment[]> {
console.warn('Comments for:', pentestId);
if (pentestId) {
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
} else {
// return of([]);
// Todo: Remove mocked Comments
return of([
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd2',
title: 'This is a creative title',
description: 'This is a creative description',
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'This is a creative description',
relatedFindings: [],
}
]);
}
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
// return of([]);
/* ToDo: Use mocked Comments?
return of([
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd2',
title: 'This is a creative title',
description: 'This is a creative description',
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a creative title',
description: 'This is a creative description',
relatedFindings: [],
}
]);*/
}
}

View File

@ -33,3 +33,10 @@ export class ChangePentest {
constructor(public pentest: Pentest) {
}
}
export class UpdatePentestFindings {
static readonly type = '[ProjectState] UpdatePentestFindings';
constructor(public findingId: string) {
}
}

View File

@ -1,7 +1,13 @@
import {Action, Selector, State, StateContext} from '@ngxs/store';
import {Injectable} from '@angular/core';
import {Project} from '@shared/models/project.model';
import {ChangeCategory, ChangePentest, ChangeProject, InitProjectState} from '@shared/stores/project-state/project-state.actions';
import {
ChangeCategory,
ChangePentest,
ChangeProject,
InitProjectState,
UpdatePentestFindings
} from '@shared/stores/project-state/project-state.actions';
import {Category} from '@shared/models/category.model';
import {Pentest} from '@shared/models/pentest.model';
@ -80,4 +86,26 @@ export class ProjectState {
selectedPentest: {...pentest, projectId: state.selectedProject.id}
});
}
@Action(UpdatePentestFindings)
updatePentestFindings(ctx: StateContext<ProjectStateModel>, {findingId}: UpdatePentestFindings): void {
const state = ctx.getState();
let stateSelectedPentest: Pentest = state.selectedPentest;
const stateFindingIds: Array<string> = stateSelectedPentest.findingIds || [];
let updatedFindingIds: Array<string> = [];
if (!stateFindingIds.includes(findingId)) {
updatedFindingIds = [...stateFindingIds, findingId];
} else {
// ToDo: Add logic to remove findingId from array
}
// overwrites only findingIds
stateSelectedPentest = {
...stateSelectedPentest,
findingIds: updatedFindingIds
};
// path project state
ctx.patchState({
selectedPentest: stateSelectedPentest
});
}
}

View File

@ -45,6 +45,7 @@ class ProjectController(private val projectService: ProjectService) {
@DeleteMapping("/{id}")
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
// ToDo: Delete all associated Pentests, Findings and Comments
return this.projectService.deleteProject(id).map{
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
}.switchIfEmpty {