Compare commits
1 Commits
main
...
c4po_mhg_1
Author | SHA1 | Date |
---|---|---|
|
7b961cb8b2 |
|
@ -54,5 +54,4 @@
|
|||
</table>
|
||||
</nb-card>
|
||||
|
||||
<!--ToDo: Add loading spinner after routing fix to avoid circular dependency issues -->
|
||||
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
||||
|
|
|
@ -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<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
objectiveFindings: RelatedFindingOption[] = [];
|
||||
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
|
||||
|
@ -43,7 +46,6 @@ export class PentestCommentsComponent implements OnInit {
|
|||
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
|
||||
];
|
||||
dataSource: NbTreeGridDataSource<CommentEntry>;
|
||||
|
||||
data: CommentEntry[] = [];
|
||||
|
||||
getters: NbGetters<CommentEntry, CommentEntry> = {
|
||||
|
@ -53,6 +55,7 @@ export class PentestCommentsComponent implements OnInit {
|
|||
};
|
||||
|
||||
constructor(private readonly commentService: CommentService,
|
||||
private readonly findingService: FindingService,
|
||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
||||
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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -23,4 +23,5 @@ export interface GenericFormFieldOption {
|
|||
headerLabelKey: string;
|
||||
buttonKey: string;
|
||||
accentColor: string;
|
||||
additionalData?: any;
|
||||
}
|
||||
|
|
|
@ -57,13 +57,16 @@
|
|||
{{formArray[2].labelKey | translate}}
|
||||
</label>
|
||||
<!--<fa-icon nbPrefix [icon]="fa.faExclamationCircle" size="lg" class="finding-icon"></fa-icon>-->
|
||||
<nb-select placeholder="{{formArray[2].placeholder | translate}}" formControlName="{{formArray[2].fieldName}}" multiple
|
||||
<nb-select placeholder="{{formArray[2].placeholder | translate}}"
|
||||
id="{{formArray[2].fieldName}}"
|
||||
formControlName="{{formArray[2].fieldName}}"
|
||||
(selectedChange)="changeSelected($event)"
|
||||
fullWidth shape="semi-round" filled status="info" [size]="'large'" class="form-field relatedFindings">
|
||||
<nb-option class="reset-option">{{'global.action.reset' | translate}}</nb-option>
|
||||
<nb-option class="finding-option" *ngFor="let finding of relatedFindings" [value]="{value: finding}">
|
||||
{{finding.title}}
|
||||
</nb-option>
|
||||
multiple fullWidth shape="semi-round" filled status="info"
|
||||
[size]="'large'" class="form-field relatedFindings">
|
||||
<nb-option class="reset-option">{{'global.action.reset' | translate}}</nb-option>
|
||||
<nb-option class="finding-option" *ngFor="let finding of relatedFindings" [value]="finding">
|
||||
{{finding.title}}
|
||||
</nb-option>
|
||||
</nb-select>
|
||||
</nb-form-field>
|
||||
</form>
|
||||
|
|
|
@ -182,7 +182,8 @@ export const mockedCommentDialogData = {
|
|||
{
|
||||
headerLabelKey: 'comment.create.header',
|
||||
buttonKey: 'global.action.save',
|
||||
accentColor: 'info'
|
||||
accentColor: 'info',
|
||||
additionalData: []
|
||||
},
|
||||
]
|
||||
};
|
||||
|
|
|
@ -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<CommentDialogComponent>,
|
||||
private readonly findingService: FindingService,
|
||||
private store: Store
|
||||
protected dialogRef: NbDialogRef<CommentDialogComponent>
|
||||
) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CommentDialogService> {
|
||||
|
||||
|
@ -11,6 +11,7 @@ export class CommentDialogServiceMock implements Required<CommentDialogService>
|
|||
openCommentDialog(
|
||||
componentOrTemplateRef: ComponentType<any>,
|
||||
findingIds: [],
|
||||
relatedFindings: RelatedFindingOption[],
|
||||
comment: Comment | undefined,
|
||||
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
||||
return of(undefined);
|
||||
|
|
|
@ -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<any>,
|
||||
findingIds: string[],
|
||||
relatedFindings: RelatedFindingOption[],
|
||||
comment?: Comment,
|
||||
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
||||
let dialogOptions: Partial<NbDialogConfig<Partial<any> | 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
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Comment by Id
|
||||
* @param commentId the id of the comment
|
||||
*/
|
||||
public getCommentById(commentId: string): Observable<Comment> {
|
||||
return this.http.get<Comment>(`${this.apiBaseURL}/${commentId}/comment`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Comment
|
||||
* @param pentestId the id of the pentest
|
||||
|
@ -35,6 +44,15 @@ export class CommentService {
|
|||
return this.http.post<Comment>(`${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<Comment> {
|
||||
return this.http.patch<Comment>(`${this.apiBaseURL}/${commentId}/comment`, comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Comment
|
||||
* @param pentestId the id of the pentest
|
||||
|
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ResponseEntity<ResponseBody>> {
|
||||
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<ResponseEntity<ResponseBody>> {
|
||||
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<ResponseEntity<ResponseBody>> {
|
||||
|
|
|
@ -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<CommentService>()
|
||||
|
||||
/**
|
||||
* Get all [Comments]s by commentId's
|
||||
*
|
||||
* @return list of [Comment]s
|
||||
*/
|
||||
fun getCommentsByIds(commentIds: List<String>): Mono<List<Comment>> {
|
||||
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<Comment> {
|
||||
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<String>): Mono<List<Comment>> {
|
||||
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<Comment> {
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue