diff --git a/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html b/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html
index 656bed1..194518b 100644
--- a/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html
+++ b/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html
@@ -54,5 +54,4 @@
-
diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.ts b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.ts
index 194b9a4..3674bc8 100644
--- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.ts
+++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.ts
@@ -9,7 +9,7 @@ import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
import {
Comment,
CommentDialogBody,
- CommentEntry,
+ CommentEntry, RelatedFindingOption,
transformCommentsToObjectiveEntries,
transformCommentToRequestBody
} from '@shared/models/comment.model';
@@ -17,11 +17,13 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {Store} from '@ngxs/store';
import {PentestStatus} from '@shared/models/pentest-status.model';
-import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
import {CommentService} from '@shared/services/comment.service';
-import {UpdatePentestComments, UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
+import {UpdatePentestComments} from '@shared/stores/project-state/project-state.actions';
+import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
+import {Finding} from '@shared/models/finding.model';
+import {FindingService} from '@shared/services/finding.service';
@UntilDestroy()
@Component({
@@ -36,6 +38,7 @@ export class PentestCommentsComponent implements OnInit {
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
pentestInfo$: BehaviorSubject = new BehaviorSubject(null);
+ objectiveFindings: RelatedFindingOption[] = [];
// comments$: BehaviorSubject = new BehaviorSubject(null);
loading$: BehaviorSubject = new BehaviorSubject(true);
@@ -43,7 +46,6 @@ export class PentestCommentsComponent implements OnInit {
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
];
dataSource: NbTreeGridDataSource;
-
data: CommentEntry[] = [];
getters: NbGetters = {
@@ -53,6 +55,7 @@ export class PentestCommentsComponent implements OnInit {
};
constructor(private readonly commentService: CommentService,
+ private readonly findingService: FindingService,
private dataSourceBuilder: NbTreeGridDataSourceBuilder,
private notificationService: NotificationService,
private dialogService: DialogService,
@@ -68,6 +71,7 @@ export class PentestCommentsComponent implements OnInit {
next: (selectedPentest: Pentest) => {
this.pentestInfo$.next(selectedPentest);
this.loadCommentsData();
+ this.requestFindingsData(selectedPentest.id);
},
error: err => {
console.error(err);
@@ -103,8 +107,9 @@ export class PentestCommentsComponent implements OnInit {
onClickAddComment(): void {
this.commentDialogService.openCommentDialog(
- FindingDialogComponent,
+ CommentDialogComponent,
this.pentestInfo$.getValue().findingIds,
+ this.objectiveFindings,
null,
{
closeOnEsc: false,
@@ -114,7 +119,6 @@ export class PentestCommentsComponent implements OnInit {
}
).pipe(
filter(value => !!value),
- tap((value) => console.warn('CommentDialogBody: ', value)),
mergeMap((value: CommentDialogBody) =>
this.commentService.saveComment(
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
@@ -126,19 +130,74 @@ export class PentestCommentsComponent implements OnInit {
next: (newComment: Comment) => {
this.store.dispatch(new UpdatePentestComments(newComment.id));
this.loadCommentsData();
- // Todo: Fix trans keys
this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS);
},
error: err => {
console.error(err);
- // Todo: Fix trans keys
this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE);
}
});
}
onClickEditComment(commentEntry): void {
- console.info('Coming soon..', commentEntry);
+ this.commentService.getCommentById(commentEntry.data.commentId).pipe(
+ filter(isNotNullOrUndefined),
+ untilDestroyed(this)
+ ).subscribe({
+ next: (existingComment: Comment) => {
+ if (existingComment) {
+ this.commentDialogService.openCommentDialog(
+ CommentDialogComponent,
+ this.pentestInfo$.getValue().findingIds,
+ this.objectiveFindings,
+ existingComment,
+ {
+ closeOnEsc: false,
+ hasScroll: false,
+ autoFocus: false,
+ closeOnBackdropClick: false
+ }
+ ).pipe(
+ filter(value => !!value),
+ mergeMap((value: CommentDialogBody) =>
+ this.commentService.updateComment(
+ commentEntry.data.commentId,
+ transformCommentToRequestBody(value)
+ )
+ ),
+ untilDestroyed(this)
+ ).subscribe({
+ next: (updatedComment: Comment) => {
+ this.loadCommentsData();
+ this.notificationService.showPopup('comment.popup.update.success', PopupType.SUCCESS);
+ },
+ error: err => {
+ console.error(err);
+ this.notificationService.showPopup('comment.popup.update.failed', PopupType.FAILURE);
+ }
+ });
+ } else {
+ this.notificationService.showPopup('comment.popup.not.available', PopupType.INFO);
+ }
+ },
+ error: err => {
+ console.error(err);
+ }
+ });
+ }
+
+ requestFindingsData(pentestId: string): void {
+ this.objectiveFindings = [];
+ this.findingService.getFindingsByPentestId(pentestId).pipe(
+ untilDestroyed(this)
+ ).subscribe({
+ next: (findings: Finding[]) => {
+ findings.forEach(finding => this.objectiveFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
+ },
+ error: err => {
+ console.error(err);
+ }
+ });
}
onClickDeleteComment(commentEntry): void {
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 08f47fe..a2117f1 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
@@ -163,7 +163,6 @@ export class PentestFindingsComponent implements OnInit {
untilDestroyed(this)
).subscribe({
next: (updatedFinding: Finding) => {
- this.store.dispatch(new UpdatePentestFindings(updatedFinding.id));
this.loadFindingsData();
this.notificationService.showPopup('finding.popup.update.success', PopupType.SUCCESS);
},
@@ -173,7 +172,7 @@ export class PentestFindingsComponent implements OnInit {
}
});
} else {
- this.notificationService.showPopup('finding.popup.not.available', PopupType.FAILURE);
+ this.notificationService.showPopup('finding.popup.not.available', PopupType.INFO);
}
},
error: err => {
diff --git a/security-c4po-angular/src/assets/i18n/de-DE.json b/security-c4po-angular/src/assets/i18n/de-DE.json
index 1044e31..4ac1d99 100644
--- a/security-c4po-angular/src/assets/i18n/de-DE.json
+++ b/security-c4po-angular/src/assets/i18n/de-DE.json
@@ -181,7 +181,8 @@
"update.success": "Kommentar erfolgreich aktualisiert",
"update.failed": "Kommentar konnte nicht aktualisiert werden",
"delete.success": "Kommentar erfolgreich gelöscht",
- "delete.failed": "Kommentar konnte nicht gelöscht werden"
+ "delete.failed": "Kommentar konnte nicht gelöscht werden",
+ "not.available": "Kommentar ist nicht mehr verfügbar"
}
},
"pentest": {
diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json
index f08f30f..be3f87d 100644
--- a/security-c4po-angular/src/assets/i18n/en-US.json
+++ b/security-c4po-angular/src/assets/i18n/en-US.json
@@ -181,7 +181,8 @@
"update.success": "Comment updated successfully",
"update.failed": "Comment could not be updated",
"delete.success": "Comment deleted successfully",
- "delete.failed": "Comment could not be deleted"
+ "delete.failed": "Comment could not be deleted",
+ "not.available": "Comment is not available anymore"
}
},
"pentest": {
diff --git a/security-c4po-angular/src/shared/models/comment.model.ts b/security-c4po-angular/src/shared/models/comment.model.ts
index 3d10c74..1d49c06 100644
--- a/security-c4po-angular/src/shared/models/comment.model.ts
+++ b/security-c4po-angular/src/shared/models/comment.model.ts
@@ -49,7 +49,7 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme
title: comment.title,
description: comment.description,
// Transforms related findings from RelatedFindingOption to list of finding ids
- relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.value.id) : [],
+ relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.id) : [],
/* Remove Table Entry Object Properties */
childEntries: undefined,
kind: undefined,
diff --git a/security-c4po-angular/src/shared/models/generic-dialog-data.ts b/security-c4po-angular/src/shared/models/generic-dialog-data.ts
index 6cede10..72af17f 100644
--- a/security-c4po-angular/src/shared/models/generic-dialog-data.ts
+++ b/security-c4po-angular/src/shared/models/generic-dialog-data.ts
@@ -23,4 +23,5 @@ export interface GenericFormFieldOption {
headerLabelKey: string;
buttonKey: string;
accentColor: string;
+ additionalData?: any;
}
diff --git a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.html b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.html
index fcae7b1..f010830 100644
--- a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.html
+++ b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.html
@@ -57,13 +57,16 @@
{{formArray[2].labelKey | translate}}
-
- {{'global.action.reset' | translate}}
-
- {{finding.title}}
-
+ multiple fullWidth shape="semi-round" filled status="info"
+ [size]="'large'" class="form-field relatedFindings">
+ {{'global.action.reset' | translate}}
+
+ {{finding.title}}
+
diff --git a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.spec.ts
index 178b4d1..213c0a1 100644
--- a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.spec.ts
+++ b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.spec.ts
@@ -182,7 +182,8 @@ export const mockedCommentDialogData = {
{
headerLabelKey: 'comment.create.header',
buttonKey: 'global.action.save',
- accentColor: 'info'
+ accentColor: 'info',
+ additionalData: []
},
]
};
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 c0c4cbd..cf5fca1 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
@@ -1,18 +1,11 @@
-import {ChangeDetectionStrategy, Component, Inject, OnChanges, OnInit} from '@angular/core';
+import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
import * as FA from '@fortawesome/free-solid-svg-icons';
import deepEqual from 'deep-equal';
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
-import {PentestService} from '@shared/services/pentest.service';
-import {ProjectState} from '@shared/stores/project-state/project-state';
-import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
-import {Pentest} from '@shared/models/pentest.model';
-import {Store} from '@ngxs/store';
-import {Finding} from '@shared/models/finding.model';
+import {UntilDestroy} from '@ngneat/until-destroy';
import {RelatedFindingOption} from '@shared/models/comment.model';
-import {BehaviorSubject} from 'rxjs';
-import {FindingService} from '@shared/services/finding.service';
@Component({
selector: 'app-comment-dialog',
@@ -39,28 +32,14 @@ export class CommentDialogComponent implements OnInit {
constructor(
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
private fb: FormBuilder,
- protected dialogRef: NbDialogRef,
- private readonly findingService: FindingService,
- private store: Store
+ protected dialogRef: NbDialogRef
) {
}
ngOnInit(): void {
- this.commentFormGroup = this.generateFormCreationFieldArray();
this.dialogData = this.data;
- // Resets related Findings input fields when comment was found in dialog context
- // tslint:disable-next-line:no-string-literal
- // this.commentFormGroup.controls['commentRelatedFindings'].reset('');
- }
-
- changeSelected($event): void {
- // Latest Value
- console.info($event[$event.length - 1].value);
- // use this element at the end
- // tslint:disable-next-line:no-string-literal
- console.warn(this.commentFormGroup.controls['commentRelatedFindings'].value);
- // tslint:disable-next-line:no-string-literal
- this.selectedFindings = this.commentFormGroup.controls['commentRelatedFindings'].value;
+ this.relatedFindings = this.dialogData.options[0].additionalData;
+ this.commentFormGroup = this.generateFormCreationFieldArray();
}
generateFormCreationFieldArray(): FormGroup {
@@ -70,52 +49,27 @@ export class CommentDialogComponent implements OnInit {
[currentValue?.fieldName]: currentValue?.controlsConfig
}), {});
// tslint:disable-next-line:no-string-literal
- const relatedFindings = this.data.form['commentRelatedFindings'].controlsConfig[0].value;
- if (relatedFindings && relatedFindings.length > 0) {
- // ToDo: Select included findings here (selectedFindings / initialSelectedFindings)
- console.warn('IF (EDIT)', relatedFindings);
- this.renderRelatedFindings(relatedFindings);
- } else {
- this.renderRelatedFindings([]);
+ const preSelectedRelatedFindings = this.data.form['commentRelatedFindings'].controlsConfig[0].value;
+ if (preSelectedRelatedFindings && preSelectedRelatedFindings.length > 0) {
+ this.relatedFindings.forEach(finding => {
+ if (preSelectedRelatedFindings.includes(finding)) {
+ this.initialSelectedFindings.push(finding);
+ this.selectedFindings.push(finding);
+ }
+ });
}
return this.fb.group(config);
}
- renderRelatedFindings(relatedFindings: any): void {
- this.store.select(ProjectState.pentest).pipe(
- untilDestroyed(this)
- ).subscribe({
- next: (selectedPentest: Pentest) => {
- // console.warn(selectedPentest.findingIds);
- this.requestRelatedFindingsData(selectedPentest.id, relatedFindings);
- },
- error: err => {
- console.error(err);
- }
- });
- }
-
- requestRelatedFindingsData(pentestId: string, relatedFindings: any): void {
- this.findingService.getFindingsByPentestId(pentestId).pipe(
- untilDestroyed(this)
- ).subscribe({
- next: (findings: Finding[]) => {
- findings.forEach(finding => this.relatedFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
- // ToDo: Only add the findings that were included in
- console.info('initialSelectedFindings OnINIT ', relatedFindings);
- // findings.forEach(finding => this.initialSelectedFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
- },
- error: err => {
- console.error(err);
- }
- });
+ changeSelected($event): void {
+ // tslint:disable-next-line:no-string-literal
+ this.selectedFindings = this.commentFormGroup.controls['commentRelatedFindings'].value;
}
onClickSave(value: any): void {
this.dialogRef.close({
title: value.commentTitle,
description: value.commentDescription,
- // ToDo: Refactor this to only include the ids this.commentFormGroup.controls['commentRelatedFindings'].value
relatedFindings: this.selectedFindings ? this.selectedFindings : []
});
}
@@ -141,11 +95,6 @@ export class CommentDialogComponent implements OnInit {
newCommentData[key] = '';
}
});
- /* ToDo: Use for EDIT implementation
- console.info('initialSelectedFindings: ', this.initialSelectedFindings);
- console.info('selectedFindings: ', this.selectedFindings);
- console.info('deepEqual related Findings: ', deepEqual(this.initialSelectedFindings, this.selectedFindings));
- */
const didChange = !deepEqual(oldCommentData, newCommentData) || !deepEqual(this.initialSelectedFindings, this.selectedFindings);
return didChange;
}
@@ -155,17 +104,16 @@ export class CommentDialogComponent implements OnInit {
* @return parsed findingData
*/
private parseInitializedCommentDialogData(dialogData: GenericDialogData): any {
- const findingData = {};
+ const commentData = {};
Object.entries(dialogData.form).forEach(entry => {
const [key, value] = entry;
- // console.info(key);
- findingData[key] = value.controlsConfig[0] ?
+ commentData[key] = value.controlsConfig[0] ?
(value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : '';
// Related Findings form field can be ignored since changes here will be recognised inside commentRelatedFindings of tag-list
if (key === 'commentRelatedFindings') {
- findingData[key] = '';
+ commentData[key] = '';
}
});
- return findingData;
+ return commentData;
}
}
diff --git a/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.mock.ts b/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.mock.ts
index 853f3ec..aa90573 100644
--- a/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.mock.ts
+++ b/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.mock.ts
@@ -2,7 +2,7 @@ import {CommentDialogService} from '@shared/modules/comment-dialog/service/comme
import {ComponentType} from '@angular/cdk/overlay';
import {NbDialogConfig} from '@nebular/theme';
import {Observable, of} from 'rxjs';
-import {Comment} from '@shared/models/comment.model';
+import {Comment, RelatedFindingOption} from '@shared/models/comment.model';
export class CommentDialogServiceMock implements Required {
@@ -11,6 +11,7 @@ export class CommentDialogServiceMock implements Required
openCommentDialog(
componentOrTemplateRef: ComponentType,
findingIds: [],
+ relatedFindings: RelatedFindingOption[],
comment: Comment | undefined,
config: Partial | string>> | undefined): Observable {
return of(undefined);
diff --git a/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.ts b/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.ts
index b49e009..3e40ba8 100644
--- a/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.ts
+++ b/security-c4po-angular/src/shared/modules/comment-dialog/service/comment-dialog.service.ts
@@ -1,11 +1,11 @@
-import { Injectable } from '@angular/core';
+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 {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
-import {Comment} from '@shared/models/comment.model';
+import {Comment, RelatedFindingOption} from '@shared/models/comment.model';
@Injectable()
export class CommentDialogService {
@@ -30,10 +30,20 @@ export class CommentDialogService {
public openCommentDialog(componentOrTemplateRef: ComponentType,
findingIds: string[],
+ relatedFindings: RelatedFindingOption[],
comment?: Comment,
config?: Partial | string>>): Observable {
let dialogOptions: Partial | string>>;
let dialogData: GenericDialogData;
+ // Preselect related findings
+ const selectedRelatedFindings: RelatedFindingOption[] = [];
+ if (comment && comment.relatedFindings.length > 0 && relatedFindings) {
+ relatedFindings.forEach(finding => {
+ if (comment.relatedFindings.includes(finding.id)) {
+ selectedRelatedFindings.push(finding);
+ }
+ });
+ }
// Setup CommentDialogBody
dialogData = {
form: {
@@ -69,7 +79,10 @@ export class CommentDialogService {
labelKey: 'comment.relatedFindings.label',
placeholder: findingIds.length === 0 ? 'comment.noFindingsInObjectivePlaceholder' : 'comment.relatedFindingsPlaceholder',
controlsConfig: [
- {value: comment ? comment.relatedFindings : [], disabled: findingIds.length === 0},
+ {
+ value: comment ? selectedRelatedFindings : [],
+ disabled: findingIds.length === 0
+ },
[]
],
errors: [
@@ -84,7 +97,8 @@ export class CommentDialogService {
{
headerLabelKey: 'comment.edit.header',
buttonKey: 'global.action.update',
- accentColor: 'warning'
+ accentColor: 'warning',
+ additionalData: relatedFindings
},
];
} else {
@@ -92,7 +106,8 @@ export class CommentDialogService {
{
headerLabelKey: 'comment.create.header',
buttonKey: 'global.action.save',
- accentColor: 'info'
+ accentColor: 'info',
+ additionalData: relatedFindings
},
];
}
diff --git a/security-c4po-angular/src/shared/services/comment.service.ts b/security-c4po-angular/src/shared/services/comment.service.ts
index dbd888e..4b7bc29 100644
--- a/security-c4po-angular/src/shared/services/comment.service.ts
+++ b/security-c4po-angular/src/shared/services/comment.service.ts
@@ -4,6 +4,7 @@ import {HttpClient} from '@angular/common/http';
import {Store} from '@ngxs/store';
import {Observable} from 'rxjs';
import {Comment} from '@shared/models/comment.model';
+import {Finding} from '@shared/models/finding.model';
@Injectable({
providedIn: 'root'
@@ -26,6 +27,14 @@ export class CommentService {
return this.http.get(`${this.apiBaseURL}/${pentestId}/comments`);
}
+ /**
+ * Get Comment by Id
+ * @param commentId the id of the comment
+ */
+ public getCommentById(commentId: string): Observable {
+ return this.http.get(`${this.apiBaseURL}/${commentId}/comment`);
+ }
+
/**
* Save Comment
* @param pentestId the id of the pentest
@@ -35,6 +44,15 @@ export class CommentService {
return this.http.post(`${this.apiBaseURL}/${pentestId}/comment`, comment);
}
+ /**
+ * Update Comment
+ * @param commentId the id of the comment
+ * @param comment the information of the comment
+ */
+ public updateComment(commentId: string, comment: Comment): Observable {
+ return this.http.patch(`${this.apiBaseURL}/${commentId}/comment`, comment);
+ }
+
/**
* Delete Comment
* @param pentestId the id of the pentest
diff --git a/security-c4po-api/security-c4po-api.postman_collection.json b/security-c4po-api/security-c4po-api.postman_collection.json
index 92bb33c..6f7b2ac 100644
--- a/security-c4po-api/security-c4po-api.postman_collection.json
+++ b/security-c4po-api/security-c4po-api.postman_collection.json
@@ -481,7 +481,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\",\n \"affectedUrls\": []\n}",
+ "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\",\n \"relatedFindings\": []\n}",
"options": {
"raw": {
"language": "json"
@@ -512,7 +512,7 @@
"bearer": [
{
"key": "token",
- "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzE3MTM3MzQsImlhdCI6MTY3MTcxMzQzNCwiYXV0aF90aW1lIjoxNjcxNzEyNjkwLCJqdGkiOiJjNWYxYWZiZi1mZTczLTQ0NTAtYjA4YS1lMGEwMDcyNjMyOTgiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODgvYXV0aC9yZWFsbXMvYzRwb19yZWFsbV9sb2NhbCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIxMGUwNmQ3YS04ZGQwLTRlY2QtODk2My0wNTZiNDUwNzljNGYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjNHBvX2xvY2FsIiwibm9uY2UiOiIyZTYyNjNhNC1lM2U2LTRlMzUtYjQ5Yy1lMjYyNzM1ZTk2MGQiLCJzZXNzaW9uX3N0YXRlIjoiMGNmYmY4MGEtNzAxMS00NmQzLTllNGQtNTUxYWU4NTA5NjZmIiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjNHBvX3VzZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYzRwb19sb2NhbCI6eyJyb2xlcyI6WyJ1c2VyIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.se43hq_vPjzAG6MpIxBiHb9vJHZmbLEko0tiN5m2hbhzd8s3YiBWpeiI6kgZ5kzl23iBQyMnXN4Sqpbt2ERKbKyUusezWcXhGTP22usi3b1vzFOAY9mqCI32i15sxCM2UDRYDFYcAblaKPxKsQf6EWduXpcn4L9_kQE4EpoLyWWWqFThGvFPSvkPGodffcEOz8BrnYDVUnwkodFsOWAnQmQHaR7jq1Y0hhZzWi3IlrRWlnRi0TKVWCZgUwO0PJttNq5wYZPsxgiS-khUCC1qtbKrRgBK_3sefxPkWDOQEubu0Kjyjq4rVZnq66anO3Qw82CSLn0nSCu-AL5Xd4Xchw",
+ "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzIyMzc2MjQsImlhdCI6MTY3MjIzNzMyNCwianRpIjoiMTI2MTBmOWUtMjY1OC00ZmE3LTlkNTItZGU4NzNiZDVjZGE3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI4NzQ5MTFkNS00ZTZlLTRjNmMtYmQxYy1hMzlmZjE2OTI0YWIiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.FOZNfhuWutaKUsVnr3A2jxJ6PM7fpX5BdB63SI_3CI7LFgnVmruprQC3ibhYGD77yX59yRWlGlIDlXvybd6v4tujKEL1Kuf4J-MSjj3dJcIx29PtqMe91I49MkIsjr3M24YW4bgOtdbUTYvT1l0IUisW1V_-t23qW_tsbXxviNr_9HSiJYZJZ7a47tmEptJaDZtAwjBaQc8s4BVIqiPbIcYE1Mj1Giu56C3k_v_boSxcl3rrRMXgIWTSO4TtV_jm2UfERzp82B6dZYdq0ZxeyaD0nCzXSkZ41pOeqFK4Qsm6ttCKe5OJ-RGpqvi0KH4YNuhUKHpzisVOL0cPDiWmLg",
"type": "string"
},
{
@@ -574,6 +574,85 @@
}
},
"response": []
+ },
+ {
+ "name": "getCommentById",
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzIyMzc2MjQsImlhdCI6MTY3MjIzNzMyNCwianRpIjoiMTI2MTBmOWUtMjY1OC00ZmE3LTlkNTItZGU4NzNiZDVjZGE3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI4NzQ5MTFkNS00ZTZlLTRjNmMtYmQxYy1hMzlmZjE2OTI0YWIiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.FOZNfhuWutaKUsVnr3A2jxJ6PM7fpX5BdB63SI_3CI7LFgnVmruprQC3ibhYGD77yX59yRWlGlIDlXvybd6v4tujKEL1Kuf4J-MSjj3dJcIx29PtqMe91I49MkIsjr3M24YW4bgOtdbUTYvT1l0IUisW1V_-t23qW_tsbXxviNr_9HSiJYZJZ7a47tmEptJaDZtAwjBaQc8s4BVIqiPbIcYE1Mj1Giu56C3k_v_boSxcl3rrRMXgIWTSO4TtV_jm2UfERzp82B6dZYdq0ZxeyaD0nCzXSkZ41pOeqFK4Qsm6ttCKe5OJ-RGpqvi0KH4YNuhUKHpzisVOL0cPDiWmLg",
+ "type": "string"
+ },
+ {
+ "key": "undefined",
+ "type": "any"
+ }
+ ]
+ },
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:8443/pentests/df516de6-ca5e-44a6-ac50-db89bb17aac3/comment",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8443",
+ "path": [
+ "pentests",
+ "df516de6-ca5e-44a6-ac50-db89bb17aac3",
+ "comment"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "updateComment",
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzA0MTQ3ODYsImlhdCI6MTY3MDQxNDQ4NiwianRpIjoiM2FmOWU5M2MtY2YzNi00MjQwLTkzNWEtNDkxYTJkZTY2MWU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI5M2ExNTBlMC03ZWRkLTQxZTgtYWE4Yi0yZWY5YTgzOWU4NDciLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.QjUkCInyCJ5Wsz4q56gfsLqERr6pYlGjwNw-VsKNJ_3Jp-8Dazq9UmDGN8AmAkQ0sp0b-FMm3jArKMBpr84gKd65trvQx_qHvXev5x2MWBG4_9v3C9MmjxWcAYRVmfRdURUOhfto-4YfRwMwNRsKJfwMIjfS5VT8bHJWipcCDzaidN8h_LLORbmmQZ2o0l4Jnv5qrrWzUcSTeEeBpHGOjes1-T0gOlDJa34Z9x_xrsTsybKAylrmX03mDSI-f2h5XqqtgnrxtddtHXHatfxB1BHWq-FILDsGf0UG47FEQjqapFvn9bFiNyq0GVrgdK42miEO7ywOtCOKpCfAUnMwdQ",
+ "type": "string"
+ },
+ {
+ "key": "undefined",
+ "type": "any"
+ }
+ ]
+ },
+ "method": "PATCH",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Edited Test Comment Description\",\n \"relatedFindings\": []\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8443/pentests/df516de6-ca5e-44a6-ac50-db89bb17aac3/comment",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8443",
+ "path": [
+ "pentests",
+ "df516de6-ca5e-44a6-ac50-db89bb17aac3",
+ "comment"
+ ]
+ }
+ },
+ "response": []
}
]
},
diff --git a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc
index a959d4a..8d0a385 100644
--- a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc
+++ b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc
@@ -317,20 +317,63 @@ include::{snippets}/deleteCommentByPentestAndCommentId/http-response.adoc[]
include::{snippets}/deleteCommentByPentestAndCommentId/response-fields.adoc[]
+=== Get comment for id
+
+To get a comment by id, call the GET request /pentests/+{commentId}+/comment.
+
+==== Request example
+
+include::{snippets}/getCommentById/http-request.adoc[]
+
+==== Request structure
+
+include::{snippets}/getCommentById/path-parameters.adoc[]
+
+==== Response example
+
+include::{snippets}/getCommentById/http-response.adoc[]
+
+==== Response structure
+
+include::{snippets}/getCommentById/response-fields.adoc[]
+
+=== Update comment
+
+To update a comment, call the PATCH request /pentests/+{commentId}+/comment
+
+==== Request example
+
+include::{snippets}/updateCommentById/http-request.adoc[]
+
+==== Request structure
+
+include::{snippets}/updateCommentById/path-parameters.adoc[]
+
+==== Response example
+
+include::{snippets}/updateCommentById/http-response.adoc[]
+
+==== Response structure
+
+include::{snippets}/updateCommentById/response-fields.adoc[]
+
+
== Change History
|===
|Date |Change
+|2022-12-28
+|Added GET, PATCH endpoint for Comment
|2022-12-23
|Added DELETE endpoint for Comment
|2022-12-22
-|Added GET, POST endpoint for Comment
+|Added GET, POST endpoint for Comment(s)
|2022-12-09
|Added DELETE endpoint for Finding
|2022-12-08
|Added GET and PATCH endpoint for Finding
|2022-12-02
-|Added GET and POST endpoint for Findings
+|Added GET and POST endpoint for Finding(s)
|2022-11-21
|Added GET, POST and PATCH endpoint for Pentests
|2022-03-07
diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt
index 628f7f3..ff12531 100644
--- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt
+++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentController.kt
@@ -4,11 +4,7 @@ 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.configuration.error.handler.EntityNotFoundException
-import com.securityc4po.api.configuration.error.handler.Errorcode
-import com.securityc4po.api.pentest.finding.toFindingResponseBody
import com.securityc4po.api.pentest.PentestService
-import com.securityc4po.api.pentest.finding.toFindingDeleteResponseBody
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.noContent
import org.springframework.web.bind.annotation.*
@@ -40,6 +36,13 @@ class CommentController(private val pentestService: PentestService, private val
}
}
+ @GetMapping("/{commentId}/comment")
+ fun getComment(@PathVariable(value = "commentId") commentId: String):Mono> {
+ return this.commentService.getCommentById(commentId).map {
+ ResponseEntity.ok().body(it.toCommentResponseBody())
+ }
+ }
+
@PostMapping("/{pentestId}/comment")
fun saveComment(
@PathVariable(value = "pentestId") pentestId: String,
@@ -50,8 +53,18 @@ class CommentController(private val pentestService: PentestService, private val
}
}
+ @PatchMapping("/{commentId}/comment")
+ fun updateComment(
+ @PathVariable(value = "commentId") commentId: String,
+ @RequestBody body: CommentRequestBody
+ ): Mono> {
+ return this.commentService.updateComment(commentId, body).map {
+ ResponseEntity.accepted().body(it.toCommentResponseBody())
+ }
+ }
+
@DeleteMapping("/{pentestId}/comment/{commentId}")
- fun deleteFinding(
+ fun deleteComment(
@PathVariable(value = "pentestId") pentestId: String,
@PathVariable(value = "commentId") commentId: String
): Mono> {
diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt
index 513e945..f267628 100644
--- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt
+++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentService.kt
@@ -8,10 +8,12 @@ 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.pentest.PentestService
+import com.securityc4po.api.pentest.finding.*
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.switchIfEmpty
+import java.time.Instant
@Service
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
@@ -19,6 +21,36 @@ class CommentService(private val commentRepository: CommentRepository, private v
var logger = getLoggerFor()
+ /**
+ * Get all [Comments]s by commentId's
+ *
+ * @return list of [Comment]s
+ */
+ fun getCommentsByIds(commentIds: List): Mono> {
+ return commentRepository.findCommentsByIds(commentIds).collectList().map {
+ it.map { commentEntity -> commentEntity.toComment() }
+ }.switchIfEmpty {
+ val msg = "Comment not found."
+ val ex = EntityNotFoundException(msg, Errorcode.CommentsNotFound)
+ logger.warn(msg, ex)
+ throw ex
+ }
+ }
+
+ /**
+ * Get [Comment] by commentId
+ *
+ * @return of [Comment]
+ */
+ fun getCommentById(commentId: String): Mono {
+ return this.commentRepository.findCommentById(commentId).switchIfEmpty {
+ logger.warn("Comment with id $commentId not found.")
+ val msg = "Comment with id $commentId not found."
+ val ex = EntityNotFoundException(msg, Errorcode.CommentNotFound)
+ throw ex
+ }.map { it.toComment() }
+ }
+
/**
* Save [Comment]
*
@@ -38,7 +70,7 @@ class CommentService(private val commentRepository: CommentRepository, private v
val commentEntity = CommentEntity(comment)
return commentRepository.insert(commentEntity).flatMap { newCommentEntity: CommentEntity ->
val comment = newCommentEntity.toComment()
- // After successfully saving finding add id to pentest
+ // After successfully saving comment add id to pentest
pentestService.updatePentestComment(pentestId, comment.id).onErrorMap {
TransactionInterruptedException(
"Pentest could not be updated in Database.",
@@ -59,18 +91,39 @@ class CommentService(private val commentRepository: CommentRepository, private v
}
/**
- * Get all [Comments]s by commentId's
+ * Update [Comment]
*
- * @return list of [Comment]s
+ * @throws [InvalidModelException] if the [Comment] is invalid
+ * @throws [TransactionInterruptedException] if the [Comment] could not be stored
+ * @return saved [Comment]
*/
- fun getCommentsByIds(commentIds: List): Mono> {
- return commentRepository.findCommentsByIds(commentIds).collectList().map {
- it.map { commentEntity -> commentEntity.toComment() }
- }.switchIfEmpty {
- val msg = "Comment not found."
- val ex = EntityNotFoundException(msg, Errorcode.CommentsNotFound)
- logger.warn(msg, ex)
+ fun updateComment(commentId: String, body: CommentRequestBody): Mono {
+ validate(
+ require = body.isValid(),
+ logging = { logger.warn("Comment not valid.") },
+ mappedException = InvalidModelException(
+ "Comment not valid.", Errorcode.CommentInvalid
+ )
+ )
+ return this.commentRepository.findCommentById(commentId).switchIfEmpty {
+ logger.warn("Comment with id $commentId not found.")
+ val msg = "Comment with id $commentId not found."
+ val ex = EntityNotFoundException(msg, Errorcode.CommentNotFound)
throw ex
+ }.flatMap { currentCommentEntity: CommentEntity ->
+ currentCommentEntity.lastModified = Instant.now()
+ currentCommentEntity.data = buildComment(body, currentCommentEntity)
+ commentRepository.save(currentCommentEntity).map {
+ it.toComment()
+ }
+ }.doOnError {
+ throw wrappedException(
+ logging = { logger.warn("Comment could not be updated in Database. Thrown exception: ", it) },
+ mappedException = TransactionInterruptedException(
+ "Comment could not be stored.",
+ Errorcode.CommentInsertionFailed
+ )
+ )
}
}
diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt
index ee68d0b..957abf7 100644
--- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt
+++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerDocumentationTest.kt
@@ -101,6 +101,54 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
)
}
+ @Nested
+ inner class GetComment {
+ @Test
+ fun getCommentById() {
+ val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
+ webTestClient.get()
+ .uri("/pentests/{commentId}/comment", commentId)
+ .header("Authorization", "Bearer $tokenAdmin")
+ .exchange()
+ .expectStatus().isOk
+ .expectHeader().doesNotExist("")
+ .expectBody().json(Json.write(commentOne.toCommentResponseBody()))
+ .consumeWith(
+ WebTestClientRestDocumentation.document(
+ "{methodName}",
+ Preprocessors.preprocessRequest(
+ Preprocessors.prettyPrint(),
+ Preprocessors.modifyUris().removePort(),
+ Preprocessors.removeHeaders("Host", "Content-Length")
+ ),
+ Preprocessors.preprocessResponse(
+ Preprocessors.prettyPrint()
+ ),
+ RequestDocumentation.relaxedPathParameters(
+ RequestDocumentation.parameterWithName("commentId").description("The id of the comment you want to get")
+ ),
+ PayloadDocumentation.relaxedResponseFields(
+ PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
+ .description("The id of the requested comment"),
+ PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
+ .description("The title of the requested comment"),
+ PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
+ .description("The description number of the comment"),
+ PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
+ .description("List of related findings of the comment")
+ )
+ )
+ )
+ }
+
+ private val commentOne = Comment(
+ id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
+ title = "Found Bug",
+ description = "OTG-INFO-002 Bug",
+ relatedFindings = emptyList()
+ )
+ }
+
@Nested
inner class SaveComment {
@Test
@@ -149,6 +197,54 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
)
}
+ @Nested
+ inner class UpdateFinding {
+ @Test
+ fun updateCommentById() {
+ val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
+ webTestClient.patch()
+ .uri("/pentests/{commentId}/comment", commentId)
+ .header("Authorization", "Bearer $tokenAdmin")
+ .body(Mono.just(commentBody), CommentRequestBody::class.java)
+ .exchange()
+ .expectStatus().isAccepted
+ .expectHeader().doesNotExist("")
+ .expectBody().json(Json.write(commentBody))
+ .consumeWith(
+ WebTestClientRestDocumentation.document(
+ "{methodName}",
+ Preprocessors.preprocessRequest(
+ Preprocessors.prettyPrint(),
+ Preprocessors.modifyUris().removePort(),
+ Preprocessors.removeHeaders("Host", "Content-Length")
+ ),
+ Preprocessors.preprocessResponse(
+ Preprocessors.prettyPrint()
+ ),
+ RequestDocumentation.relaxedPathParameters(
+ RequestDocumentation.parameterWithName("commentId").description("The id of the comment you want to update")
+ ),
+ PayloadDocumentation.relaxedResponseFields(
+ PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
+ .description("The id of the updated comment"),
+ PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
+ .description("The title of the requested comment"),
+ PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
+ .description("The description number of the comment"),
+ PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
+ .description("List of related findings of the comment")
+ )
+ )
+ )
+ }
+
+ private val commentBody = CommentRequestBody(
+ title = "Updated Comment",
+ description = "Updated Description",
+ relatedFindings = emptyList()
+ )
+ }
+
@Nested
inner class DeleteComment {
@Test
diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntegrationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt
similarity index 79%
rename from security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntegrationTest.kt
rename to security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt
index b56d150..ee09542 100644
--- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntegrationTest.kt
+++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt
@@ -29,7 +29,7 @@ import java.time.Duration
NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
)
-class CommentControllerIntegrationTest: BaseIntTest() {
+class CommentControllerIntTest : BaseIntTest() {
@LocalServerPort
private var port = 0
@@ -85,6 +85,28 @@ class CommentControllerIntegrationTest: BaseIntTest() {
)
}
+ @Nested
+ inner class GetComment {
+ @Test
+ fun `requesting comment by commentId successfully`() {
+ val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
+ webTestClient.get()
+ .uri("/pentests/{commentId}/comment", commentId)
+ .header("Authorization", "Bearer $tokenAdmin")
+ .exchange()
+ .expectStatus().isOk
+ .expectHeader().valueEquals("Application-Name", "SecurityC4PO")
+ .expectBody().json(Json.write(commentOne.toCommentResponseBody()))
+ }
+
+ private val commentOne = Comment(
+ id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
+ title = "Found Bug",
+ description = "OTG-INFO-002 Bug",
+ relatedFindings = emptyList()
+ )
+ }
+
@Nested
inner class SaveComment {
@Test
@@ -110,6 +132,31 @@ class CommentControllerIntegrationTest: BaseIntTest() {
)
}
+ @Nested
+ inner class UpdateComment {
+ @Test
+ fun `update comment successfully`() {
+ val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
+ webTestClient.patch()
+ .uri("/pentests/{commentId}/comment", commentId)
+ .header("Authorization", "Bearer $tokenAdmin")
+ .body(Mono.just(commentBody), CommentRequestBody::class.java)
+ .exchange()
+ .expectStatus().isAccepted
+ .expectHeader().valueEquals("Application-Name", "SecurityC4PO")
+ .expectBody()
+ .jsonPath("$.title").isEqualTo("Updated Comment")
+ .jsonPath("$.description").isEqualTo("Updated Description")
+ .jsonPath("$.relatedFindings").isEmpty
+ }
+
+ private val commentBody = CommentRequestBody(
+ title = "Updated Comment",
+ description = "Updated Description",
+ relatedFindings = emptyList()
+ )
+ }
+
@Nested
inner class DeleteComment {
@Test
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
index 056bd36..1863fc5 100644
--- 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
@@ -137,7 +137,7 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
Preprocessors.prettyPrint()
),
RequestDocumentation.relaxedPathParameters(
- RequestDocumentation.parameterWithName("findingId").description("The id of the feinidng you want to get")
+ RequestDocumentation.parameterWithName("findingId").description("The id of the finding you want to get")
),
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
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
index c4b6907..69f202f 100644
--- 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
@@ -152,8 +152,8 @@ class FindingControllerIntTest: BaseIntTest() {
inner class UpdateFinding {
@Test
fun `update finding successfully`() {
- val findingId = "43fbc63c-f624-11ec-b939-0242ac120002"
- webTestClient.post()
+ 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)