From f658073bcf8edeb9e296d245cd55d232263d145c Mon Sep 17 00:00:00 2001 From: Marcel Haag Date: Wed, 26 Oct 2022 12:26:40 +0200 Subject: [PATCH] feat: As a user I want to add a finding via dialog --- security-c4po-angular/package-lock.json | 6 +- .../pentest-findings.component.spec.ts | 5 +- .../pentest-findings.component.ts | 41 ++- .../src/app/pentest/pentest.module.ts | 6 +- .../src/assets/i18n/de-DE.json | 36 ++- .../src/assets/i18n/en-US.json | 34 ++- .../src/shared/models/finding.model.ts | 14 +- ...-dialog-data.ts => generic-dialog-data.ts} | 2 +- .../finding-dialog.component.html | 180 +++++++++++++ .../finding-dialog.component.scss | 92 +++++++ .../finding-dialog.component.spec.ts | 241 ++++++++++++++++++ .../finding-dialog.component.ts | 156 ++++++++++++ .../finding-dialog/finding-dialog.module.ts | 38 +++ .../service/finding-dialog.service.mock.ts | 17 ++ .../service/finding-dialog.service.spec.ts | 30 +++ .../service/finding-dialog.service.ts | 155 +++++++++++ .../project-dialog.component.html | 2 +- .../project-dialog.component.scss | 2 +- .../project-dialog.component.spec.ts | 7 +- .../project-dialog.component.ts | 8 +- .../service/project-dialog.service.ts | 8 +- .../src/shared/services/pentest.service.ts | 43 +++- .../status-tag/status-tag.component.spec.ts | 4 +- .../src/main/kotlin/finding/FindingEntity.kt | 1 - 24 files changed, 1077 insertions(+), 51 deletions(-) rename security-c4po-angular/src/shared/models/{project-dialog-data.ts => generic-dialog-data.ts} (94%) create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.html create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.scss create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.ts create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.module.ts create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.mock.ts create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.spec.ts create mode 100644 security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.ts diff --git a/security-c4po-angular/package-lock.json b/security-c4po-angular/package-lock.json index 122e2e8..122e791 100644 --- a/security-c4po-angular/package-lock.json +++ b/security-c4po-angular/package-lock.json @@ -11338,9 +11338,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.34", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", - "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", + "version": "0.5.38", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.38.tgz", + "integrity": "sha512-nMIrzGah4+oYZPflDvLZUgoVUO4fvAqHstvG3xAUnMolWncuAiLDWNnJZj6EwJGMGfb1ZcuTFE6GI3hNOVWI/Q==", "requires": { "moment": ">= 2.9.0" } diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.spec.ts b/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.spec.ts index 8502674..3451b34 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.spec.ts +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.spec.ts @@ -18,6 +18,8 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {ThemeModule} from '@assets/@theme/theme.module'; import {Category} from '@shared/models/category.model'; import {PentestStatus} from '@shared/models/pentest-status.model'; +import {FindingDialogService} from '@shared/modules/finding-dialog/service/finding-dialog.service'; +import {FindingDialogServiceMock} from '@shared/modules/finding-dialog/service/finding-dialog.service.mock'; const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = { selectedProject: { @@ -74,7 +76,8 @@ describe('PentestFindingsComponent', () => { NgxsModule.forRoot([ProjectState]) ], providers: [ - {provide: NotificationService, useValue: new NotificationServiceMock()} + {provide: NotificationService, useValue: new NotificationServiceMock()}, + {provide: FindingDialogService, useClass: FindingDialogServiceMock}, ] }) .compileComponents(); diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.ts b/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.ts index 70bc6e7..51ad3a2 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.ts +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.ts @@ -1,14 +1,16 @@ import {Component, Input, OnInit} from '@angular/core'; import {PentestService} from '@shared/services/pentest.service'; -import {BehaviorSubject, Observable, of} from 'rxjs'; +import {BehaviorSubject, Observable} from 'rxjs'; import {Pentest} from '@shared/models/pentest.model'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; -import {filter, tap} from 'rxjs/operators'; +import {filter, mergeMap, tap} from 'rxjs/operators'; import {NotificationService, PopupType} from '@shared/services/notification.service'; -import {Finding, FindingEntry, transformFindingsToObjectiveEntries} from '@shared/models/finding.model'; +import {Finding, FindingDialogBody, FindingEntry, transformFindingsToObjectiveEntries} from '@shared/models/finding.model'; import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme'; import * as FA from '@fortawesome/free-solid-svg-icons'; 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'; @UntilDestroy() @Component({ @@ -23,7 +25,6 @@ export class PentestFindingsComponent implements OnInit { // HTML only readonly fa = FA; - // findings$: BehaviorSubject = new BehaviorSubject(null); loading$: BehaviorSubject = new BehaviorSubject(true); columns: Array = [ @@ -41,7 +42,8 @@ export class PentestFindingsComponent implements OnInit { constructor(private readonly pentestService: PentestService, private dataSourceBuilder: NbTreeGridDataSourceBuilder, - private notificationService: NotificationService) { + private notificationService: NotificationService, + private findingDialogService: FindingDialogService) { this.dataSource = dataSourceBuilder.create(this.data, this.getters); } @@ -71,14 +73,39 @@ export class PentestFindingsComponent implements OnInit { } onClickAddFinding(): void { - console.info('Coming soon..'); + this.findingDialogService.openFindingDialog( + FindingDialogComponent, + null, + { + closeOnEsc: false, + hasScroll: false, + autoFocus: false, + closeOnBackdropClick: false + } + ).pipe( + filter(value => !!value), + tap((value) => console.warn('FindingDialogBody: ', value)), + mergeMap((value: FindingDialogBody) => + this.pentestService.saveFinding(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '', value) + ), + untilDestroyed(this) + ).subscribe({ + next: () => { + this.loadFindingsData(); + this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS); + }, + error: error => { + console.error(error); + this.notificationService.showPopup('finding.popup.save.failed', PopupType.FAILURE); + } + }); } onClickEditFinding(finding): void { console.info('Coming soon..'); } - onClickDeleteFinding(finding): void{ + onClickDeleteFinding(finding): void { console.info('Coming soon..'); } diff --git a/security-c4po-angular/src/app/pentest/pentest.module.ts b/security-c4po-angular/src/app/pentest/pentest.module.ts index 3bf7b9d..37fc99f 100644 --- a/security-c4po-angular/src/app/pentest/pentest.module.ts +++ b/security-c4po-angular/src/app/pentest/pentest.module.ts @@ -12,10 +12,9 @@ import {StatusTagModule} from '@shared/widgets/status-tag/status-tag.module'; import { PentestInfoComponent } from './pentest-content/pentest-info/pentest-info.component'; import { PentestFindingsComponent } from './pentest-content/pentest-findings/pentest-findings.component'; import { PentestCommentsComponent } from './pentest-content/pentest-comments/pentest-comments.component'; -import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component'; -import {ProjectOverviewModule} from '../project-overview'; import {CommonAppModule} from '../common-app.module'; import {SeverityTagModule} from '@shared/widgets/severity-tag/severity-tag.module'; +import {FindingDialogModule} from '@shared/modules/finding-dialog/finding-dialog.module'; @NgModule({ declarations: [ @@ -42,7 +41,8 @@ import {SeverityTagModule} from '@shared/widgets/severity-tag/severity-tag.modul NbTabsetModule, NbTreeGridModule, CommonAppModule, - SeverityTagModule + SeverityTagModule, + FindingDialogModule ] }) export class PentestModule { diff --git a/security-c4po-angular/src/assets/i18n/de-DE.json b/security-c4po-angular/src/assets/i18n/de-DE.json index 16dbf2f..8a8a2cf 100644 --- a/security-c4po-angular/src/assets/i18n/de-DE.json +++ b/security-c4po-angular/src/assets/i18n/de-DE.json @@ -90,11 +90,43 @@ }, "finding": { "findingId": "Fund Id", - "title": "Titel", - "impact": "Auswirkung", "severity": "Schwere", + "title": "Titel", + "description": "Beschreibung", + "impact": "Auswirkung", + "affectedUrls": "Betroffene URL's", + "reproduction": "Reproduktion", + "mitigation": "Minderung", "add": "Fund hinzufügen", + "add.url": "Betroffene URL hinzufügen", + "affectedUrls.placeholder": "Betroffene URL hier eingeben..", + "create": { + "header": "Neuen Fund erstellen" + }, + "edit": { + "header": "Fund editieren" + }, + "delete": { + "title": "Fund löschen", + "key": "Möchten Sie den Fund \"{{name}}\" unwiderruflich löschen?" + }, + "severity.label": "Schwere des Funds", + "title.label": "Fundtitel", + "description.label": "Beschreibung des Funds", + "impact.label": "Auswirkung auf Anwendung", + "affectedUrls.label": "Betroffene URL's", + "reproduction.label": "Reproduktionsschritte", + "mitigation.label": "Minderungsvorschlag", "no.findings": "Keine Funde verfügbar", + "validationMessage": { + "titleRequired": "Titel ist erforderlich.", + "severityRequired": "Schwere ist erforderlich.", + "descriptionRequired": "Beschreibung ist erforderlich.", + "impactRequired": "Auswirkung ist erforderlich.", + "affectedUrlsRequired": "Betroffene URL's erforderlich.", + "reproductionRequired": "Reproduktionschritt(e) sind erforderlich.", + "mitigationRequired": "Minderungsvorschlag ist erforderlich." + }, "popup": { "not.found": "Keine Funde gefunden", "save.success": "Fund erfolgreich gespeichert", diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json index a3a4d1f..ab4b6a2 100644 --- a/security-c4po-angular/src/assets/i18n/en-US.json +++ b/security-c4po-angular/src/assets/i18n/en-US.json @@ -92,9 +92,41 @@ "findingId": "Finding Id", "severity": "Severity", "title": "Title", + "description": "Description", "impact": "Impact", + "affectedUrls": "Affected URL's", + "reproduction": "Reproduction", + "mitigation": "Mitigation", "add": "Add finding", + "add.url": "Add affected Url", + "affectedUrls.placeholder": "Enter affected URL here..", + "create": { + "header": "Create New Finding" + }, + "edit": { + "header": "Edit Finding" + }, + "delete": { + "title": "Delete Finding", + "key": "Do you want to permanently delete the finding \"{{name}}\"?" + }, + "severity.label": "Severity of Finding", + "title.label": "Finding Title", + "description.label": "Description of Finding", + "impact.label": "Impacted Applicationparts", + "affectedUrls.label": "Affected URL's", + "reproduction.label": "Reproductionsteps", + "mitigation.label": "Suggest Mitigation", "no.findings": "No findings available", + "validationMessage": { + "titleRequired": "Title is required.", + "severityRequired": "Severity is required.", + "descriptionRequired": "Description is required.", + "impactRequired": "Impact is required.", + "affectedUrlsRequired": "Affected Url's required.", + "reproductionRequired": "Reproductionstep(s) are required.", + "mitigationRequired": "Mitigation is required." + }, "popup": { "not.found": "No finding found", "save.success": "Finding saved successfully", @@ -116,7 +148,7 @@ "title": "Title", "description": "Description", "relatedFindings": "Related Findings", - "add": "Add comment", + "add": "Add Comment", "no.comments": "No comments available", "no.relatedFindings": "Not related to finding", "popup": { diff --git a/security-c4po-angular/src/shared/models/finding.model.ts b/security-c4po-angular/src/shared/models/finding.model.ts index 2c53345..3f2beb6 100644 --- a/security-c4po-angular/src/shared/models/finding.model.ts +++ b/security-c4po-angular/src/shared/models/finding.model.ts @@ -5,10 +5,10 @@ export class Finding { id?: string; severity: Severity; title: string; - description?: string; + description: string; impact: string; affectedUrls?: Array; - reproduction?: string; + reproduction: string; mitigation?: string; constructor(title: string, @@ -55,3 +55,13 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin }); return findingEntries; } + +export interface FindingDialogBody { + title: string; + severity: Severity; + description: string; + impact: string; + affectedUrls: Array; + reproduction: string; + mitigation: string; +} diff --git a/security-c4po-angular/src/shared/models/project-dialog-data.ts b/security-c4po-angular/src/shared/models/generic-dialog-data.ts similarity index 94% rename from security-c4po-angular/src/shared/models/project-dialog-data.ts rename to security-c4po-angular/src/shared/models/generic-dialog-data.ts index 37c2ed0..6cede10 100644 --- a/security-c4po-angular/src/shared/models/project-dialog-data.ts +++ b/security-c4po-angular/src/shared/models/generic-dialog-data.ts @@ -1,4 +1,4 @@ -export interface ProjectDialogData { +export interface GenericDialogData { form: { [key: string]: GenericFormFieldConfig // key is property name, e.g. title }; diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.html b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.html new file mode 100644 index 0000000..c47ed29 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.html @@ -0,0 +1,180 @@ + + + {{ dialogData?.options[0].headerLabelKey | translate }} + + +
+
+
+ +
+ + + + + + + + {{error.translationKey | translate}} + + + + + + + + + + + {{error.translationKey | translate}} + + + + + + + + + + + {{error.translationKey | translate}} + + + +
+ + +
+ + + + {{ severity.translationText | translate }} + + +
+
+ + + + + + + + + {{error.translationKey | translate}} + + + + + + + + + + +
+ + + + + + + + {{error.translationKey | translate}} + + + + + + + + + + + {{error.translationKey | translate}} + + + +
+
+
+
+ + + + +
diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.scss b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.scss new file mode 100644 index 0000000..4285655 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.scss @@ -0,0 +1,92 @@ +@import "../../../assets/@theme/styles/_dialog.scss"; +@import '../../../assets/@theme/styles/themes'; + +.finding-dialog { + width: 65.25rem !important; + height: 55rem; + + .finding-dialog-header { + height: 8vh; + font-size: 1.5rem; + } + + nb-form-field { + padding: 0.5rem 0 0.75rem; + } + + .label { + display: block; + font-size: 0.95rem; + padding-bottom: 0.5rem; + } + + .form-field { + font-weight: normal; + margin-bottom: 0.5rem; + } + + .form-text { + width: 30rem !important; + } + + .form-textarea { + width: 30rem !important; + height: 8rem; + } + + .additionalUrl { + font-family: Courier, serif; + background-color: nb-theme(card-header-basic-background-color); + padding-left: 1.5rem; + width: 100% !important; + } + + .add-url-button { + width: 100% !important; + margin-bottom: 1.5rem; + + .new-url-icon { + padding-right: 0.5rem; + } + } + + .url-tag-list { + margin-bottom: 1.5rem; + + .url-tag { + max-width: 62rem !important; + overflow: hidden; + display: inline-block; + // Style + font-family: Courier, serif; + text-transform: none !important; + } + } + + .error-text { + float: left; + color: nb-theme(color-danger-default); + } + + .severity-dialog { + margin-left: auto; + margin-right: 0; + padding: 0.5rem 0 0.75rem; + + .severities { + width: 8rem; + } + + .severity-0 { + } + .severity-1 { + background-color: nb-theme(color-info-default); + } + .severity-2 { + background-color: nb-theme(color-warning-default); + } + .severity-3 { + background-color: nb-theme(color-danger-default); + } + } +} diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts new file mode 100644 index 0000000..8e218a5 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts @@ -0,0 +1,241 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {FindingDialogComponent} from './finding-dialog.component'; +import {CommonModule} from '@angular/common'; +import { + NB_DIALOG_CONFIG, + NbButtonModule, + NbCardModule, + NbDialogRef, + NbFormFieldModule, + NbInputModule, + NbLayoutModule, NbTagModule +} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {ReactiveFormsModule, Validators} from '@angular/forms'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {ThemeModule} from '@assets/@theme/theme.module'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {HttpLoaderFactory} from '../../../app/common-app.module'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {NotificationService} from '@shared/services/notification.service'; +import {NotificationServiceMock} from '@shared/services/notification.service.mock'; +import {DialogService} from '@shared/services/dialog-service/dialog.service'; +import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock'; +import {Severity} from '@shared/models/severity.enum'; +import {Finding} from '@shared/models/finding.model'; +import Mock = jest.Mock; +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'; + +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: ['56c47c56-3bcd-45f1-a05b-c197dbd33112'], + commentsIds: [] + }, +}; + +describe('FindingDialogComponent', () => { + let component: FindingDialogComponent; + let fixture: ComponentFixture; + let store: Store; + + beforeEach(async () => { + const dialogSpy = createSpyObj('NbDialogRef', ['close']); + + await TestBed.configureTestingModule({ + declarations: [ + FindingDialogComponent + ], + imports: [ + CommonModule, + NbLayoutModule, + NbCardModule, + NbButtonModule, + FlexLayoutModule, + NbInputModule, + NbFormFieldModule, + NbTagModule, + ReactiveFormsModule, + BrowserAnimationsModule, + ThemeModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + NgxsModule.forRoot([ProjectState]), + HttpClientModule, + HttpClientTestingModule + ], + providers: [ + {provide: NotificationService, useValue: new NotificationServiceMock()}, + {provide: DialogService, useClass: DialogServiceMock}, + {provide: NbDialogRef, useValue: dialogSpy}, + {provide: NB_DIALOG_CONFIG, useValue: mockedFindingDialogData} + ] + }).compileComponents(); + }); + + beforeEach(() => { + TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedFindingDialogData}); + fixture = TestBed.createComponent(FindingDialogComponent); + store = TestBed.inject(Store); + store.reset({ + ...store.snapshot(), + [PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION + }); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); + +export const createSpyObj = (baseName, methodNames): { [key: string]: Mock } => { + const obj: any = {}; + for (const i of methodNames) { + obj[i] = jest.fn(); + } + return obj; +}; + +export const mockFinding: Finding = { + id: '11-22-33', + severity: Severity.LOW, + title: 'Test Finding', + description: 'Test Description', + impact: 'Test Impact', + affectedUrls: ['https://test.de'], + reproduction: 'Step N: Test', + mitigation: 'Mitigation Test' +}; + +export const mockedFindingDialogData = { + form: { + findingTitle: { + fieldName: 'findingTitle', + type: 'formText', + labelKey: 'finding.title.label', + placeholder: 'finding.title', + controlsConfig: [ + {value: mockFinding ? mockFinding.title : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.titleRequired'} + ] + }, + findingSeverity: { + fieldName: 'findingSeverity', + type: 'severity-select', + labelKey: 'finding.severity.label', + placeholder: 'finding.severity', + controlsConfig: [ + {value: mockFinding ? mockFinding.severity : Severity.LOW, disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.severityRequired'} + ] + }, + findingDescription: { + fieldName: 'findingDescription', + type: 'formText', + labelKey: 'finding.description.label', + placeholder: 'finding.description', + controlsConfig: [ + {value: mockFinding ? mockFinding.description : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.descriptionRequired'} + ] + }, + findingImpact: { + fieldName: 'findingImpact', + type: 'formText', + labelKey: 'finding.impact.label', + placeholder: 'finding.impact', + controlsConfig: [ + {value: mockFinding ? mockFinding.impact : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.impactRequired'} + ] + }, + findingAffectedUrls: { + fieldName: 'findingAffectedUrls', + type: 'text', + labelKey: 'finding.affectedUrls.label', + placeholder: 'finding.affectedUrls.placeholder', + controlsConfig: [ + {value: '', disabled: false}, + [] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.affectedUrlsRequired'} + ] + }, + findingReproduction: { + fieldName: 'findingReproduction', + type: 'text', + labelKey: 'finding.reproduction.label', + placeholder: 'finding.reproduction', + controlsConfig: [ + {value: mockFinding ? mockFinding.reproduction : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.reproductionRequired'} + ] + }, + findingMitigation: { + fieldName: 'findingMitigation', + type: 'text', + labelKey: 'finding.mitigation.label', + placeholder: 'finding.mitigation', + controlsConfig: [ + {value: mockFinding ? mockFinding.mitigation : '', disabled: false}, + [] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.mitigationRequired'} + ] + } + }, + options: [ + { + headerLabelKey: 'finding.create.header', + buttonKey: 'global.action.save', + accentColor: 'info' + }, + ] +}; diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.ts b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.ts new file mode 100644 index 0000000..ec4a6e1 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.ts @@ -0,0 +1,156 @@ +import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core'; +import {FormBuilder, FormGroup} from '@angular/forms'; +import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-dialog-data'; +import {NB_DIALOG_CONFIG, NbDialogRef, NbTagComponent} from '@nebular/theme'; +import deepEqual from 'deep-equal'; +import {UntilDestroy} from '@ngneat/until-destroy'; +import {Severity} from '@shared/models/severity.enum'; +import * as FA from '@fortawesome/free-solid-svg-icons'; + +@UntilDestroy() +@Component({ + selector: 'app-finding-dialog', + templateUrl: './finding-dialog.component.html', + styleUrls: ['./finding-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FindingDialogComponent implements OnInit { + // form control elements + findingFormGroup: FormGroup; + formArray: GenericFormFieldConfig[]; + + dialogData: GenericDialogData; + + // HTML only + readonly fa = FA; + severity: Severity = Severity.LOW; + readonly severityTexts: Array = [ + {value: Severity.LOW, translationText: 'severities.low'}, + {value: Severity.MEDIUM, translationText: 'severities.medium'}, + {value: Severity.HIGH, translationText: 'severities.high'}, + {value: Severity.CRITICAL, translationText: 'severities.critical'} + ]; + + // ToDo: Adjust for edit finding dialog to include existing urls + affectedUrls: string[] = []; + + constructor( + @Inject(NB_DIALOG_CONFIG) private data: GenericDialogData, + private fb: FormBuilder, + protected dialogRef: NbDialogRef + ) { + } + + ngOnInit(): void { + this.findingFormGroup = this.generateFormCreationFieldArray(); + this.dialogData = this.data; + } + + generateFormCreationFieldArray(): FormGroup { + this.formArray = Object.values(this.data.form); + const config = this.formArray?.reduce((accumulator: {}, currentValue: GenericFormFieldConfig) => ({ + ...accumulator, + [currentValue?.fieldName]: currentValue?.controlsConfig + }), {}); + return this.fb.group(config); + } + + onClickSave(value: any): void { + this.dialogRef.close({ + title: value.findingTitle, + severity: value.findingSeverity, + description: value.findingDescription, + impact: value.findingImpact, + affectedUrls: this.affectedUrls ? this.affectedUrls : [], + reproduction: value.findingReproduction, + mitigation: value.findingMitigation + }); + } + + onAffectedUrlAdd(): void { + // tslint:disable-next-line:no-string-literal + const newUrl = this.findingFormGroup.controls['findingAffectedUrls'].value; + if (newUrl) { + this.affectedUrls.push(newUrl); + } + // tslint:disable-next-line:no-string-literal + this.findingFormGroup.controls['findingAffectedUrls'].reset(''); + } + + onAffectedUrlTagRemove(tagToRemove: NbTagComponent): void { + this.affectedUrls = this.affectedUrls.filter(t => t !== tagToRemove.text); + } + + onClickClose(): void { + this.dialogRef.close(); + } + + allowSave(): boolean { + return this.findingFormGroup.valid && this.findingDataChanged(); + } + + /** + * @return the correct nb-status for current severity + */ + getSeverityFillStatus(value: number): string { + let severityFillStatus; + switch (value) { + case 0: { + severityFillStatus = 'basic'; + break; + } + case 1: { + severityFillStatus = 'info'; + break; + } + case 2: { + severityFillStatus = 'warning'; + break; + } + case 3: { + severityFillStatus = 'danger'; + break; + } + default: { + severityFillStatus = 'control'; + break; + } + } + return severityFillStatus; + } + + /** + * @return true if finding data is different from initial value + */ + private findingDataChanged(): boolean { + const oldFindingData = this.parseInitializedFindingDialogData(this.dialogData); + const newFindingData = this.findingFormGroup.getRawValue(); + Object.entries(newFindingData).forEach(entry => { + const [key, value] = entry; + if (value === null) { + newFindingData[key] = ''; + } + }); + const didChange = !deepEqual(oldFindingData, newFindingData); + return didChange; + } + + /** + * @param dialogData of type GenericDialogData + * @return parsed findingData + */ + private parseInitializedFindingDialogData(dialogData: GenericDialogData): any { + const findingData = {}; + Object.entries(dialogData.form).forEach(entry => { + const [key, value] = entry; + findingData[key] = value.controlsConfig[0] ? + (value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : ''; + }); + return findingData; + } +} + +interface SeverityText { + value: Severity; + translationText: string; +} diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.module.ts b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.module.ts new file mode 100644 index 0000000..f985d7c --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component'; +import {DialogService} from '@shared/services/dialog-service/dialog.service'; +import {NbButtonModule, NbCardModule, NbDialogService, NbFormFieldModule, NbInputModule, NbSelectModule, NbTagModule} from '@nebular/theme'; +import {FindingDialogService} from '@shared/modules/finding-dialog/service/finding-dialog.service'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {TranslateModule} from '@ngx-translate/core'; +import {ReactiveFormsModule} from '@angular/forms'; + +@NgModule({ + declarations: [ + FindingDialogComponent + ], + imports: [ + CommonModule, + NbCardModule, + NbButtonModule, + NbFormFieldModule, + NbInputModule, + FlexLayoutModule, + FontAwesomeModule, + TranslateModule, + ReactiveFormsModule, + NbSelectModule, + NbTagModule + ], + providers: [ + DialogService, + FindingDialogService, + NbDialogService + ], + entryComponents: [ + FindingDialogComponent + ] +}) +export class FindingDialogModule { } diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.mock.ts b/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.mock.ts new file mode 100644 index 0000000..9df2c35 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.mock.ts @@ -0,0 +1,17 @@ +import {ComponentType} from '@angular/cdk/overlay'; +import {NbDialogConfig} from '@nebular/theme'; +import {Observable, of} from 'rxjs'; +import {FindingDialogService} from '@shared/modules/finding-dialog/service/finding-dialog.service'; +import {Finding} from '@shared/models/finding.model'; + +export class FindingDialogServiceMock implements Required { + + dialog: any; + + openFindingDialog( + componentOrTemplateRef: ComponentType, + finding: Finding | undefined, + config: Partial | string>> | undefined): Observable { + return of(undefined); + } +} diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.spec.ts b/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.spec.ts new file mode 100644 index 0000000..f3bfaba --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.spec.ts @@ -0,0 +1,30 @@ +import { TestBed } from '@angular/core/testing'; + +import { FindingDialogService } from './finding-dialog.service'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {NbDialogModule, NbDialogRef} from '@nebular/theme'; +import {FindingDialogServiceMock} from '@shared/modules/finding-dialog/service/finding-dialog.service.mock'; + +describe('FindingDialogService', () => { + let service: FindingDialogService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + BrowserAnimationsModule, + NbDialogModule.forRoot() + ], + providers: [ + {provide: FindingDialogService, useClass: FindingDialogServiceMock}, + {provide: NbDialogRef, useValue: {}}, + ] + }); + service = TestBed.inject(FindingDialogService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.ts b/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.ts new file mode 100644 index 0000000..6157a2e --- /dev/null +++ b/security-c4po-angular/src/shared/modules/finding-dialog/service/finding-dialog.service.ts @@ -0,0 +1,155 @@ +import {Injectable} from '@angular/core'; +import {NbDialogConfig, NbDialogService} from '@nebular/theme'; +import {GenericDialogData} from '@shared/models/generic-dialog-data'; +import {ComponentType} from '@angular/cdk/overlay'; +import {Observable} from 'rxjs'; +import {Validators} from '@angular/forms'; +import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component'; +import {Finding} from '@shared/models/finding.model'; +import {Severity} from '@shared/models/severity.enum'; + +@Injectable() +export class FindingDialogService { + + constructor(private readonly dialog: NbDialogService) { + } + + private readonly MIN_LENGTH: number = 4; + + static addDataToDialogConfig( + dialogOptions?: Partial | string>>, + findingData?: GenericDialogData + ): Partial | string>> { + return { + context: {data: findingData}, + closeOnEsc: dialogOptions?.closeOnEsc || false, + hasScroll: dialogOptions?.hasScroll || false, + autoFocus: dialogOptions?.autoFocus || false, + closeOnBackdropClick: dialogOptions?.closeOnBackdropClick || false + }; + } + + public openFindingDialog(componentOrTemplateRef: ComponentType, + finding?: Finding, + config?: Partial | string>>): Observable { + let dialogOptions: Partial | string>>; + let dialogData: GenericDialogData; + // Setup FindingDialogBody + dialogData = { + form: { + findingTitle: { + fieldName: 'findingTitle', + type: 'formText', + labelKey: 'finding.title.label', + placeholder: 'finding.title', + controlsConfig: [ + {value: finding ? finding.title : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.titleRequired'} + ] + }, + findingSeverity: { + fieldName: 'findingSeverity', + type: 'severity-select', + labelKey: 'finding.severity.label', + placeholder: 'finding.severity', + controlsConfig: [ + {value: finding ? finding.severity : Severity.LOW, disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.severityRequired'} + ] + }, + findingDescription: { + fieldName: 'findingDescription', + type: 'formText', + labelKey: 'finding.description.label', + placeholder: 'finding.description', + controlsConfig: [ + {value: finding ? finding.description : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.descriptionRequired'} + ] + }, + findingImpact: { + fieldName: 'findingImpact', + type: 'formText', + labelKey: 'finding.impact.label', + placeholder: 'finding.impact', + controlsConfig: [ + {value: finding ? finding.impact : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.impactRequired'} + ] + }, + findingAffectedUrls: { + fieldName: 'findingAffectedUrls', + type: 'text', + labelKey: 'finding.affectedUrls.label', + placeholder: 'finding.affectedUrls.placeholder', + controlsConfig: [ + {value: '', disabled: false}, + [] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.affectedUrlsRequired'} + ] + }, + findingReproduction: { + fieldName: 'findingReproduction', + type: 'text', + labelKey: 'finding.reproduction.label', + placeholder: 'finding.reproduction', + controlsConfig: [ + {value: finding ? finding.reproduction : '', disabled: false}, + [Validators.required] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.reproductionRequired'} + ] + }, + findingMitigation: { + fieldName: 'findingMitigation', + type: 'text', + labelKey: 'finding.mitigation.label', + placeholder: 'finding.mitigation', + controlsConfig: [ + {value: finding ? finding.mitigation : '', disabled: false}, + [] + ], + errors: [ + {errorCode: 'required', translationKey: 'finding.validationMessage.mitigationRequired'} + ] + } + }, + options: [] + }; + if (finding) { + dialogData.options = [ + { + headerLabelKey: 'finding.edit.header', + buttonKey: 'global.action.update', + accentColor: 'warning' + }, + ]; + } else { + dialogData.options = [ + { + headerLabelKey: 'finding.create.header', + buttonKey: 'global.action.save', + accentColor: 'info' + }, + ]; + } + // Merge dialog config with finding data + dialogOptions = FindingDialogService.addDataToDialogConfig(config, dialogData); + return this.dialog.open(FindingDialogComponent, dialogOptions).onClose; + } +} diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html index 157d43e..b3d85a3 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html +++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html @@ -16,7 +16,7 @@ diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss index 7fc74d7..63123ba 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss +++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss @@ -20,7 +20,7 @@ padding-bottom: 0.5rem; } - .input { + .form-field { width: 18rem; margin-bottom: 0.5rem; } diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts index 52e5d76..80a5c54 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts +++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts @@ -24,7 +24,6 @@ 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 deepEqual from 'deep-equal'; describe('ProjectDialogComponent', () => { let component: ProjectDialogComponent; @@ -62,13 +61,13 @@ describe('ProjectDialogComponent', () => { {provide: NotificationService, useValue: new NotificationServiceMock()}, {provide: DialogService, useClass: DialogServiceMock}, {provide: NbDialogRef, useValue: dialogSpy}, - {provide: NB_DIALOG_CONFIG, useValue: mockedDialogData} + {provide: NB_DIALOG_CONFIG, useValue: mockedProjectDialogData} ] }).compileComponents(); }); beforeEach(() => { - TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedDialogData}); + TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedProjectDialogData}); fixture = TestBed.createComponent(ProjectDialogComponent); component = fixture.componentInstance; fixture.detectChanges(); @@ -97,7 +96,7 @@ export const mockProject: Project = { createdBy: 'UID-11-12-13' }; -export const mockedDialogData = { +export const mockedProjectDialogData = { form: { projectTitle: { fieldName: 'projectTitle', diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts index cb8cb1d..3952cdf 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts +++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts @@ -1,7 +1,7 @@ import {Component, Inject, OnInit} from '@angular/core'; import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme'; import {FormBuilder, FormGroup} from '@angular/forms'; -import {GenericFormFieldConfig, ProjectDialogData} from '@shared/models/project-dialog-data'; +import {GenericFormFieldConfig, GenericDialogData} from '@shared/models/generic-dialog-data'; import deepEqual from 'deep-equal'; import {UntilDestroy} from '@ngneat/until-destroy'; @@ -16,10 +16,10 @@ export class ProjectDialogComponent implements OnInit { projectFormGroup: FormGroup; formArray: GenericFormFieldConfig[]; - dialogData: ProjectDialogData; + dialogData: GenericDialogData; constructor( - @Inject(NB_DIALOG_CONFIG) private data: ProjectDialogData, + @Inject(NB_DIALOG_CONFIG) private data: GenericDialogData, private fb: FormBuilder, protected dialogRef: NbDialogRef ) { @@ -75,7 +75,7 @@ export class ProjectDialogComponent implements OnInit { * @param dialogData of type ProjectDialogData * @return parsed projectData */ - private parseInitializedProjectDialogData(dialogData: ProjectDialogData): any { + private parseInitializedProjectDialogData(dialogData: GenericDialogData): any { const projectData = {}; Object.entries(dialogData.form).forEach(entry => { const [key, value] = entry; diff --git a/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts index 1e89fba..76cf5b6 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts +++ b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts @@ -5,7 +5,7 @@ import {Observable} from 'rxjs'; import {Project} from '@shared/models/project.model'; import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component'; import {Validators} from '@angular/forms'; -import {ProjectDialogData} from '@shared/models/project-dialog-data'; +import {GenericDialogData} from '@shared/models/generic-dialog-data'; @Injectable() export class ProjectDialogService { @@ -17,7 +17,7 @@ export class ProjectDialogService { static addDataToDialogConfig( dialogOptions?: Partial | string>>, - projectData?: ProjectDialogData + projectData?: GenericDialogData ): Partial | string>> { return { context: {data: projectData}, @@ -32,7 +32,7 @@ export class ProjectDialogService { project?: Project, config?: Partial | string>>): Observable { let dialogOptions: Partial | string>>; - let dialogData: ProjectDialogData; + let dialogData: GenericDialogData; // Setup ProjectDialogData dialogData = { form: { @@ -91,7 +91,7 @@ export class ProjectDialogService { { headerLabelKey: 'project.create.header', buttonKey: 'global.action.save', - accentColor: 'primary' + accentColor: 'info' }, ]; } diff --git a/security-c4po-angular/src/shared/services/pentest.service.ts b/security-c4po-angular/src/shared/services/pentest.service.ts index a7df8f1..9f8e025 100644 --- a/security-c4po-angular/src/shared/services/pentest.service.ts +++ b/security-c4po-angular/src/shared/services/pentest.service.ts @@ -8,7 +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} from '@shared/models/finding.model'; +import {Finding, FindingDialogBody} from '@shared/models/finding.model'; import {Severity} from '@shared/models/severity.enum'; import {Comment} from '@shared/models/comment.model'; @@ -68,32 +68,49 @@ export class PentestService { return of([ { id: 'ca96cc19-88ff-4874-8406-dc892620afd4', - title: 'This is a lit test finding ma brother', - impact: 'fucked up a lot man. better fix it', + 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 lit test finding ma brother', - impact: 'fucked up a lot man. better fix it', + 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 lit test finding ma brother', - impact: 'fucked up a lot man. better fix it', + 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 lit test finding ma brother', - impact: 'fucked up a lot man. better fix it', + title: 'This is a creative title', + description: 'test', + impact: 'This is impacts a lot', severity: Severity.CRITICAL, + reproduction: '' } ]); } } + /** + * Save Finding + * @param pentestId the id of the pentest + * @param finding the information of the finding + */ + public saveFinding(pentestId: string, finding: FindingDialogBody): Observable { + return this.http.post(`${this.apiBaseURL}/${pentestId}/finding`, finding); + } + /** * Get Comments for Pentest Id * @param pentestId the id of the project @@ -108,14 +125,14 @@ export class PentestService { 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', + 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 lit test finding ma brother', - description: 'fucked up a lot man. better fix it', + title: 'This is a creative title', + description: 'This is a creative description', relatedFindings: [], } ]); diff --git a/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.spec.ts b/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.spec.ts index 3a3f3b1..a080bfb 100644 --- a/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.spec.ts +++ b/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.spec.ts @@ -4,9 +4,7 @@ import {StatusTagComponent} from './status-tag.component'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {HttpLoaderFactory} from '../../../app/common-app.module'; import {HttpClient} from '@angular/common/http'; -import {NbCardModule, NbFocusMonitor, NbTagModule} from '@nebular/theme'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {CommonModule} from '@angular/common'; +import {NbCardModule, NbTagModule} from '@nebular/theme'; import {MockModule} from 'ng-mocks'; import {HttpClientTestingModule} from '@angular/common/http/testing'; diff --git a/security-c4po-api/src/main/kotlin/finding/FindingEntity.kt b/security-c4po-api/src/main/kotlin/finding/FindingEntity.kt index 805e373..026dbba 100644 --- a/security-c4po-api/src/main/kotlin/finding/FindingEntity.kt +++ b/security-c4po-api/src/main/kotlin/finding/FindingEntity.kt @@ -1,7 +1,6 @@ package finding import com.securityc4po.api.BaseEntity -import comment.Comment import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "findings")