From 2ab6940673926804737d24683e7d2047858743f3 Mon Sep 17 00:00:00 2001 From: Marcel Haag Date: Fri, 23 Dec 2022 10:17:06 +0100 Subject: [PATCH] fix: design pattern and QoL Improvements --- security-c4po-angular/package-lock.json | 21 +- security-c4po-angular/package.json | 1 + .../pentest-comments.component.html | 7 +- .../pentest-comments.component.scss | 12 +- .../pentest-findings.component.html | 4 +- .../pentest-findings.component.ts | 16 +- .../src/app/pentest/pentest.module.ts | 46 +- .../src/assets/i18n/de-DE.json | 2 +- .../src/assets/i18n/en-US.json | 2 +- .../comment-dialog.component.ts | 5 +- .../shared/services/finding.service.spec.ts | 26 ++ .../src/shared/services/finding.service.ts | 62 +++ .../src/shared/services/pentest.service.ts | 45 -- .../stores/project-state/project-state.ts | 12 +- security-c4po-angular/tsconfig.json | 7 +- .../api/pentest/PentestController.kt | 58 +-- .../api/pentest/PentestService.kt | 6 +- .../api/{ => pentest}/comment/Comment.kt | 4 +- .../comment/CommentController.kt | 4 +- .../{ => pentest}/comment/CommentEntity.kt | 2 +- .../comment/CommentRepository.kt | 2 +- .../{ => pentest}/comment/CommentService.kt | 3 +- .../api/{ => pentest}/finding/Finding.kt | 2 +- .../api/pentest/finding/FindingController.kt | 85 ++++ .../{ => pentest}/finding/FindingEntity.kt | 2 +- .../finding/FindingRepository.kt | 2 +- .../{ => pentest}/finding/FindingService.kt | 8 +- .../api/{ => pentest}/finding/Severity.kt | 2 +- .../PentestControllerDocumentationTest.kt | 299 +------------ .../api/pentest/PentestControllerIntTest.kt | 157 +------ .../CommentControllerDocumentationTest.kt | 6 +- .../CommentControllerIntegrationTest.kt | 6 +- .../FindingControllerDocumentationTest.kt | 403 ++++++++++++++++++ .../finding/FindingControllerIntTest.kt | 270 ++++++++++++ .../test/resources/collections/comments.json | 10 +- 35 files changed, 972 insertions(+), 627 deletions(-) create mode 100644 security-c4po-angular/src/shared/services/finding.service.spec.ts create mode 100644 security-c4po-angular/src/shared/services/finding.service.ts rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/comment/Comment.kt (93%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/comment/CommentController.kt (94%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/comment/CommentEntity.kt (89%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/comment/CommentRepository.kt (93%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/comment/CommentService.kt (97%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/finding/Finding.kt (98%) create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingController.kt rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/finding/FindingEntity.kt (91%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/finding/FindingRepository.kt (93%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/finding/FindingService.kt (96%) rename security-c4po-api/src/main/kotlin/com/securityc4po/api/{ => pentest}/finding/Severity.kt (60%) rename security-c4po-api/src/test/kotlin/com/securityc4po/api/{ => pentest}/comment/CommentControllerDocumentationTest.kt (97%) rename security-c4po-api/src/test/kotlin/com/securityc4po/api/{ => pentest}/comment/CommentControllerIntegrationTest.kt (95%) create mode 100644 security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerDocumentationTest.kt create mode 100644 security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerIntTest.kt diff --git a/security-c4po-angular/package-lock.json b/security-c4po-angular/package-lock.json index 122e791..f38a711 100644 --- a/security-c4po-angular/package-lock.json +++ b/security-c4po-angular/package-lock.json @@ -2997,6 +2997,21 @@ "tslib": "^2.0.0" } }, + "@ngxs/storage-plugin": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@ngxs/storage-plugin/-/storage-plugin-3.7.6.tgz", + "integrity": "sha512-jeeoUgsMdb4M8kIHdnO0X6I73okmRJhMW9znaqhYF/aZ+PiQrogdJ5xvSXmoQ7Gg57YfQEhVErM+zf/HD7fnMA==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@ngxs/store": { "version": "3.7.4", "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.7.4.tgz", @@ -11338,9 +11353,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.38", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.38.tgz", - "integrity": "sha512-nMIrzGah4+oYZPflDvLZUgoVUO4fvAqHstvG3xAUnMolWncuAiLDWNnJZj6EwJGMGfb1ZcuTFE6GI3hNOVWI/Q==", + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", + "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", "requires": { "moment": ">= 2.9.0" } diff --git a/security-c4po-angular/package.json b/security-c4po-angular/package.json index 6deb8d9..df1565c 100644 --- a/security-c4po-angular/package.json +++ b/security-c4po-angular/package.json @@ -32,6 +32,7 @@ "@ngneat/until-destroy": "~8.0.4", "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", + "@ngxs/storage-plugin": "^3.7.3", "@ngxs/store": "^3.7.3", "eva-icons": "^1.1.3", "i18n-iso-countries": "^6.8.0", diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html index e1053b0..a53374e 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html @@ -38,7 +38,12 @@ {{ 'comment.relatedFindings' | translate }} - {{ comment.data['relatedFindings'].length ? comment.data['relatedFindings'] : 'comment.no.relatedFindings' | translate }} + + + + + {{ 'comment.no.relatedFindings' | translate }} + diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.scss b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.scss index da388fe..1e7c7c9 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.scss +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.scss @@ -7,7 +7,8 @@ .comment-cell { // Add style here height: 4.5rem !important; - max-height: 4.5rem !important; + // max-height: 4.5rem !important; + overflow: hidden; } .comment-cell:hover { @@ -15,10 +16,17 @@ background-color: nb-theme(color-basic-transparent-focus); } + .cell { + height: 4.5rem !important; + max-height: 4.5rem !important; + } + .related-finding-cell { + height: 4.5rem !important; + max-height: 4.5rem !important; // cursor: pointer; font-family: Courier, serif; - color: nb-theme(color-info-default); + color: nb-theme(color-danger-default); } .cell-actions { diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.html b/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.html index fcd12bb..de72409 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.html +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-findings/pentest-findings.component.html @@ -20,7 +20,9 @@ {{ 'finding.severity' | translate }} - + + + 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 9ee360e..08f47fe 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 @@ -22,6 +22,7 @@ import {Store} from '@ngxs/store'; import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions'; import {ProjectState} from '@shared/stores/project-state/project-state'; import {DialogService} from '@shared/services/dialog-service/dialog.service'; +import {FindingService} from '@shared/services/finding.service'; @UntilDestroy() @Component({ @@ -31,7 +32,7 @@ import {DialogService} from '@shared/services/dialog-service/dialog.service'; }) export class PentestFindingsComponent implements OnInit { - constructor(private readonly pentestService: PentestService, + constructor(private readonly findingService: FindingService, private dataSourceBuilder: NbTreeGridDataSourceBuilder, private notificationService: NotificationService, private dialogService: DialogService, @@ -75,7 +76,7 @@ export class PentestFindingsComponent implements OnInit { } loadFindingsData(): void { - this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '') + this.findingService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '') .pipe( untilDestroyed(this), /*filter(isNotNullOrUndefined),*/ @@ -115,7 +116,7 @@ export class PentestFindingsComponent implements OnInit { filter(value => !!value), /*tap((value) => console.warn('FindingDialogBody: ', value)),*/ mergeMap((value: FindingDialogBody) => - this.pentestService.saveFinding( + this.findingService.saveFinding( this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '', transformFindingToRequestBody(value) ) @@ -135,7 +136,7 @@ export class PentestFindingsComponent implements OnInit { } onClickEditFinding(findingEntry): void { - this.pentestService.getFindingById(findingEntry.data.findingId).pipe( + this.findingService.getFindingById(findingEntry.data.findingId).pipe( filter(isNotNullOrUndefined), untilDestroyed(this) ).subscribe({ @@ -154,7 +155,7 @@ export class PentestFindingsComponent implements OnInit { filter(value => !!value), /*tap((value) => console.warn('FindingDialogBody: ', value)),*/ mergeMap((value: FindingDialogBody) => - this.pentestService.updateFinding( + this.findingService.updateFinding( findingEntry.data.findingId, transformFindingToRequestBody(value) ) @@ -191,7 +192,7 @@ export class PentestFindingsComponent implements OnInit { message ).onClose.pipe( filter((confirm) => !!confirm), - switchMap(() => this.pentestService.deleteFindingByPentestAndFindingId( + switchMap(() => this.findingService.deleteFindingByPentestAndFindingId( this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '', findingEntry.data.findingId) ), @@ -201,7 +202,8 @@ export class PentestFindingsComponent implements OnInit { }), untilDestroyed(this) ).subscribe({ - next: () => { + next: (deletedFinding: any) => { + this.store.dispatch(new UpdatePentestFindings(deletedFinding.id)); this.loadFindingsData(); this.notificationService.showPopup('finding.popup.delete.success', PopupType.SUCCESS); }, error: error => { diff --git a/security-c4po-angular/src/app/pentest/pentest.module.ts b/security-c4po-angular/src/app/pentest/pentest.module.ts index 0f244b3..1395da2 100644 --- a/security-c4po-angular/src/app/pentest/pentest.module.ts +++ b/security-c4po-angular/src/app/pentest/pentest.module.ts @@ -16,6 +16,7 @@ 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'; import {CommentDialogModule} from '@shared/modules/comment-dialog/comment-dialog.module'; +import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.module'; @NgModule({ declarations: [ @@ -26,28 +27,29 @@ import {CommentDialogModule} from '@shared/modules/comment-dialog/comment-dialog PentestFindingsComponent, PentestCommentsComponent ], - imports: [ - CommonModule, - CommonAppModule, - RouterModule.forChild([{ - path: '', - component: PentestComponent - }]), - NbLayoutModule, - NbCardModule, - FlexLayoutModule, - FontAwesomeModule, - TranslateModule, - NbButtonModule, - StatusTagModule, - NbTabsetModule, - NbTreeGridModule, - SeverityTagModule, - NbSelectModule, - // Dialog Modules - FindingDialogModule, - CommentDialogModule, - ] + imports: [ + CommonModule, + CommonAppModule, + RouterModule.forChild([{ + path: '', + component: PentestComponent + }]), + NbLayoutModule, + NbCardModule, + FlexLayoutModule, + FontAwesomeModule, + TranslateModule, + NbButtonModule, + StatusTagModule, + NbTabsetModule, + NbTreeGridModule, + SeverityTagModule, + NbSelectModule, + // Dialog Modules + FindingDialogModule, + CommentDialogModule, + FindigWidgetModule, + ] }) 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 eb25746..1044e31 100644 --- a/security-c4po-angular/src/assets/i18n/de-DE.json +++ b/security-c4po-angular/src/assets/i18n/de-DE.json @@ -153,7 +153,7 @@ "add": "Kommentar hinzufügen", "add.finding": "Fund hinzufügen", "no.comments": "Keine Kommentare verfügbar", - "no.relatedFindings": "Nicht verbunden mit Fund", + "no.relatedFindings": "Nicht verbunden mit einem Fund", "relatedFindingsPlaceholder": "Fund auswählen", "noFindingsInObjectivePlaceholder": "Objective hat keine Befunde, auf die es sich beziehen könnte.", "create": { diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json index 405e4ae..f08f30f 100644 --- a/security-c4po-angular/src/assets/i18n/en-US.json +++ b/security-c4po-angular/src/assets/i18n/en-US.json @@ -153,7 +153,7 @@ "add": "Add Comment", "add.finding": "Add related finding", "no.comments": "No comments available", - "no.relatedFindings": "Not related to finding", + "no.relatedFindings": "Not related to any finding", "relatedFindingsPlaceholder": "Select a related finding", "noFindingsInObjectivePlaceholder": "Objective doesn't have any findings to relate to.", "create": { diff --git a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.ts b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.ts index 4ba9e7e..c0c4cbd 100644 --- a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.ts +++ b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.ts @@ -12,6 +12,7 @@ import {Store} from '@ngxs/store'; import {Finding} from '@shared/models/finding.model'; import {RelatedFindingOption} from '@shared/models/comment.model'; import {BehaviorSubject} from 'rxjs'; +import {FindingService} from '@shared/services/finding.service'; @Component({ selector: 'app-comment-dialog', @@ -39,7 +40,7 @@ export class CommentDialogComponent implements OnInit { @Inject(NB_DIALOG_CONFIG) private data: GenericDialogData, private fb: FormBuilder, protected dialogRef: NbDialogRef, - private readonly pentestService: PentestService, + private readonly findingService: FindingService, private store: Store ) { } @@ -95,7 +96,7 @@ export class CommentDialogComponent implements OnInit { } requestRelatedFindingsData(pentestId: string, relatedFindings: any): void { - this.pentestService.getFindingsByPentestId(pentestId).pipe( + this.findingService.getFindingsByPentestId(pentestId).pipe( untilDestroyed(this) ).subscribe({ next: (findings: Finding[]) => { diff --git a/security-c4po-angular/src/shared/services/finding.service.spec.ts b/security-c4po-angular/src/shared/services/finding.service.spec.ts new file mode 100644 index 0000000..bd27e83 --- /dev/null +++ b/security-c4po-angular/src/shared/services/finding.service.spec.ts @@ -0,0 +1,26 @@ +import { TestBed } from '@angular/core/testing'; + +import { FindingService } from './finding.service'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {NgxsModule} from '@ngxs/store'; +import {ProjectState} from '@shared/stores/project-state/project-state'; + +describe('FindingService', () => { + let service: FindingService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + BrowserAnimationsModule, + NgxsModule.forRoot([ProjectState]) + ] + }); + service = TestBed.inject(FindingService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/services/finding.service.ts b/security-c4po-angular/src/shared/services/finding.service.ts new file mode 100644 index 0000000..16cd9e5 --- /dev/null +++ b/security-c4po-angular/src/shared/services/finding.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@angular/core'; +import {environment} from '../../environments/environment'; +import {HttpClient} from '@angular/common/http'; +import {Store} from '@ngxs/store'; +import {Observable} from 'rxjs'; +import {Finding} from '@shared/models/finding.model'; + +@Injectable({ + providedIn: 'root' +}) +export class FindingService { + + private apiBaseURL = `${environment.apiEndpoint}/pentests`; + + constructor( + private http: HttpClient, + private readonly store: Store) { + } + + /** + * Get Findings for Pentest Id + * @param pentestId the id of the project + */ + public getFindingsByPentestId(pentestId: string): Observable { + return this.http.get(`${this.apiBaseURL}/${pentestId}/findings`); + } + + /** + * Get Finding by Id + * @param findingId the id of the finding + */ + public getFindingById(findingId: string): Observable { + return this.http.get(`${this.apiBaseURL}/${findingId}/finding`); + } + + /** + * Save Finding + * @param pentestId the id of the pentest + * @param finding the information of the finding + */ + public saveFinding(pentestId: string, finding: Finding): Observable { + return this.http.post(`${this.apiBaseURL}/${pentestId}/finding`, finding); + } + + /** + * Update Finding + * @param findingId the id of the finding + * @param finding the information of the finding + */ + public updateFinding(findingId: string, finding: Finding): Observable { + return this.http.patch(`${this.apiBaseURL}/${findingId}/finding`, finding); + } + + /** + * Delete Finding + * @param pentestId the id of the pentest + * @param findingId the id of the finding + */ + public deleteFindingByPentestAndFindingId(pentestId: string, findingId: string): Observable { + return this.http.delete(`${this.apiBaseURL}/${pentestId}/finding/${findingId}`); + } +} diff --git a/security-c4po-angular/src/shared/services/pentest.service.ts b/security-c4po-angular/src/shared/services/pentest.service.ts index dbe17d6..4e23836 100644 --- a/security-c4po-angular/src/shared/services/pentest.service.ts +++ b/security-c4po-angular/src/shared/services/pentest.service.ts @@ -9,8 +9,6 @@ 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 {Comment} from '@shared/models/comment.model'; -import {v4 as UUID} from 'uuid'; @Injectable({ providedIn: 'root' @@ -78,47 +76,4 @@ export class PentestService { public updatePentest(pentest: Pentest): Observable { return this.http.patch(`${this.apiBaseURL}/${pentest.id}`, pentest); } - - /** - * Get Findings for Pentest Id - * @param pentestId the id of the project - */ - public getFindingsByPentestId(pentestId: string): Observable { - return this.http.get(`${this.apiBaseURL}/${pentestId}/findings`); - } - - /** - * Get Finding by Id - * @param findingId the id of the finding - */ - public getFindingById(findingId: string): Observable { - return this.http.get(`${this.apiBaseURL}/${findingId}/finding`); - } - - /** - * Save Finding - * @param pentestId the id of the pentest - * @param finding the information of the finding - */ - public saveFinding(pentestId: string, finding: Finding): Observable { - return this.http.post(`${this.apiBaseURL}/${pentestId}/finding`, finding); - } - - /** - * Update Finding - * @param findingId the id of the finding - * @param finding the information of the finding - */ - public updateFinding(findingId: string, finding: Finding): Observable { - return this.http.patch(`${this.apiBaseURL}/${findingId}/finding`, finding); - } - - /** - * Delete Finding - * @param pentestId the id of the pentest - * @param findingId the id of the finding - */ - public deleteFindingByPentestAndFindingId(pentestId: string, findingId: string): Observable { - return this.http.delete(`${this.apiBaseURL}/${pentestId}/finding/${findingId}`); - } } diff --git a/security-c4po-angular/src/shared/stores/project-state/project-state.ts b/security-c4po-angular/src/shared/stores/project-state/project-state.ts index 709a9ee..c13e323 100644 --- a/security-c4po-angular/src/shared/stores/project-state/project-state.ts +++ b/security-c4po-angular/src/shared/stores/project-state/project-state.ts @@ -23,6 +23,10 @@ export interface ProjectStateModel { selectedPentest: Pentest; } +export interface FindingMap { + [key: string]: string; +} + @State({ name: PROJECT_STATE_NAME, defaults: { @@ -91,12 +95,14 @@ export class ProjectState { updatePentestFindings(ctx: StateContext, {findingId}: UpdatePentestFindings): void { const state = ctx.getState(); let stateSelectedPentest: Pentest = state.selectedPentest; + // State objects const stateFindingIds: Array = stateSelectedPentest.findingIds || []; let updatedFindingIds: Array = []; if (!stateFindingIds.includes(findingId)) { updatedFindingIds = [...stateFindingIds, findingId]; } else { - // ToDo: Add logic to remove findingId from array + const findingIndex = stateFindingIds.indexOf(findingId); + updatedFindingIds = [...stateFindingIds.slice(0, findingIndex)]; } // overwrites only findingIds stateSelectedPentest = { @@ -113,12 +119,14 @@ export class ProjectState { updatePentestComments(ctx: StateContext, {commentId}: UpdatePentestComments): void { const state = ctx.getState(); let stateSelectedPentest: Pentest = state.selectedPentest; + // State objects const stateCommentIds: Array = stateSelectedPentest.commentIds || []; let updatedCommentIds: Array = []; if (!stateCommentIds.includes(commentId)) { updatedCommentIds = [...stateCommentIds, commentId]; } else { - // ToDo: Add logic to remove commentId from array + const commentIndex = stateCommentIds.indexOf(commentId); + updatedCommentIds = [...stateCommentIds.slice(0, commentIndex)]; } // overwrites only findingIds stateSelectedPentest = { diff --git a/security-c4po-angular/tsconfig.json b/security-c4po-angular/tsconfig.json index 4768eae..8a6b944 100644 --- a/security-c4po-angular/tsconfig.json +++ b/security-c4po-angular/tsconfig.json @@ -13,11 +13,8 @@ "moduleResolution": "node", "importHelpers": true, "target": "es2015", - "module": "es2020", - "lib": [ - "es2018", - "dom" - ], + "module": "esNext", + "lib": ["es2019", "es2018", "dom"], "paths": { "@shared/*": ["./src/shared/*"], "@assets/*": ["./src/assets/*"] diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt index 9c9cb05..317ac13 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt @@ -4,15 +4,10 @@ import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION import com.securityc4po.api.extensions.getLoggerFor import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import com.securityc4po.api.ResponseBody -import com.securityc4po.api.finding.FindingRequestBody -import com.securityc4po.api.finding.FindingService -import com.securityc4po.api.finding.toFindingDeleteResponseBody -import com.securityc4po.api.finding.toFindingResponseBody import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity.noContent import org.springframework.web.bind.annotation.* import reactor.core.publisher.Mono -import reactor.kotlin.core.publisher.switchIfEmpty @RestController @RequestMapping("/pentests") @@ -23,7 +18,7 @@ import reactor.kotlin.core.publisher.switchIfEmpty methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST, RequestMethod.PATCH] ) @SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION) -class PentestController(private val pentestService: PentestService, private val findingService: FindingService) { +class PentestController(private val pentestService: PentestService) { var logger = getLoggerFor() @@ -71,55 +66,4 @@ class PentestController(private val pentestService: PentestService, private val ResponseEntity.accepted().body(it.toPentestResponseBody()) } } - - @GetMapping("/{pentestId}/findings") - fun getFindings(@PathVariable(value = "pentestId") pentestId: String): Mono>> { - return this.pentestService.getFindingIdsByPentestId(pentestId).flatMap { findingIds: List -> - this.findingService.getFindingsByIds(findingIds).map { findingList -> - findingList.map { it.toFindingResponseBody() } - } - }.map { - if (it.isEmpty()) noContent().build() - else ResponseEntity.ok(it) - } - } - - @GetMapping("/{findingId}/finding") - fun getFinding(@PathVariable(value = "findingId") findingId: String):Mono> { - return this.findingService.getFindingById(findingId).map { - ResponseEntity.ok().body(it.toFindingResponseBody()) - } - } - - @PostMapping("/{pentestId}/finding") - fun saveFinding( - @PathVariable(value = "pentestId") pentestId: String, - @RequestBody body: FindingRequestBody - ): Mono> { - return this.findingService.saveFinding(pentestId, body).map { - ResponseEntity.accepted().body(it.toFindingResponseBody()) - } - } - - @PatchMapping("/{findingId}/finding") - fun updateFinding( - @PathVariable(value = "findingId") findingId: String, - @RequestBody body: FindingRequestBody - ): Mono> { - return this.findingService.updateFinding(findingId, body).map { - ResponseEntity.accepted().body(it.toFindingResponseBody()) - } - } - - @DeleteMapping("/{pentestId}/finding/{findingId}") - fun deleteFinding( - @PathVariable(value = "pentestId") pentestId: String, - @PathVariable(value = "findingId") findingId: String - ): Mono> { - return this.findingService.deleteFindingByPentestAndFindingId(pentestId, findingId).map { - ResponseEntity.ok().body(it.toFindingDeleteResponseBody()) - }.switchIfEmpty { - Mono.just(noContent().build()) - } - } } \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt index 044c0e1..3c41ea6 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt @@ -53,16 +53,16 @@ class PentestService(private val pentestRepository: PentestRepository, private v val pentest = body.toPentest() val pentestEntity = PentestEntity(pentest) return pentestRepository.insert(pentestEntity).flatMap { newPentestEntity: PentestEntity -> - val pentest = newPentestEntity.toPentest() + val newPentest = newPentestEntity.toPentest() // After successfully saving pentest add id and status to project - val projectPentest = ProjectPentest(pentestId = pentest.id, status = pentest.status) + val projectPentest = ProjectPentest(pentestId = newPentest.id, status = newPentest.status) projectService.updateProjectTestingProgress(projectId, projectPentest).onErrorMap { TransactionInterruptedException( "Project Pentests could not be updated in Database.", Errorcode.ProjectPentestInsertionFailed ) }.map { - pentest + newPentest } }.doOnError { throw wrappedException( diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/Comment.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt similarity index 93% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/Comment.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt index 39fedf4..3b7cebd 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/Comment.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt @@ -1,7 +1,7 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import com.securityc4po.api.ResponseBody -import com.securityc4po.api.finding.FindingRequestBody +import com.securityc4po.api.pentest.finding.FindingRequestBody import org.springframework.data.mongodb.core.index.Indexed import java.util.* diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt similarity index 94% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentController.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt index e1d5066..218ec22 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentController.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION import com.securityc4po.api.extensions.getLoggerFor @@ -6,7 +6,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import com.securityc4po.api.ResponseBody import com.securityc4po.api.configuration.error.handler.EntityNotFoundException import com.securityc4po.api.configuration.error.handler.Errorcode -import com.securityc4po.api.finding.toFindingResponseBody +import com.securityc4po.api.pentest.finding.toFindingResponseBody import com.securityc4po.api.pentest.PentestService import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity.noContent diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt similarity index 89% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentEntity.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt index cc7352c..79af7b8 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import com.securityc4po.api.BaseEntity import org.springframework.data.mongodb.core.mapping.Document diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentRepository.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentRepository.kt similarity index 93% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentRepository.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentRepository.kt index a19ba17..02dd8eb 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentRepository.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentRepository.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import org.springframework.data.mongodb.repository.DeleteQuery import org.springframework.data.mongodb.repository.Query diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt similarity index 97% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentService.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt index f76ef5c..f023426 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/comment/CommentService.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION @@ -7,7 +7,6 @@ import com.securityc4po.api.configuration.error.handler.EntityNotFoundException import com.securityc4po.api.configuration.error.handler.InvalidModelException import com.securityc4po.api.configuration.error.handler.TransactionInterruptedException import com.securityc4po.api.extensions.getLoggerFor -import com.securityc4po.api.finding.* import com.securityc4po.api.pentest.PentestService import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.springframework.stereotype.Service diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/Finding.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt similarity index 98% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/Finding.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt index 268f5f6..694251c 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/Finding.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.finding +package com.securityc4po.api.pentest.finding import com.securityc4po.api.ResponseBody import org.springframework.data.mongodb.core.index.Indexed diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingController.kt new file mode 100644 index 0000000..f081ace --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingController.kt @@ -0,0 +1,85 @@ +package com.securityc4po.api.pentest.finding + +import com.securityc4po.api.pentest.PentestCategory +import com.securityc4po.api.pentest.PentestRequestBody +import com.securityc4po.api.pentest.PentestService +import com.securityc4po.api.pentest.toPentestResponseBody +import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION +import com.securityc4po.api.extensions.getLoggerFor +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import com.securityc4po.api.ResponseBody +import com.securityc4po.api.pentest.finding.FindingRequestBody +import com.securityc4po.api.pentest.finding.FindingService +import com.securityc4po.api.pentest.finding.toFindingDeleteResponseBody +import com.securityc4po.api.pentest.finding.toFindingResponseBody +import org.springframework.http.ResponseEntity +import org.springframework.http.ResponseEntity.noContent +import org.springframework.web.bind.annotation.* +import reactor.core.publisher.Mono +import reactor.kotlin.core.publisher.switchIfEmpty + +@RestController +@RequestMapping("/pentests") +@CrossOrigin( + origins = [], + allowCredentials = "false", + allowedHeaders = ["*"], + methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST, RequestMethod.PATCH] +) +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION) +class FindingController(private val pentestService: PentestService, private val findingService: FindingService) { + + var logger = getLoggerFor() + + @GetMapping("/{pentestId}/findings") + fun getFindings(@PathVariable(value = "pentestId") pentestId: String): Mono>> { + return this.pentestService.getFindingIdsByPentestId(pentestId).flatMap { findingIds: List -> + this.findingService.getFindingsByIds(findingIds).map { findingList -> + findingList.map { it.toFindingResponseBody() } + } + }.map { + if (it.isEmpty()) noContent().build() + else ResponseEntity.ok(it) + } + } + + @GetMapping("/{findingId}/finding") + fun getFinding(@PathVariable(value = "findingId") findingId: String):Mono> { + return this.findingService.getFindingById(findingId).map { + ResponseEntity.ok().body(it.toFindingResponseBody()) + } + } + + @PostMapping("/{pentestId}/finding") + fun saveFinding( + @PathVariable(value = "pentestId") pentestId: String, + @RequestBody body: FindingRequestBody + ): Mono> { + return this.findingService.saveFinding(pentestId, body).map { + ResponseEntity.accepted().body(it.toFindingResponseBody()) + } + } + + @PatchMapping("/{findingId}/finding") + fun updateFinding( + @PathVariable(value = "findingId") findingId: String, + @RequestBody body: FindingRequestBody + ): Mono> { + return this.findingService.updateFinding(findingId, body).map { + ResponseEntity.accepted().body(it.toFindingResponseBody()) + } + } + + @DeleteMapping("/{pentestId}/finding/{findingId}") + fun deleteFinding( + @PathVariable(value = "pentestId") pentestId: String, + @PathVariable(value = "findingId") findingId: String + ): Mono> { + // ToDo: Add remove finding from comment as well + return this.findingService.deleteFindingByPentestAndFindingId(pentestId, findingId).map { + ResponseEntity.ok().body(it.toFindingDeleteResponseBody()) + }.switchIfEmpty { + Mono.just(noContent().build()) + } + } +} \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt similarity index 91% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingEntity.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt index 93f2d79..4928dab 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.finding +package com.securityc4po.api.pentest.finding import com.securityc4po.api.BaseEntity import org.springframework.data.mongodb.core.mapping.Document diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingRepository.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingRepository.kt similarity index 93% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingRepository.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingRepository.kt index c4d5777..7580352 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingRepository.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingRepository.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.finding +package com.securityc4po.api.pentest.finding import org.springframework.data.mongodb.repository.DeleteQuery import org.springframework.data.mongodb.repository.Query diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingService.kt similarity index 96% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingService.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingService.kt index b0fb231..ee9a761 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/FindingService.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingService.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.finding +package com.securityc4po.api.pentest.finding import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION @@ -37,15 +37,15 @@ class FindingService(private val findingRepository: FindingRepository, private v val finding = body.toFinding() val findingEntity = FindingEntity(finding) return findingRepository.insert(findingEntity).flatMap { newFindingEntity: FindingEntity -> - val finding = newFindingEntity.toFinding() + val newFinding = newFindingEntity.toFinding() // After successfully saving finding add id to pentest - pentestService.updatePentestFinding(pentestId, finding.id).onErrorMap { + pentestService.updatePentestFinding(pentestId, newFinding.id).onErrorMap { TransactionInterruptedException( "Pentest could not be updated in Database.", Errorcode.PentestInsertionFailed ) }.map { - finding + newFinding } }.doOnError { throw wrappedException( diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/Severity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Severity.kt similarity index 60% rename from security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/Severity.kt rename to security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Severity.kt index bd93d30..9412362 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/finding/Severity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Severity.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.finding +package com.securityc4po.api.pentest.finding enum class Severity { LOW, diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt index d79db95..d4044a9 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt @@ -6,7 +6,6 @@ import com.securityc4po.api.BaseDocumentationIntTest import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC -import com.securityc4po.api.finding.* import com.securityc4po.api.project.Project import com.securityc4po.api.project.ProjectEntity import edu.umd.cs.findbugs.annotations.SuppressFBWarnings @@ -109,7 +108,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, - findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + findingIds = emptyList(), commentIds = emptyList() ) @@ -233,287 +232,6 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { ) } - @Nested - inner class GetFindings { - @Test - fun getFindingsByPentestId() { - val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" - webTestClient.get() - .uri("/pentests/{pentestId}/findings", pentestTwoId) - .header("Authorization", "Bearer $tokenAdmin") - .exchange() - .expectStatus().isOk - .expectHeader().doesNotExist("") - .expectBody().json(Json.write(getFindingsResponse())) - .consumeWith( - WebTestClientRestDocumentation.document( - "{methodName}", - Preprocessors.preprocessRequest( - Preprocessors.prettyPrint(), - Preprocessors.modifyUris().removePort(), - Preprocessors.removeHeaders("Host", "Content-Length") - ), - Preprocessors.preprocessResponse( - Preprocessors.prettyPrint() - ), - RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to get the findings for") - ), - PayloadDocumentation.relaxedResponseFields( - PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING) - .description("The id of the requested findings"), - PayloadDocumentation.fieldWithPath("[].severity").type(JsonFieldType.STRING) - .description("The severity of the finding"), - PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING) - .description("The title of the requested finding"), - PayloadDocumentation.fieldWithPath("[].description").type(JsonFieldType.STRING) - .description("The description number of the finding"), - PayloadDocumentation.fieldWithPath("[].impact").type(JsonFieldType.STRING) - .description("The impact of the finding"), - PayloadDocumentation.fieldWithPath("[].affectedUrls").type(JsonFieldType.ARRAY) - .description("List of affected Urls of the finding"), - PayloadDocumentation.fieldWithPath("[].reproduction").type(JsonFieldType.STRING) - .description("The reproduction steps of the finding"), - PayloadDocumentation.fieldWithPath("[].mitigation").type(JsonFieldType.STRING) - .description("The example mitigation for the finding") - ) - ) - ) - } - - private val findingOne = Finding( - id = "ab62d365-1b1d-4da1-89bc-5496616e220f", - severity = Severity.LOW, - title = "Found Bug", - description = "OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "None" - ) - - private fun getFindingsResponse() = listOf( - findingOne.toFindingResponseBody() - ) - } - - @Nested - inner class GetFinding { - @Test - fun getFindingById() { - val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" - webTestClient.get() - .uri("/pentests/{findingId}/finding", findingId) - .header("Authorization", "Bearer $tokenAdmin") - .exchange() - .expectStatus().isOk - .expectHeader().doesNotExist("") - .expectBody().json(Json.write(findingOne.toFindingResponseBody())) - .consumeWith( - WebTestClientRestDocumentation.document( - "{methodName}", - Preprocessors.preprocessRequest( - Preprocessors.prettyPrint(), - Preprocessors.modifyUris().removePort(), - Preprocessors.removeHeaders("Host", "Content-Length") - ), - Preprocessors.preprocessResponse( - Preprocessors.prettyPrint() - ), - RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("findingId").description("The id of the feinidng you want to get") - ), - PayloadDocumentation.relaxedResponseFields( - PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) - .description("The id of the requested finding"), - PayloadDocumentation.fieldWithPath("severity").type(JsonFieldType.STRING) - .description("The severity of the finding"), - PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING) - .description("The title of the requested finding"), - PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING) - .description("The description number of the finding"), - PayloadDocumentation.fieldWithPath("impact").type(JsonFieldType.STRING) - .description("The impact of the finding"), - PayloadDocumentation.fieldWithPath("affectedUrls").type(JsonFieldType.ARRAY) - .description("List of affected Urls of the finding"), - PayloadDocumentation.fieldWithPath("reproduction").type(JsonFieldType.STRING) - .description("The reproduction steps of the finding"), - PayloadDocumentation.fieldWithPath("mitigation").type(JsonFieldType.STRING) - .description("The example mitigation for the finding") - ) - ) - ) - } - - private val findingOne = Finding( - id = "ab62d365-1b1d-4da1-89bc-5496616e220f", - severity = Severity.LOW, - title = "Found Bug", - description = "OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "None" - ) - } - - @Nested - inner class SaveFinding { - @Test - fun saveFindingByPentestId() { - val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" - webTestClient.post() - .uri("/pentests/{pentestId}/finding", pentestTwoId) - .header("Authorization", "Bearer $tokenAdmin") - .body(Mono.just(findingBody), FindingRequestBody::class.java) - .exchange() - .expectStatus().isAccepted - .expectHeader().doesNotExist("") - .expectBody().json(Json.write(findingBody)) - .consumeWith( - WebTestClientRestDocumentation.document( - "{methodName}", - Preprocessors.preprocessRequest( - Preprocessors.prettyPrint(), - Preprocessors.modifyUris().removePort(), - Preprocessors.removeHeaders("Host", "Content-Length") - ), - Preprocessors.preprocessResponse( - Preprocessors.prettyPrint() - ), - RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to save the finding for") - ), - PayloadDocumentation.relaxedResponseFields( - PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) - .description("The id of the saved finding"), - PayloadDocumentation.fieldWithPath("severity").type(JsonFieldType.STRING) - .description("The severity of the finding"), - PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING) - .description("The title of the finding"), - PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING) - .description("The description number of the finding"), - PayloadDocumentation.fieldWithPath("impact").type(JsonFieldType.STRING) - .description("The impact of the finding"), - PayloadDocumentation.fieldWithPath("affectedUrls").type(JsonFieldType.ARRAY) - .description("List of affected Urls of the finding"), - PayloadDocumentation.fieldWithPath("reproduction").type(JsonFieldType.STRING) - .description("The reproduction steps of the finding"), - PayloadDocumentation.fieldWithPath("mitigation").type(JsonFieldType.STRING) - .description("The example mitigation for the finding") - ) - ) - ) - } - - private val findingBody = FindingRequestBody( - severity = "MEDIUM", - title = "Found another Bug", - description = "Another OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack more", - mitigation = "Another None" - ) - } - - @Nested - inner class UpdateFinding { - @Test - fun updateFindingById() { - val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" - webTestClient.patch() - .uri("/pentests/{findingId}/finding", findingId) - .header("Authorization", "Bearer $tokenAdmin") - .body(Mono.just(findingBody), FindingRequestBody::class.java) - .exchange() - .expectStatus().isAccepted - .expectHeader().doesNotExist("") - .expectBody().json(Json.write(findingBody)) - .consumeWith( - WebTestClientRestDocumentation.document( - "{methodName}", - Preprocessors.preprocessRequest( - Preprocessors.prettyPrint(), - Preprocessors.modifyUris().removePort(), - Preprocessors.removeHeaders("Host", "Content-Length") - ), - Preprocessors.preprocessResponse( - Preprocessors.prettyPrint() - ), - RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("findingId").description("The id of the finding you want to update") - ), - PayloadDocumentation.relaxedResponseFields( - PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) - .description("The id of the updated finding"), - PayloadDocumentation.fieldWithPath("severity").type(JsonFieldType.STRING) - .description("The severity of the finding"), - PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING) - .description("The title of the requested finding"), - PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING) - .description("The description number of the finding"), - PayloadDocumentation.fieldWithPath("impact").type(JsonFieldType.STRING) - .description("The impact of the finding"), - PayloadDocumentation.fieldWithPath("affectedUrls").type(JsonFieldType.ARRAY) - .description("List of affected Urls of the finding"), - PayloadDocumentation.fieldWithPath("reproduction").type(JsonFieldType.STRING) - .description("The reproduction steps of the finding"), - PayloadDocumentation.fieldWithPath("mitigation").type(JsonFieldType.STRING) - .description("The example mitigation for the finding") - ) - ) - ) - } - - private val findingBody = FindingRequestBody( - severity = "HIGH", - title = "Updated Bug", - description = "Updated OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "Still None" - ) - } - - @Nested - inner class DeleteFinding { - @Test - fun deleteFindingByPentestAndFindingId() { - val pentestId = "43fbc63c-f624-11ec-b939-0242ac120002" - val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" - webTestClient.delete() - .uri("/pentests/{pentestId}/finding/{findingId}", pentestId, findingId) - .header("Authorization", "Bearer $tokenAdmin") - .exchange() - .expectStatus().isOk - .expectHeader().doesNotExist("") - .expectBody() - .consumeWith( - WebTestClientRestDocumentation.document( - "{methodName}", - Preprocessors.preprocessRequest( - Preprocessors.prettyPrint(), - Preprocessors.modifyUris().removePort(), - Preprocessors.removeHeaders("Host", "Content-Length") - ), - Preprocessors.preprocessResponse( - Preprocessors.prettyPrint() - ), - RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to remove the finidng from"), - RequestDocumentation.parameterWithName("findingId").description("The id of the finding you want to delete") - ), - PayloadDocumentation.relaxedResponseFields( - PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) - .description("The id of the finding you deleted") - ) - ) - ) - } - } - private fun persistBasicTestScenario() { // setup test data // Project @@ -542,7 +260,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, - findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + findingIds = emptyList(), commentIds = emptyList() ) val pentestThree = Pentest( @@ -554,23 +272,11 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { findingIds = emptyList(), commentIds = emptyList() ) - // Finding - val findingOne = Finding( - id = "ab62d365-1b1d-4da1-89bc-5496616e220f", - severity = Severity.LOW, - title = "Found Bug", - description = "OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "None" - ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) mongoTemplate.save(PentestEntity(pentestOne)) mongoTemplate.save(PentestEntity(pentestTwo)) mongoTemplate.save(PentestEntity(pentestThree)) - mongoTemplate.save(FindingEntity(findingOne)) } private fun configureAdminToken() { @@ -580,7 +286,6 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { private fun cleanUp() { mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) - mongoTemplate.findAllAndRemove(Query(), FindingEntity::class.java) tokenAdmin = "n/a" } diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt index 87ac6d3..daa876c 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt @@ -5,7 +5,6 @@ import com.securityc4po.api.BaseIntTest import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC -import com.securityc4po.api.finding.* import com.securityc4po.api.project.Project import com.securityc4po.api.project.ProjectEntity import edu.umd.cs.findbugs.annotations.SuppressFBWarnings @@ -86,7 +85,7 @@ class PentestControllerIntTest : BaseIntTest() { category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, - findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + findingIds = emptyList(), commentIds = emptyList() ) @@ -159,145 +158,6 @@ class PentestControllerIntTest : BaseIntTest() { ) } - @Nested - inner class GetFindings { - @Test - fun `requesting findings by pentestId successfully`() { - val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" - webTestClient.get() - .uri("/pentests/{pentestId}/findings", pentestTwoId) - .header("Authorization", "Bearer $tokenAdmin") - .exchange() - .expectStatus().isOk - .expectHeader().valueEquals("Application-Name", "SecurityC4PO") - .expectBody().json(Json.write(getFindings())) - } - - private val findingOne = Finding( - id = "ab62d365-1b1d-4da1-89bc-5496616e220f", - severity = Severity.LOW, - title = "Found Bug", - description = "OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "None" - ) - - private fun getFindings() = listOf( - findingOne.toFindingResponseBody() - ) - } - - @Nested - inner class GetFinding { - @Test - fun `requesting finding by findingId successfully`() { - val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" - webTestClient.get() - .uri("/pentests/{findingId}/finding", findingId) - .header("Authorization", "Bearer $tokenAdmin") - .exchange() - .expectStatus().isOk - .expectHeader().valueEquals("Application-Name", "SecurityC4PO") - .expectBody().json(Json.write(findingOne.toFindingResponseBody())) - } - - private val findingOne = Finding( - id = "ab62d365-1b1d-4da1-89bc-5496616e220f", - severity = Severity.LOW, - title = "Found Bug", - description = "OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "None" - ) - } - - @Nested - inner class SaveFinding { - @Test - fun `save finding successfully`() { - val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" - webTestClient.post() - .uri("/pentests/{pentestId}/finding", pentestTwoId) - .header("Authorization", "Bearer $tokenAdmin") - .body(Mono.just(findingBody), FindingRequestBody::class.java) - .exchange() - .expectStatus().isAccepted - .expectHeader().valueEquals("Application-Name", "SecurityC4PO") - .expectBody() - .jsonPath("$.severity").isEqualTo("MEDIUM") - .jsonPath("$.title").isEqualTo("Found another Bug") - .jsonPath("$.description").isEqualTo("Another OTG-INFO-002 Bug") - .jsonPath("$.impact").isEqualTo("Service") - .jsonPath("$.affectedUrls").isEmpty - .jsonPath("$.reproduction").isEqualTo("Step 1: Hack more") - .jsonPath("$.mitigation").isEqualTo("Another None") - } - - private val findingBody = FindingRequestBody( - severity = "MEDIUM", - title = "Found another Bug", - description = "Another OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack more", - mitigation = "Another None" - ) - } - - @Nested - inner class UpdateFinding { - @Test - fun `update finding successfully`() { - val findingId = "43fbc63c-f624-11ec-b939-0242ac120002" - webTestClient.post() - .uri("/pentests/{findingId}/finding", findingId) - .header("Authorization", "Bearer $tokenAdmin") - .body(Mono.just(findingBody), FindingRequestBody::class.java) - .exchange() - .expectStatus().isAccepted - .expectHeader().valueEquals("Application-Name", "SecurityC4PO") - .expectBody() - .jsonPath("$.severity").isEqualTo("HIGH") - .jsonPath("$.title").isEqualTo("Updated Bug") - .jsonPath("$.description").isEqualTo("Updated OTG-INFO-002 Bug") - .jsonPath("$.impact").isEqualTo("Service") - .jsonPath("$.affectedUrls").isEmpty - .jsonPath("$.reproduction").isEqualTo("Step 1: Hack") - .jsonPath("$.mitigation").isEqualTo("Still None") - } - - private val findingBody = FindingRequestBody( - severity = "HIGH", - title = "Updated Bug", - description = "Updated OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "Still None" - ) - } - - @Nested - inner class DeleteFinding { - @Test - fun `delete finding successfully`() { - val pentestId = "43fbc63c-f624-11ec-b939-0242ac120002" - val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" - webTestClient.delete() - .uri("/pentests/{pentestId}/finding/{findingId}", pentestId, findingId) - .header("Authorization", "Bearer $tokenAdmin") - .exchange() - .expectStatus().isOk - .expectHeader().valueEquals("Application-Name", "SecurityC4PO") - .expectBody() - .jsonPath("$.id").isEqualTo("ab62d365-1b1d-4da1-89bc-5496616e220f") - } - } - private fun persistBasicTestScenario() { // setup test data // project @@ -325,7 +185,7 @@ class PentestControllerIntTest : BaseIntTest() { category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, - findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + findingIds = emptyList(), commentIds = emptyList() ) val pentestThree = Pentest( @@ -337,23 +197,11 @@ class PentestControllerIntTest : BaseIntTest() { findingIds = emptyList(), commentIds = emptyList() ) - // Finding - val findingOne = Finding( - id = "ab62d365-1b1d-4da1-89bc-5496616e220f", - severity = Severity.LOW, - title = "Found Bug", - description = "OTG-INFO-002 Bug", - impact = "Service", - affectedUrls = emptyList(), - reproduction = "Step 1: Hack", - mitigation = "None" - ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) mongoTemplate.save(PentestEntity(pentestOne)) mongoTemplate.save(PentestEntity(pentestTwo)) mongoTemplate.save(PentestEntity(pentestThree)) - mongoTemplate.save(FindingEntity(findingOne)) } private fun configureAdminToken() { @@ -363,7 +211,6 @@ class PentestControllerIntTest : BaseIntTest() { private fun cleanUp() { mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) - mongoTemplate.findAllAndRemove(Query(), FindingEntity::class.java) tokenAdmin = "n/a" } diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/comment/CommentControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt similarity index 97% rename from security-c4po-api/src/test/kotlin/com/securityc4po/api/comment/CommentControllerDocumentationTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt index 4853cd7..0778128 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/comment/CommentControllerDocumentationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import com.fasterxml.jackson.databind.ObjectMapper import com.github.tomakehurst.wiremock.common.Json @@ -10,6 +10,10 @@ import com.securityc4po.api.pentest.Pentest import com.securityc4po.api.pentest.PentestCategory import com.securityc4po.api.pentest.PentestEntity import com.securityc4po.api.pentest.PentestStatus +import com.securityc4po.api.pentest.comment.Comment +import com.securityc4po.api.pentest.comment.CommentEntity +import com.securityc4po.api.pentest.comment.CommentRequestBody +import com.securityc4po.api.pentest.comment.toCommentResponseBody import com.securityc4po.api.project.Project import com.securityc4po.api.project.ProjectEntity import edu.umd.cs.findbugs.annotations.SuppressFBWarnings diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/comment/CommentControllerIntegrationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntegrationTest.kt similarity index 95% rename from security-c4po-api/src/test/kotlin/com/securityc4po/api/comment/CommentControllerIntegrationTest.kt rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntegrationTest.kt index d148436..36603ec 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/comment/CommentControllerIntegrationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntegrationTest.kt @@ -1,4 +1,4 @@ -package com.securityc4po.api.comment +package com.securityc4po.api.pentest.comment import com.github.tomakehurst.wiremock.common.Json import com.securityc4po.api.BaseIntTest @@ -9,6 +9,10 @@ import com.securityc4po.api.pentest.Pentest import com.securityc4po.api.pentest.PentestCategory import com.securityc4po.api.pentest.PentestEntity import com.securityc4po.api.pentest.PentestStatus +import com.securityc4po.api.pentest.comment.Comment +import com.securityc4po.api.pentest.comment.CommentEntity +import com.securityc4po.api.pentest.comment.CommentRequestBody +import com.securityc4po.api.pentest.comment.toCommentResponseBody import com.securityc4po.api.project.Project import com.securityc4po.api.project.ProjectEntity import edu.umd.cs.findbugs.annotations.SuppressFBWarnings diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerDocumentationTest.kt new file mode 100644 index 0000000..5caaa32 --- /dev/null +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerDocumentationTest.kt @@ -0,0 +1,403 @@ +package com.securityc4po.api.pentest.finding + +import com.fasterxml.jackson.databind.ObjectMapper +import com.github.tomakehurst.wiremock.common.Json +import com.securityc4po.api.BaseDocumentationIntTest +import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR +import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC +import com.securityc4po.api.pentest.Pentest +import com.securityc4po.api.pentest.PentestCategory +import com.securityc4po.api.pentest.PentestEntity +import com.securityc4po.api.pentest.PentestStatus +import com.securityc4po.api.project.Project +import com.securityc4po.api.project.ProjectEntity +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Query +import org.springframework.restdocs.operation.preprocess.Preprocessors +import org.springframework.restdocs.payload.JsonFieldType +import org.springframework.restdocs.payload.PayloadDocumentation +import org.springframework.restdocs.request.RequestDocumentation +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation +import reactor.core.publisher.Mono + +@SuppressFBWarnings( + SIC_INNER_SHOULD_BE_STATIC, + NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, + RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +) +class FindingControllerDocumentationTest: BaseDocumentationIntTest() { + + @Autowired + lateinit var mongoTemplate: MongoTemplate + var mapper = ObjectMapper() + + @BeforeEach + fun init() { + configureAdminToken() + persistBasicTestScenario() + } + + @AfterEach + fun destroy() { + cleanUp() + } + + @Nested + inner class GetFindings { + @Test + fun getFindingsByPentestId() { + val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" + webTestClient.get() + .uri("/pentests/{pentestId}/findings", pentestTwoId) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().doesNotExist("") + .expectBody().json(Json.write(getFindingsResponse())) + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + RequestDocumentation.relaxedPathParameters( + RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to get the findings for") + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING) + .description("The id of the requested findings"), + PayloadDocumentation.fieldWithPath("[].severity").type(JsonFieldType.STRING) + .description("The severity of the finding"), + PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING) + .description("The title of the requested finding"), + PayloadDocumentation.fieldWithPath("[].description").type(JsonFieldType.STRING) + .description("The description number of the finding"), + PayloadDocumentation.fieldWithPath("[].impact").type(JsonFieldType.STRING) + .description("The impact of the finding"), + PayloadDocumentation.fieldWithPath("[].affectedUrls").type(JsonFieldType.ARRAY) + .description("List of affected Urls of the finding"), + PayloadDocumentation.fieldWithPath("[].reproduction").type(JsonFieldType.STRING) + .description("The reproduction steps of the finding"), + PayloadDocumentation.fieldWithPath("[].mitigation").type(JsonFieldType.STRING) + .description("The example mitigation for the finding") + ) + ) + ) + } + + private val findingOne = Finding( + id = "ab62d365-1b1d-4da1-89bc-5496616e220f", + severity = Severity.LOW, + title = "Found Bug", + description = "OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "None" + ) + + private fun getFindingsResponse() = listOf( + findingOne.toFindingResponseBody() + ) + } + + @Nested + inner class GetFinding { + @Test + fun getFindingById() { + val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" + webTestClient.get() + .uri("/pentests/{findingId}/finding", findingId) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().doesNotExist("") + .expectBody().json(Json.write(findingOne.toFindingResponseBody())) + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + RequestDocumentation.relaxedPathParameters( + RequestDocumentation.parameterWithName("findingId").description("The id of the feinidng you want to get") + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) + .description("The id of the requested finding"), + PayloadDocumentation.fieldWithPath("severity").type(JsonFieldType.STRING) + .description("The severity of the finding"), + PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING) + .description("The title of the requested finding"), + PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING) + .description("The description number of the finding"), + PayloadDocumentation.fieldWithPath("impact").type(JsonFieldType.STRING) + .description("The impact of the finding"), + PayloadDocumentation.fieldWithPath("affectedUrls").type(JsonFieldType.ARRAY) + .description("List of affected Urls of the finding"), + PayloadDocumentation.fieldWithPath("reproduction").type(JsonFieldType.STRING) + .description("The reproduction steps of the finding"), + PayloadDocumentation.fieldWithPath("mitigation").type(JsonFieldType.STRING) + .description("The example mitigation for the finding") + ) + ) + ) + } + + private val findingOne = Finding( + id = "ab62d365-1b1d-4da1-89bc-5496616e220f", + severity = Severity.LOW, + title = "Found Bug", + description = "OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "None" + ) + } + + @Nested + inner class SaveFinding { + @Test + fun saveFindingByPentestId() { + val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" + webTestClient.post() + .uri("/pentests/{pentestId}/finding", pentestTwoId) + .header("Authorization", "Bearer $tokenAdmin") + .body(Mono.just(findingBody), FindingRequestBody::class.java) + .exchange() + .expectStatus().isAccepted + .expectHeader().doesNotExist("") + .expectBody().json(Json.write(findingBody)) + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + RequestDocumentation.relaxedPathParameters( + RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to save the finding for") + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) + .description("The id of the saved finding"), + PayloadDocumentation.fieldWithPath("severity").type(JsonFieldType.STRING) + .description("The severity of the finding"), + PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING) + .description("The title of the finding"), + PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING) + .description("The description number of the finding"), + PayloadDocumentation.fieldWithPath("impact").type(JsonFieldType.STRING) + .description("The impact of the finding"), + PayloadDocumentation.fieldWithPath("affectedUrls").type(JsonFieldType.ARRAY) + .description("List of affected Urls of the finding"), + PayloadDocumentation.fieldWithPath("reproduction").type(JsonFieldType.STRING) + .description("The reproduction steps of the finding"), + PayloadDocumentation.fieldWithPath("mitigation").type(JsonFieldType.STRING) + .description("The example mitigation for the finding") + ) + ) + ) + } + + private val findingBody = FindingRequestBody( + severity = "MEDIUM", + title = "Found another Bug", + description = "Another OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack more", + mitigation = "Another None" + ) + } + + @Nested + inner class UpdateFinding { + @Test + fun updateFindingById() { + val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" + webTestClient.patch() + .uri("/pentests/{findingId}/finding", findingId) + .header("Authorization", "Bearer $tokenAdmin") + .body(Mono.just(findingBody), FindingRequestBody::class.java) + .exchange() + .expectStatus().isAccepted + .expectHeader().doesNotExist("") + .expectBody().json(Json.write(findingBody)) + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + RequestDocumentation.relaxedPathParameters( + RequestDocumentation.parameterWithName("findingId").description("The id of the finding you want to update") + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) + .description("The id of the updated finding"), + PayloadDocumentation.fieldWithPath("severity").type(JsonFieldType.STRING) + .description("The severity of the finding"), + PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING) + .description("The title of the requested finding"), + PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING) + .description("The description number of the finding"), + PayloadDocumentation.fieldWithPath("impact").type(JsonFieldType.STRING) + .description("The impact of the finding"), + PayloadDocumentation.fieldWithPath("affectedUrls").type(JsonFieldType.ARRAY) + .description("List of affected Urls of the finding"), + PayloadDocumentation.fieldWithPath("reproduction").type(JsonFieldType.STRING) + .description("The reproduction steps of the finding"), + PayloadDocumentation.fieldWithPath("mitigation").type(JsonFieldType.STRING) + .description("The example mitigation for the finding") + ) + ) + ) + } + + private val findingBody = FindingRequestBody( + severity = "HIGH", + title = "Updated Bug", + description = "Updated OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "Still None" + ) + } + + @Nested + inner class DeleteFinding { + @Test + fun deleteFindingByPentestAndFindingId() { + val pentestId = "43fbc63c-f624-11ec-b939-0242ac120002" + val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" + webTestClient.delete() + .uri("/pentests/{pentestId}/finding/{findingId}", pentestId, findingId) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().doesNotExist("") + .expectBody() + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + RequestDocumentation.relaxedPathParameters( + RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to remove the finidng from"), + RequestDocumentation.parameterWithName("findingId").description("The id of the finding you want to delete") + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) + .description("The id of the finding you deleted") + ) + ) + ) + } + } + + private fun persistBasicTestScenario() { + // setup test data + // Project + val projectOne = Project( + id = "d2e126ba-f608-11ec-b939-0242ac120025", + client = "E Corp", + title = "Some Mock API (v1.0) Scanning", + createdAt = "2021-01-10T18:05:00Z", + tester = "Novatester", + projectPentests = emptyList(), + createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + ) + // Pentests + val pentestOne = Pentest( + id = "9c8af320-f608-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120025", + category = PentestCategory.INFORMATION_GATHERING, + refNumber = "OTG-INFO-001", + status = PentestStatus.NOT_STARTED, + findingIds = emptyList(), + commentIds = emptyList() + ) + val pentestTwo = Pentest( + id = "43fbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120025", + category = PentestCategory.INFORMATION_GATHERING, + refNumber = "OTG-INFO-002", + status = PentestStatus.IN_PROGRESS, + findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + commentIds = emptyList() + ) + val pentestThree = Pentest( + id = "74eae112-f62c-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120025", + category = PentestCategory.AUTHENTICATION_TESTING, + refNumber = "OTG-AUTHN-001", + status = PentestStatus.COMPLETED, + findingIds = emptyList(), + commentIds = emptyList() + ) + // Finding + val findingOne = Finding( + id = "ab62d365-1b1d-4da1-89bc-5496616e220f", + severity = Severity.LOW, + title = "Found Bug", + description = "OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "None" + ) + // persist test data in database + mongoTemplate.save(ProjectEntity(projectOne)) + mongoTemplate.save(PentestEntity(pentestOne)) + mongoTemplate.save(PentestEntity(pentestTwo)) + mongoTemplate.save(PentestEntity(pentestThree)) + mongoTemplate.save(FindingEntity(findingOne)) + } + + private fun configureAdminToken() { + tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + } + + private fun cleanUp() { + mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) + mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) + mongoTemplate.findAllAndRemove(Query(), FindingEntity::class.java) + + tokenAdmin = "n/a" + } +} \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerIntTest.kt new file mode 100644 index 0000000..c4b6907 --- /dev/null +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/finding/FindingControllerIntTest.kt @@ -0,0 +1,270 @@ +package com.securityc4po.api.pentest.finding + +import com.github.tomakehurst.wiremock.common.Json +import com.securityc4po.api.BaseIntTest +import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR +import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC +import com.securityc4po.api.pentest.Pentest +import com.securityc4po.api.pentest.PentestCategory +import com.securityc4po.api.pentest.PentestEntity +import com.securityc4po.api.pentest.PentestStatus +import com.securityc4po.api.project.Project +import com.securityc4po.api.project.ProjectEntity +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.web.server.LocalServerPort +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Query +import org.springframework.test.web.reactive.server.WebTestClient +import reactor.core.publisher.Mono +import java.time.Duration + +@SuppressFBWarnings( + SIC_INNER_SHOULD_BE_STATIC, + NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, + RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +) +class FindingControllerIntTest: BaseIntTest() { + + @LocalServerPort + private var port = 0 + + @Autowired + lateinit var mongoTemplate: MongoTemplate + + @Autowired + private lateinit var webTestClient: WebTestClient + + @BeforeEach + fun setupWebClient() { + webTestClient = WebTestClient.bindToServer() + .baseUrl("http://localhost:$port") + .responseTimeout(Duration.ofMillis(10000)) + .build() + } + + @BeforeEach + fun init() { + configureAdminToken() + persistBasicTestScenario() + } + + @AfterEach + fun destroy() { + cleanUp() + } + + @Nested + inner class GetFindings { + @Test + fun `requesting findings by pentestId successfully`() { + val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" + webTestClient.get() + .uri("/pentests/{pentestId}/findings", pentestTwoId) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody().json(Json.write(getFindings())) + } + + private val findingOne = Finding( + id = "ab62d365-1b1d-4da1-89bc-5496616e220f", + severity = Severity.LOW, + title = "Found Bug", + description = "OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "None" + ) + + private fun getFindings() = listOf( + findingOne.toFindingResponseBody() + ) + } + + @Nested + inner class GetFinding { + @Test + fun `requesting finding by findingId successfully`() { + val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" + webTestClient.get() + .uri("/pentests/{findingId}/finding", findingId) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody().json(Json.write(findingOne.toFindingResponseBody())) + } + + private val findingOne = Finding( + id = "ab62d365-1b1d-4da1-89bc-5496616e220f", + severity = Severity.LOW, + title = "Found Bug", + description = "OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "None" + ) + } + + @Nested + inner class SaveFinding { + @Test + fun `save finding successfully`() { + val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" + webTestClient.post() + .uri("/pentests/{pentestId}/finding", pentestTwoId) + .header("Authorization", "Bearer $tokenAdmin") + .body(Mono.just(findingBody), FindingRequestBody::class.java) + .exchange() + .expectStatus().isAccepted + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody() + .jsonPath("$.severity").isEqualTo("MEDIUM") + .jsonPath("$.title").isEqualTo("Found another Bug") + .jsonPath("$.description").isEqualTo("Another OTG-INFO-002 Bug") + .jsonPath("$.impact").isEqualTo("Service") + .jsonPath("$.affectedUrls").isEmpty + .jsonPath("$.reproduction").isEqualTo("Step 1: Hack more") + .jsonPath("$.mitigation").isEqualTo("Another None") + } + + private val findingBody = FindingRequestBody( + severity = "MEDIUM", + title = "Found another Bug", + description = "Another OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack more", + mitigation = "Another None" + ) + } + + @Nested + inner class UpdateFinding { + @Test + fun `update finding successfully`() { + val findingId = "43fbc63c-f624-11ec-b939-0242ac120002" + webTestClient.post() + .uri("/pentests/{findingId}/finding", findingId) + .header("Authorization", "Bearer $tokenAdmin") + .body(Mono.just(findingBody), FindingRequestBody::class.java) + .exchange() + .expectStatus().isAccepted + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody() + .jsonPath("$.severity").isEqualTo("HIGH") + .jsonPath("$.title").isEqualTo("Updated Bug") + .jsonPath("$.description").isEqualTo("Updated OTG-INFO-002 Bug") + .jsonPath("$.impact").isEqualTo("Service") + .jsonPath("$.affectedUrls").isEmpty + .jsonPath("$.reproduction").isEqualTo("Step 1: Hack") + .jsonPath("$.mitigation").isEqualTo("Still None") + } + + private val findingBody = FindingRequestBody( + severity = "HIGH", + title = "Updated Bug", + description = "Updated OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "Still None" + ) + } + + @Nested + inner class DeleteFinding { + @Test + fun `delete finding successfully`() { + val pentestId = "43fbc63c-f624-11ec-b939-0242ac120002" + val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" + webTestClient.delete() + .uri("/pentests/{pentestId}/finding/{findingId}", pentestId, findingId) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody() + .jsonPath("$.id").isEqualTo("ab62d365-1b1d-4da1-89bc-5496616e220f") + } + } + + private fun persistBasicTestScenario() { + // setup test data + // project + val projectOne = Project( + id = "d2e126ba-f608-11ec-b939-0242ac120025", + client = "E Corp", + title = "Some Mock API (v1.0) Scanning", + createdAt = "2021-01-10T18:05:00Z", + tester = "Novatester", + createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + ) + // pentests + val pentestOne = Pentest( + id = "9c8af320-f608-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120025", + category = PentestCategory.INFORMATION_GATHERING, + refNumber = "OTG-INFO-001", + status = PentestStatus.NOT_STARTED, + findingIds = emptyList(), + commentIds = emptyList() + ) + val pentestTwo = Pentest( + id = "43fbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120025", + category = PentestCategory.INFORMATION_GATHERING, + refNumber = "OTG-INFO-002", + status = PentestStatus.IN_PROGRESS, + findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + commentIds = emptyList() + ) + val pentestThree = Pentest( + id = "16vbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120025", + category = PentestCategory.AUTHENTICATION_TESTING, + refNumber = "OTG-AUTHN-001", + status = PentestStatus.COMPLETED, + findingIds = emptyList(), + commentIds = emptyList() + ) + // Finding + val findingOne = Finding( + id = "ab62d365-1b1d-4da1-89bc-5496616e220f", + severity = Severity.LOW, + title = "Found Bug", + description = "OTG-INFO-002 Bug", + impact = "Service", + affectedUrls = emptyList(), + reproduction = "Step 1: Hack", + mitigation = "None" + ) + // persist test data in database + mongoTemplate.save(ProjectEntity(projectOne)) + mongoTemplate.save(PentestEntity(pentestOne)) + mongoTemplate.save(PentestEntity(pentestTwo)) + mongoTemplate.save(PentestEntity(pentestThree)) + mongoTemplate.save(FindingEntity(findingOne)) + } + + private fun configureAdminToken() { + tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + } + + private fun cleanUp() { + mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) + mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) + mongoTemplate.findAllAndRemove(Query(), FindingEntity::class.java) + + tokenAdmin = "n/a" + } +} \ No newline at end of file diff --git a/security-c4po-api/src/test/resources/collections/comments.json b/security-c4po-api/src/test/resources/collections/comments.json index 1873d18..eb532da 100644 --- a/security-c4po-api/src/test/resources/collections/comments.json +++ b/security-c4po-api/src/test/resources/collections/comments.json @@ -10,7 +10,7 @@ "data": { "_id": "89703b19-16c7-49e5-8e33-0c706313e5fe", "title": "Test Comment", - "description": "Test Comment Description", + "description": "No related findings", "relatedFindings": [] }, "_class": "com.securityc4po.api.comment.CommentEntity" @@ -25,8 +25,8 @@ }, "data": { "_id": "df516de6-ca5e-44a6-ac50-db89bb17aac3", - "title": "New Test", - "description": "New Test", + "title": "New Test Comment", + "description": "Two related findings", "relatedFindings": [ "0bda8950-94fa-4ec6-8fa7-e09f5a8cd3e8", "4ddb84f6-068c-4319-a8ee-1000008bb75a" @@ -44,8 +44,8 @@ }, "data": { "_id": "e55e943b-6a48-4d84-8d72-b48d7d9de5b7", - "title": "Wow another one?", - "description": "Epic!", + "title": "Another Test Comment", + "description": "One related findings", "relatedFindings": [ "5e22d38f-a4f6-4809-84ea-a803b5f1f9fc" ]