feat: As a user I want to have an comments overview

This commit is contained in:
Marcel Haag 2022-10-24 15:25:09 +02:00
parent 747cade495
commit 14828e5098
18 changed files with 497 additions and 36 deletions

View File

@ -1 +1,83 @@
<p>pentest-comments works!</p> <div class="comment-table">
<table [nbTreeGrid]="dataSource">
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
<tr nbTreeGridRow *nbTreeGridRowDef="let comment; columns: columns"
class="comment-cell"
fragment="{{comment.data['commentId']}}">
</tr>
<!-- Comment ID -->
<ng-container [nbTreeGridColumnDef]="columns[0]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.commentId' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
{{ comment.data['commentId'] || '-' }}
</td>
</ng-container>
<!-- Title -->
<ng-container [nbTreeGridColumnDef]="columns[1]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.title' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
{{ comment.data['title'] }}
</td>
</ng-container>
<!-- Description -->
<ng-container [nbTreeGridColumnDef]="columns[2]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.description' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
{{ comment.data['description'] }}
</td>
</ng-container>
<!-- Related Findings -->
<ng-container [nbTreeGridColumnDef]="columns[3]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'comment.relatedFindings' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="related-finding-cell">
{{ comment.data['relatedFindings'].length ? comment.data['relatedFindings'] : 'comment.no.relatedFindings' | translate }}
</td>
</ng-container>
<!-- Actions -->
<ng-container [nbTreeGridColumnDef]="columns[4]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">
<button nbButton hero
status="info"
size="small"
shape="round"
class="add-comment-button"
(click)="onClickAddComment()">
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
{{'comment.add' | translate}}
</button>
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="cell-actions">
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="1rem">
<button nbButton
status="primary"
size="small"
(click)="onClickEditComment(comment)">
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
</button>
<button nbButton
status="danger"
size="small"
(click)="onClickDeleteComment(comment)">
<fa-icon [icon]="fa.faTrash"></fa-icon>
</button>
</div>
</td>
</ng-container>
</table>
</div>
<div *ngIf="data.length === 0 && loading$.getValue() === false" fxLayout="row" fxLayoutAlign="center center">
<p class="error-text">
{{'comment.no.comments' | translate}}
</p>
</div>
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>

View File

@ -0,0 +1,38 @@
@import '../../../../assets/@theme/styles/themes';
.comment-table {
// width: calc(78vw - 18%);
width: 90vw;
.comment-cell {
// Add style here
}
.comment-cell:hover {
// cursor: default;
background-color: nb-theme(color-basic-transparent-focus);
}
.related-finding-cell {
// cursor: pointer;
font-family: Courier, serif;
color: nb-theme(color-info-default);
}
.cell-actions {
width: max-content;
max-width: 200px;
.add-comment-button {
.new-comment-icon {
padding-right: 0.5rem;
}
}
}
}
.error-text {
padding-top: 0.5rem;
font-size: 1.25rem;
font-weight: bold;
}

View File

@ -1,20 +1,92 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import {ComponentFixture, TestBed} from '@angular/core/testing';
import { PentestCommentsComponent } from './pentest-comments.component'; import {PentestCommentsComponent} from './pentest-comments.component';
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 {NgxsModule, Store} from '@ngxs/store';
import {CommonModule} from '@angular/common';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {NbButtonModule, NbTreeGridModule} from '@nebular/theme';
import {ThemeModule} from '@assets/@theme/theme.module';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../common-app.module';
import {HttpClient} from '@angular/common/http';
import {NotificationService} from '@shared/services/notification.service';
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
import {MockComponent} from 'ng-mocks';
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},
// Manages Categories
disabledCategories: [],
selectedCategory: Category.INFORMATION_GATHERING,
// Manages Pentests of Category
disabledPentests: [],
selectedPentest: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
category: Category.INFORMATION_GATHERING,
refNumber: 'OTF-001',
childEntries: [],
status: PentestStatus.NOT_STARTED,
findingsIds: [],
commentsIds: ['56c47c56-3bcd-45f1-a05b-c197dbd33112']
},
};
describe('PentestCommentsComponent', () => { describe('PentestCommentsComponent', () => {
let component: PentestCommentsComponent; let component: PentestCommentsComponent;
let fixture: ComponentFixture<PentestCommentsComponent>; let fixture: ComponentFixture<PentestCommentsComponent>;
let store: Store;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PentestCommentsComponent ] declarations: [
PentestCommentsComponent,
MockComponent(LoadingSpinnerComponent)
],
imports: [
CommonModule,
BrowserAnimationsModule,
HttpClientTestingModule,
FontAwesomeModule,
NbButtonModule,
NbTreeGridModule,
ThemeModule.forRoot(),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NgxsModule.forRoot([ProjectState])
],
providers: [
{provide: NotificationService, useValue: new NotificationServiceMock()}
]
}) })
.compileComponents(); .compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(PentestCommentsComponent); fixture = TestBed.createComponent(PentestCommentsComponent);
store = TestBed.inject(Store);
store.reset({
...store.snapshot(),
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
});
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -1,5 +1,16 @@
import { Component, OnInit } from '@angular/core'; import {Component, Input, 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';
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
import {PentestService} from '@shared/services/pentest.service';
import {NotificationService, PopupType} from '@shared/services/notification.service';
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';
@UntilDestroy()
@Component({ @Component({
selector: 'app-pentest-comments', selector: 'app-pentest-comments',
templateUrl: './pentest-comments.component.html', templateUrl: './pentest-comments.component.html',
@ -7,9 +18,80 @@ import { Component, OnInit } from '@angular/core';
}) })
export class PentestCommentsComponent implements OnInit { export class PentestCommentsComponent implements OnInit {
constructor() { } @Input()
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
ngOnInit(): void { // HTML only
readonly fa = FA;
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
columns: Array<CommentColumns> = [
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
];
dataSource: NbTreeGridDataSource<CommentEntry>;
data: CommentEntry[] = [];
getters: NbGetters<CommentEntry, CommentEntry> = {
dataGetter: (node: CommentEntry) => node,
childrenGetter: (node: CommentEntry) => node.childEntries || undefined,
expandedGetter: (node: CommentEntry) => !!node.expanded,
};
constructor(private readonly pentestService: PentestService,
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
private notificationService: NotificationService) {
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
} }
ngOnInit(): void {
this.loadCommentsData();
}
loadCommentsData(): void {
this.pentestService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
.pipe(
untilDestroyed(this),
filter(isNotNullOrUndefined),
tap(() => this.loading$.next(true))
)
.subscribe({
next: (comments: Comment[]) => {
this.data = transformCommentsToObjectiveEntries(comments);
this.dataSource.setData(this.data, this.getters);
this.loading$.next(false);
},
error: err => {
console.log(err);
this.notificationService.showPopup('comment.popup.not.found', PopupType.FAILURE);
this.loading$.next(false);
}
});
}
onClickAddComment(): void {
console.info('Coming soon..');
}
onClickEditComment(comment): void {
console.info('Coming soon..');
}
onClickDeleteComment(comment): void {
console.info('Coming soon..');
}
// HTML only
isLoading(): Observable<boolean> {
return this.loading$.asObservable();
}
}
enum CommentColumns {
COMMENT_ID = 'commentId',
TITLE = 'title',
DESCRIPTION = 'description',
RELATED_FINDINGS = 'relatedFindings',
ACTIONS = 'actions'
} }

View File

@ -8,7 +8,7 @@
<app-pentest-findings [pentestInfo$] = pentest$></app-pentest-findings> <app-pentest-findings [pentestInfo$] = pentest$></app-pentest-findings>
</nb-tab> </nb-tab>
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}" badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info"> <nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}" badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
<app-pentest-comments></app-pentest-comments> <app-pentest-comments [pentestInfo$] = pentest$></app-pentest-comments>
</nb-tab> </nb-tab>
</nb-tabset> </nb-tabset>
</div> </div>

View File

@ -14,8 +14,17 @@
{{ finding.data['findingId'] || '-' }} {{ finding.data['findingId'] || '-' }}
</td> </td>
</ng-container> </ng-container>
<!-- Title --> <!-- Severity -->
<ng-container [nbTreeGridColumnDef]="columns[1]"> <ng-container [nbTreeGridColumnDef]="columns[1]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-severity">
{{ 'finding.severity' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let finding" class="cell-severity" fxFill fxLayoutAlign="center none">
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
</td>
</ng-container>
<!-- Title -->
<ng-container [nbTreeGridColumnDef]="columns[2]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef> <th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'finding.title' | translate }} {{ 'finding.title' | translate }}
</th> </th>
@ -24,7 +33,7 @@
</td> </td>
</ng-container> </ng-container>
<!-- Impact --> <!-- Impact -->
<ng-container [nbTreeGridColumnDef]="columns[2]"> <ng-container [nbTreeGridColumnDef]="columns[3]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef> <th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'finding.impact' | translate }} {{ 'finding.impact' | translate }}
</th> </th>
@ -32,15 +41,6 @@
{{ finding.data['impact'] }} {{ finding.data['impact'] }}
</td> </td>
</ng-container> </ng-container>
<!-- Severity -->
<ng-container [nbTreeGridColumnDef]="columns[3]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'finding.severity' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let finding">
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
</td>
</ng-container>
<!-- Actions --> <!-- Actions -->
<ng-container [nbTreeGridColumnDef]="columns[4]"> <ng-container [nbTreeGridColumnDef]="columns[4]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions"> <th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">

View File

@ -13,6 +13,11 @@
background-color: nb-theme(color-basic-transparent-focus); background-color: nb-theme(color-basic-transparent-focus);
} }
.cell-severity {
width: 125px;
max-width: 125px;
}
.cell-actions { .cell-actions {
width: max-content; width: max-content;
max-width: 180px; max-width: 180px;

View File

@ -3,11 +3,12 @@ import {PentestService} from '@shared/services/pentest.service';
import {BehaviorSubject, Observable, of} from 'rxjs'; import {BehaviorSubject, Observable, of} from 'rxjs';
import {Pentest} from '@shared/models/pentest.model'; import {Pentest} from '@shared/models/pentest.model';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {tap} from 'rxjs/operators'; import {filter, tap} from 'rxjs/operators';
import {NotificationService, PopupType} from '@shared/services/notification.service'; import {NotificationService, PopupType} from '@shared/services/notification.service';
import {Finding, FindingEntry, transformFindingsToObjectiveEntries} from '@shared/models/finding.model'; import {Finding, FindingEntry, transformFindingsToObjectiveEntries} from '@shared/models/finding.model';
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme'; import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
import * as FA from '@fortawesome/free-solid-svg-icons'; import * as FA from '@fortawesome/free-solid-svg-icons';
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -26,7 +27,7 @@ export class PentestFindingsComponent implements OnInit {
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true); loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
columns: Array<FindingColumns> = [ columns: Array<FindingColumns> = [
FindingColumns.FINDING_ID, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.SEVERITY, FindingColumns.ACTIONS FindingColumns.FINDING_ID, FindingColumns.SEVERITY, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.ACTIONS
]; ];
dataSource: NbTreeGridDataSource<FindingEntry>; dataSource: NbTreeGridDataSource<FindingEntry>;
@ -45,7 +46,6 @@ export class PentestFindingsComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
console.warn('Selected Pentest: ', this.pentestInfo$.getValue());
this.loadFindingsData(); this.loadFindingsData();
} }
@ -53,6 +53,7 @@ export class PentestFindingsComponent implements OnInit {
this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '') this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
.pipe( .pipe(
untilDestroyed(this), untilDestroyed(this),
filter(isNotNullOrUndefined),
tap(() => this.loading$.next(true)) tap(() => this.loading$.next(true))
) )
.subscribe({ .subscribe({
@ -89,8 +90,8 @@ export class PentestFindingsComponent implements OnInit {
enum FindingColumns { enum FindingColumns {
FINDING_ID = 'findingId', FINDING_ID = 'findingId',
SEVERITY = 'severity',
TITLE = 'title', TITLE = 'title',
IMPACT = 'impact', IMPACT = 'impact',
SEVERITY = 'severity',
ACTIONS = 'actions' ACTIONS = 'actions'
} }

View File

@ -90,11 +90,20 @@
}, },
"finding": { "finding": {
"findingId": "Fund Id", "findingId": "Fund Id",
"title": "Title", "title": "Titel",
"impact": "Auswirkung", "impact": "Auswirkung",
"severity": "Schwere", "severity": "Schwere",
"add": "Fund hinzufügen", "add": "Fund hinzufügen",
"no.findings": "Keine Funde verfügbar" "no.findings": "Keine Funde verfügbar",
"popup": {
"not.found": "Keine Funde gefunden",
"save.success": "Fund erfolgreich gespeichert",
"save.failed": "Fund konnte nicht gespeichert werden",
"update.success": "Fund erfolgreich aktualisiert",
"update.failed": "Fund konnte nicht aktualisiert werden",
"delete.success": "Fund erfolgreich gelöscht",
"delete.failed": "Fund konnte nicht gelöscht werden"
}
}, },
"severities": { "severities": {
"low": "Niedrig", "low": "Niedrig",
@ -102,6 +111,24 @@
"high": "Hoch", "high": "Hoch",
"critical": "Kritisch" "critical": "Kritisch"
}, },
"comment": {
"commentId": "Kommentar Id",
"title": "Titel",
"description": "Beschreibung",
"relatedFindings": "Verwandte Funde",
"add": "Kommentar hinzufügen",
"no.relatedFindings": "Nicht verbunden mit Fund",
"no.comments": "Keine Kommentare verfügbar",
"popup": {
"not.found": "Keine Kommentare gefunden",
"save.success": "Kommentar erfolgreich gespeichert",
"save.failed": "Kommentar konnte nicht gespeichert werden",
"update.success": "Kommentar erfolgreich aktualisiert",
"update.failed": "Kommentar konnte nicht aktualisiert werden",
"delete.success": "Kommentar erfolgreich gelöscht",
"delete.failed": "Kommentar konnte nicht gelöscht werden"
}
},
"pentest": { "pentest": {
"testId": "Nr.", "testId": "Nr.",
"title": "Titel", "title": "Titel",

View File

@ -90,11 +90,20 @@
}, },
"finding": { "finding": {
"findingId": "Finding Id", "findingId": "Finding Id",
"severity": "Severity",
"title": "Title", "title": "Title",
"impact": "Impact", "impact": "Impact",
"severity": "Severity",
"add": "Add finding", "add": "Add finding",
"no.findings": "No findings available" "no.findings": "No findings available",
"popup": {
"not.found": "No finding found",
"save.success": "Finding saved successfully",
"save.failed": "Finding could not be saved",
"update.success": "Finding updated successfully",
"update.failed": "Finding could not be updated",
"delete.success": "Finding deleted successfully",
"delete.failed": "Finding could not be deleted"
}
}, },
"severities": { "severities": {
"low": "Low", "low": "Low",
@ -102,6 +111,24 @@
"high": "High", "high": "High",
"critical": "Critical" "critical": "Critical"
}, },
"comment": {
"commentId": "Comment Id",
"title": "Title",
"description": "Description",
"relatedFindings": "Related Findings",
"add": "Add comment",
"no.comments": "No comments available",
"no.relatedFindings": "Not related to finding",
"popup": {
"not.found": "No comment found",
"save.success": "Comment saved successfully",
"save.failed": "Comment could not be saved",
"update.success": "Comment updated successfully",
"update.failed": "Comment could not be updated",
"delete.success": "Comment deleted successfully",
"delete.failed": "Comment could not be deleted"
}
},
"pentest": { "pentest": {
"testId": "No.", "testId": "No.",
"title": "Title", "title": "Title",

View File

@ -0,0 +1,45 @@
import {v4 as UUID} from 'uuid';
import {Severity} from '@shared/models/severity.enum';
export class Comment {
id?: string;
title: string;
description?: string;
relatedFindings?: Array<string>;
constructor(title: string,
description: string,
id?: string,
relatedFindings?: Array<string>) {
this.id = id ? id : UUID();
this.title = title;
this.description = description;
this.relatedFindings = relatedFindings;
}
}
export interface CommentEntry {
commentId: string;
title: string;
description: string;
relatedFindings: Array<string>;
kind?: string;
childEntries?: [];
expanded?: boolean;
}
export function transformCommentsToObjectiveEntries(findings: Comment[]): CommentEntry[] {
const findingEntries: CommentEntry[] = [];
findings.forEach((value: Comment) => {
findingEntries.push({
commentId: value.id,
title: value.title,
description: value.description,
relatedFindings: value.relatedFindings,
kind: 'cell',
childEntries: null,
expanded: false
} as CommentEntry);
});
return findingEntries;
}

View File

@ -3,27 +3,27 @@ import {Severity} from '@shared/models/severity.enum';
export class Finding { export class Finding {
id?: string; id?: string;
severity: Severity;
title: string; title: string;
description?: string; description?: string;
impact: string; impact: string;
severity: Severity;
affectedUrls?: Array<string>; affectedUrls?: Array<string>;
reproduction?: string; reproduction?: string;
mitigation?: string; mitigation?: string;
constructor(title: string, constructor(title: string,
severity: Severity,
description: string, description: string,
impact: string, impact: string,
severity: Severity,
reproduction: string, reproduction: string,
id?: string, id?: string,
affectedUrls?: Array<string>, affectedUrls?: Array<string>,
mitigation?: string) { mitigation?: string) {
this.id = id ? id : UUID(); this.id = id ? id : UUID();
this.severity = severity;
this.title = title; this.title = title;
this.description = description; this.description = description;
this.impact = impact; this.impact = impact;
this.severity = severity;
this.affectedUrls = affectedUrls ? affectedUrls : null; this.affectedUrls = affectedUrls ? affectedUrls : null;
this.reproduction = reproduction; this.reproduction = reproduction;
this.mitigation = mitigation ? mitigation : null; this.mitigation = mitigation ? mitigation : null;
@ -32,9 +32,9 @@ export class Finding {
export interface FindingEntry { export interface FindingEntry {
findingId: string; findingId: string;
severity: Severity;
title: string; title: string;
impact: string; impact: string;
severity: Severity;
kind?: string; kind?: string;
childEntries?: []; childEntries?: [];
expanded?: boolean; expanded?: boolean;
@ -45,9 +45,9 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin
findings.forEach((value: Finding) => { findings.forEach((value: Finding) => {
findingEntries.push({ findingEntries.push({
findingId: value.id, findingId: value.id,
severity: value.severity,
title: value.title, title: value.title,
impact: value.impact, impact: value.impact,
severity: value.severity,
kind: 'cell', kind: 'cell',
childEntries: null, childEntries: null,
expanded: false expanded: false

View File

@ -10,6 +10,7 @@ import {catchError, map, switchMap} from 'rxjs/operators';
import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function'; import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function';
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';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -92,4 +93,32 @@ export class PentestService {
]); ]);
} }
} }
/**
* Get Comments for Pentest Id
* @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 lit test finding ma brother',
description: 'fucked up a lot man. better fix it',
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
},
{
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
title: 'This is a lit test finding ma brother',
description: 'fucked up a lot man. better fix it',
relatedFindings: [],
}
]);
}
}
} }

View File

@ -0,0 +1,12 @@
package comment
import org.springframework.data.mongodb.core.index.Indexed
import java.util.*
data class Comment (
@Indexed(background = true, unique = true)
val id: String = UUID.randomUUID().toString(),
val title: String,
val description: String,
val relatedFindings: List<String>? = emptyList()
)

View File

@ -0,0 +1,18 @@
package comment
import com.securityc4po.api.BaseEntity
import org.springframework.data.mongodb.core.mapping.Document
@Document(collection = "comments")
open class CommentEntity(
data: Comment
) : BaseEntity<Comment>(data)
fun CommentEntity.toComment(): Comment {
return Comment(
this.data.id,
this.data.title,
this.data.description,
this.data.relatedFindings
)
}

View File

@ -1,4 +1,4 @@
package com.securityc4po.api.pentest package finding
import org.springframework.data.mongodb.core.index.Indexed import org.springframework.data.mongodb.core.index.Indexed
import java.util.* import java.util.*
@ -6,10 +6,10 @@ import java.util.*
data class Finding ( data class Finding (
@Indexed(background = true, unique = true) @Indexed(background = true, unique = true)
val id: String = UUID.randomUUID().toString(), val id: String = UUID.randomUUID().toString(),
val severity: Severity,
val title: String, val title: String,
val description: String, val description: String,
val impact: String, val impact: String,
val severity: Severity,
val affectedUrls: List<String>? = emptyList(), val affectedUrls: List<String>? = emptyList(),
val reproduction: String, val reproduction: String,
val mitigation: String val mitigation: String

View File

@ -0,0 +1,23 @@
package finding
import com.securityc4po.api.BaseEntity
import comment.Comment
import org.springframework.data.mongodb.core.mapping.Document
@Document(collection = "findings")
open class FindingEntity(
data: Finding
) : BaseEntity<Finding>(data)
fun FindingEntity.toFinding(): Finding {
return finding.Finding(
this.data.id,
this.data.severity,
this.data.title,
this.data.description,
this.data.impact,
this.data.affectedUrls,
this.data.reproduction,
this.data.mitigation
)
}

View File

@ -1,4 +1,4 @@
package com.securityc4po.api.pentest package finding
enum class Severity { enum class Severity {
LOW, LOW,