diff --git a/security-c4po-angular/package-lock.json b/security-c4po-angular/package-lock.json index c31c0da..0a9660c 100644 --- a/security-c4po-angular/package-lock.json +++ b/security-c4po-angular/package-lock.json @@ -2305,27 +2305,48 @@ "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" }, "@fortawesome/fontawesome-svg-core": { - "version": "1.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", - "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz", + "integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.3.0" + }, + "dependencies": { + "@fortawesome/fontawesome-common-types": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", + "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" + } } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", - "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz", + "integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.3.0" + }, + "dependencies": { + "@fortawesome/fontawesome-common-types": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", + "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" + } } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", - "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz", + "integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.3.0" + }, + "dependencies": { + "@fortawesome/fontawesome-common-types": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", + "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" + } } }, "@istanbuljs/load-nyc-config": { diff --git a/security-c4po-angular/package.json b/security-c4po-angular/package.json index 8f11fc3..249580b 100644 --- a/security-c4po-angular/package.json +++ b/security-c4po-angular/package.json @@ -24,9 +24,9 @@ "@angular/router": "~12.2.16", "@fortawesome/angular-fontawesome": "^0.8.2", "@fortawesome/fontawesome-common-types": "^0.2.36", - "@fortawesome/fontawesome-svg-core": "^1.2.36", - "@fortawesome/free-regular-svg-icons": "^5.15.4", - "@fortawesome/free-solid-svg-icons": "^5.15.4", + "@fortawesome/fontawesome-svg-core": "^6.3.0", + "@fortawesome/free-regular-svg-icons": "^6.3.0", + "@fortawesome/free-solid-svg-icons": "^6.3.0", "@nebular/eva-icons": "^8.0.0", "@nebular/theme": "^8.0.0", "@ngneat/until-destroy": "~8.0.4", diff --git a/security-c4po-angular/src/app/objective-overview/objective-categories/objective-categories.component.ts b/security-c4po-angular/src/app/objective-overview/objective-categories/objective-categories.component.ts index 2b5ef73..6089dbb 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-categories/objective-categories.component.ts +++ b/security-c4po-angular/src/app/objective-overview/objective-categories/objective-categories.component.ts @@ -1,11 +1,14 @@ import {Component, OnDestroy, OnInit} from '@angular/core'; import {NbMenuItem, NbMenuService} from '@nebular/theme'; -import {Subject} from 'rxjs'; +import {of, Subject} from 'rxjs'; import {Store} from '@ngxs/store'; import {ChangeCategory} from '@shared/stores/project-state/project-state.actions'; import {Category} from '@shared/models/category.model'; import {untilDestroyed} from 'ngx-take-until-destroy'; import {TranslateService} from '@ngx-translate/core'; +import {ProjectState} from '@shared/stores/project-state/project-state'; +import {catchError, switchMap, tap} from 'rxjs/operators'; +import {Pentest, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model'; @Component({ selector: 'app-objective-categories', @@ -25,8 +28,22 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy { ngOnInit(): void { this.initTranslation(); - // Set first item in list as selected - this.categories[0].selected = true; + this.store.select(ProjectState.selectedCategory).pipe( + untilDestroyed(this) + ).subscribe({ + next: (categoryIndex) => { + if (categoryIndex) { + this.selectedCategory = categoryIndex; + this.categories[categoryIndex].selected = true; + } else { + // Set first item in list as selected + this.categories[0].selected = true; + } + }, + error: error => { + console.error(error); + } + }); this.menuService.onItemClick() .pipe( untilDestroyed(this) diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html index ec1cb2f..619c68b 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html @@ -5,17 +5,8 @@ class="comment-cell" fragment="{{comment.data['commentId']}}"> - - - - {{ 'comment.commentId' | translate }} - - - {{ comment.data['commentId'] || '-' }} - - - + {{ 'comment.title' | translate }} @@ -24,7 +15,7 @@ - + {{ 'comment.description' | translate }} @@ -32,29 +23,15 @@ {{ comment.data['description'] }} - - - - {{ 'comment.relatedFindings' | translate }} - - - - - - - {{ 'comment.no.relatedFindings' | translate }} - - - - + - - - - diff --git a/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.scss b/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.scss index 2d69bf0..05df513 100644 --- a/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.scss +++ b/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.scss @@ -11,10 +11,18 @@ } .pentest-status-container { - display: flex; - align-content: flex-end; - // margin-right: 0.5rem; - // height: 5%; + // display: flex; + // align-content: flex-end; + + .timer-component { + height: 2rem !important; + max-height: 2rem !important; + margin: 0.5rem 2.25rem 1rem 0; + } + + .action-element-text { + padding-left: 0.5rem; + } .pentest-status-dialog { margin: 1rem 2.25rem 1rem 0; @@ -41,6 +49,7 @@ } .save-pentest-button { + // height: 1rem !important; margin: 1rem 0 1rem 0; } } diff --git a/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts b/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts index d8a32ee..3841a15 100644 --- a/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts +++ b/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import * as FA from '@fortawesome/free-solid-svg-icons'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {Route} from '@shared/models/route.enum'; @@ -20,7 +20,7 @@ import {StatusText} from '@shared/widgets/status-tag/status-tag.component'; templateUrl: './pentest-header.component.html', styleUrls: ['./pentest-header.component.scss'] }) -export class PentestHeaderComponent implements OnInit { +export class PentestHeaderComponent implements OnInit, OnDestroy { // HTML only readonly fa = FA; @@ -28,16 +28,21 @@ export class PentestHeaderComponent implements OnInit { selectedProjectTitle$: BehaviorSubject = new BehaviorSubject(''); pentestChanged$: BehaviorSubject = new BehaviorSubject(false); + // Pentest Timer Handler + currentTimeSpent = 0; + private initialTimeSpent: number; + // Pentest Status Handler status = PentestStatus; currentStatus: PentestStatus = PentestStatus.NOT_STARTED; private initialPentestStatus: PentestStatus; + // Status Text Translation Texts readonly statusTexts: Array = [ {value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'}, /* ToDo: Disabled not needed inside pentest */ /*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/ - {value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'}, + {value: PentestStatus.PAUSED, translationText: 'pentest.statusText.paused'}, {value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'}, {value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'} ]; @@ -51,7 +56,7 @@ export class PentestHeaderComponent implements OnInit { } ngOnInit(): void { - this.store.select(ProjectState.project).pipe( + this.store.selectOnce(ProjectState.project).pipe( untilDestroyed(this) ).subscribe({ next: (selectedProject: Project) => { @@ -68,17 +73,20 @@ export class PentestHeaderComponent implements OnInit { ).subscribe({ next: (selectedPentest: Pentest) => { this.currentStatus = selectedPentest.status; - this.initialPentestStatus = selectedPentest.status; + this.currentTimeSpent = selectedPentest.timeSpent ? selectedPentest.timeSpent : 0; this.pentest$.next(selectedPentest); }, error: err => { console.error(err); } }); + // Setup initial values for status and time outside of store subscription + this.initialPentestStatus = this.currentStatus; + this.initialTimeSpent = this.currentTimeSpent; } onClickRouteBack(): void { - // ToDo: Change to Objective Overview after routing is fixed + // Route back to overview this.router.navigate([Route.OBJECTIVE_OVERVIEW]) .then( () => { @@ -87,27 +95,18 @@ export class PentestHeaderComponent implements OnInit { ).finally(); } - onClickSavePentest(): void { - this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus}); - this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentest$.getValue())) - .subscribe({ - next: (pentest: Pentest) => { - this.store.dispatch(new ChangePentest(pentest)); - this.notificationService.showPopup('pentest.popup.save.success', PopupType.SUCCESS); - }, - error: err => { - console.log(err); - this.notificationService.showPopup('pentest.popup.save.failed', PopupType.FAILURE); - } - }); + onClickCompletePentestAndRouteBack(): void { + // Update existing Pentest + this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.COMPLETED, timeSpent: this.currentTimeSpent}); + this.updatePentest(); } - onClickUpdatePentest(): void { - this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus}); + private updatePentest(): void { this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue())) .subscribe({ next: (pentest: Pentest) => { this.store.dispatch(new ChangePentest(pentest)); + this.initialTimeSpent = pentest.timeSpent; this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS); }, error: err => { @@ -121,7 +120,7 @@ export class PentestHeaderComponent implements OnInit { * @return true if initial pentest Status is different from current pentest status */ pentestStatusChanged(): boolean { - if (this.initialPentestStatus !== this.currentStatus) { + if (this.initialTimeSpent !== this.currentTimeSpent && this.currentTimeSpent !== 0) { this.pentestChanged$.next(true); } else { this.pentestChanged$.next(false); @@ -129,6 +128,15 @@ export class PentestHeaderComponent implements OnInit { return this.pentestChanged$.getValue(); } + /** + * @return true if pentest includes at least one finding or comment + */ + pentestHasFindingsOrComments(): boolean { + const pentest: Pentest = this.pentest$.getValue(); + // Check if pentest includes any findings or comments + return pentest?.findingIds?.length > 0 || pentest?.commentIds?.length > 0; + } + /** * @return the correct nb-status for current pentest-status */ @@ -139,7 +147,7 @@ export class PentestHeaderComponent implements OnInit { pentestFillStatus = 'basic'; break; } - case PentestStatus.OPEN: { + case PentestStatus.PAUSED: { pentestFillStatus = 'info'; break; } @@ -158,4 +166,12 @@ export class PentestHeaderComponent implements OnInit { } return pentestFillStatus; } + + ngOnDestroy(): void { + if (this.pentestStatusChanged()) { + // Save current Pentest before exiting + this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.PAUSED, timeSpent: this.currentTimeSpent}); + this.updatePentest(); + } + } } diff --git a/security-c4po-angular/src/app/pentest/pentest.module.ts b/security-c4po-angular/src/app/pentest/pentest.module.ts index 1395da2..99eca2f 100644 --- a/security-c4po-angular/src/app/pentest/pentest.module.ts +++ b/security-c4po-angular/src/app/pentest/pentest.module.ts @@ -17,6 +17,7 @@ import {SeverityTagModule} from '@shared/widgets/severity-tag/severity-tag.modul import {FindingDialogModule} from '@shared/modules/finding-dialog/finding-dialog.module'; import {CommentDialogModule} from '@shared/modules/comment-dialog/comment-dialog.module'; import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.module'; +import {TimerModule} from '@shared/modules/timer/timer.module'; @NgModule({ declarations: [ @@ -49,6 +50,8 @@ import {FindigWidgetModule} from '@shared/widgets/findig-widget/findig-widget.mo FindingDialogModule, CommentDialogModule, FindigWidgetModule, + // Modules + TimerModule, ] }) export class PentestModule { diff --git a/security-c4po-angular/src/assets/i18n/de-DE.json b/security-c4po-angular/src/assets/i18n/de-DE.json index 3937b6d..ecaf71c 100644 --- a/security-c4po-angular/src/assets/i18n/de-DE.json +++ b/security-c4po-angular/src/assets/i18n/de-DE.json @@ -14,6 +14,7 @@ "action.download": "Herunterladen", "action.report": "Bericht", "action.reset": "Zurücksetzen", + "action.complete": "Fertig", "action.yes": "Ja", "action.no": "Nein", "username": "Nutzername", @@ -222,11 +223,14 @@ "not_started": "Nicht angefangen", "disabled": "Deaktiviert", "open": "Offen", + "paused": "Pausiert", "in_progress": "In Bearbeitung", "completed": "Fertig" }, "popup": { "not.found": "Keine pentests gefunden", + "initial.save.success": "Initialer Pentest erfolgreich aufgesetzt", + "initial.save.failed": "Initialer Pentest konnte nicht aufgesetzt werden", "save.success": "Pentest erfolgreich gespeichert", "save.failed": "Pentest konnte nicht gespeichert werden", "update.success": "Pentest erfolgreich aktualisiert", diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json index a5d3455..afd7e66 100644 --- a/security-c4po-angular/src/assets/i18n/en-US.json +++ b/security-c4po-angular/src/assets/i18n/en-US.json @@ -14,6 +14,7 @@ "action.download": "Download", "action.report": "Report", "action.reset": "Reset", + "action.complete": "Complete", "action.yes": "Yes", "action.no": "No", "username": "Username", @@ -222,11 +223,14 @@ "not_started": "Not Started", "disabled": "Disabled", "open": "Open", + "paused": "Paused", "in_progress": "In progress", "completed": "Completed" }, "popup": { "not.found": "No pentest found", + "initial.save.success": "Initial Pentest successfully setup", + "initial.save.failed": "Initial Pentest could not be setup", "save.success": "Pentest saved successfully", "save.failed": "Pentest could not be saved", "update.success": "Pentest updated successfully", diff --git a/security-c4po-angular/src/shared/models/comment.model.ts b/security-c4po-angular/src/shared/models/comment.model.ts index 1d49c06..b8f691b 100644 --- a/security-c4po-angular/src/shared/models/comment.model.ts +++ b/security-c4po-angular/src/shared/models/comment.model.ts @@ -4,16 +4,17 @@ export class Comment { id?: string; title: string; description?: string; - relatedFindings?: Array; + // List of attachment id's for file upload + attachments?: Array; constructor(title: string, description: string, id?: string, - relatedFindings?: Array) { + attachments?: Array) { this.id = id ? id : UUID(); this.title = title; this.description = description; - this.relatedFindings = relatedFindings; + this.attachments = attachments; } } @@ -21,7 +22,7 @@ export interface CommentEntry { commentId: string; title: string; description: string; - relatedFindings: Array; + attachments: Array; kind?: string; childEntries?: []; expanded?: boolean; @@ -34,7 +35,7 @@ export function transformCommentsToObjectiveEntries(findings: Comment[]): Commen commentId: value.id, title: value.title, description: value.description, - relatedFindings: value.relatedFindings, + attachments: value.attachments, kind: 'cell', childEntries: null, expanded: false @@ -48,8 +49,7 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme ...comment, title: comment.title, description: comment.description, - // Transforms related findings from RelatedFindingOption to list of finding ids - relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.id) : [], + attachments: comment.attachments, /* Remove Table Entry Object Properties */ childEntries: undefined, kind: undefined, @@ -62,10 +62,6 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme export interface CommentDialogBody { title: string; description: string; - relatedFindings: Array; + attachments: Array; } -export interface RelatedFindingOption { - id: string; - title: string; -} diff --git a/security-c4po-angular/src/shared/models/finding.model.ts b/security-c4po-angular/src/shared/models/finding.model.ts index ed4716e..08c78d1 100644 --- a/security-c4po-angular/src/shared/models/finding.model.ts +++ b/security-c4po-angular/src/shared/models/finding.model.ts @@ -10,6 +10,8 @@ export class Finding { affectedUrls?: Array; reproduction: string; mitigation?: string; + // List of attachment id's for file upload + attachments?: Array; constructor(title: string, severity: Severity, @@ -18,7 +20,8 @@ export class Finding { reproduction: string, id?: string, affectedUrls?: Array, - mitigation?: string) { + mitigation?: string, + attachments?: Array) { this.id = id ? id : UUID(); this.severity = severity; this.title = title; @@ -27,13 +30,15 @@ export class Finding { this.affectedUrls = affectedUrls ? affectedUrls : null; this.reproduction = reproduction; this.mitigation = mitigation ? mitigation : null; + this.attachments = attachments ? attachments : null; } } export interface FindingEntry { findingId: string; - severity: Severity; title: string; + severity: Severity; + description: string; impact: string; kind?: string; childEntries?: []; @@ -45,8 +50,9 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin findings.forEach((value: Finding) => { findingEntries.push({ findingId: value.id, - severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity, title: value.title, + severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity, + description: value.description, impact: value.impact, kind: 'cell', childEntries: null, diff --git a/security-c4po-angular/src/shared/models/pentest-status.model.ts b/security-c4po-angular/src/shared/models/pentest-status.model.ts index 547dbdd..0c279b9 100644 --- a/security-c4po-angular/src/shared/models/pentest-status.model.ts +++ b/security-c4po-angular/src/shared/models/pentest-status.model.ts @@ -1,7 +1,7 @@ export enum PentestStatus { NOT_STARTED = 'NOT_STARTED', DISABLED = 'DISABLED', - OPEN = 'OPEN', + PAUSED = 'PAUSED', IN_PROGRESS = 'IN_PROGRESS', COMPLETED = 'COMPLETED', } diff --git a/security-c4po-angular/src/shared/models/pentest.model.ts b/security-c4po-angular/src/shared/models/pentest.model.ts index 0673585..79ef392 100644 --- a/security-c4po-angular/src/shared/models/pentest.model.ts +++ b/security-c4po-angular/src/shared/models/pentest.model.ts @@ -11,6 +11,7 @@ export class Pentest { status: PentestStatus; findingIds?: Array; commentIds?: Array; + timeSpent?: number; constructor(category: Category, refNumber: string, @@ -18,7 +19,8 @@ export class Pentest { id?: string, projectId?: string, findingsIds?: Array, - commentsIds?: Array) { + commentsIds?: Array, + timeSpent?: number) { this.id = id ? id : UUID(); this.projectId = projectId ? projectId : ''; this.category = category; @@ -26,6 +28,7 @@ export class Pentest { this.status = status; this.findingIds = findingsIds ? findingsIds : []; this.commentIds = commentsIds ? commentsIds : []; + this.timeSpent = timeSpent ? timeSpent : 0; } } 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 f010830..7f79d04 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 @@ -50,25 +50,6 @@ - - - - - - - {{'global.action.reset' | translate}} - - {{finding.title}} - - - diff --git a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.scss b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.scss index 2076e7f..09db77f 100644 --- a/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.scss +++ b/security-c4po-angular/src/shared/modules/comment-dialog/comment-dialog.component.scss @@ -31,7 +31,8 @@ .form-textarea { width: 42rem !important; - height: 8rem; + // Change back to 16rem after attachments can be uploaded + height: 24rem; } .comment-form-field { 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 cf5fca1..e8d43f3 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 @@ -5,7 +5,6 @@ import * as FA from '@fortawesome/free-solid-svg-icons'; import deepEqual from 'deep-equal'; import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme'; import {UntilDestroy} from '@ngneat/until-destroy'; -import {RelatedFindingOption} from '@shared/models/comment.model'; @Component({ selector: 'app-comment-dialog', @@ -24,11 +23,6 @@ export class CommentDialogComponent implements OnInit { // HTML only readonly fa = FA; - relatedFindings: RelatedFindingOption[] = []; - // Includes the findings that got selected as an option - selectedFindings: RelatedFindingOption[] = []; - initialSelectedFindings: RelatedFindingOption[] = []; - constructor( @Inject(NB_DIALOG_CONFIG) private data: GenericDialogData, private fb: FormBuilder, @@ -38,7 +32,6 @@ export class CommentDialogComponent implements OnInit { ngOnInit(): void { this.dialogData = this.data; - this.relatedFindings = this.dialogData.options[0].additionalData; this.commentFormGroup = this.generateFormCreationFieldArray(); } @@ -48,29 +41,19 @@ export class CommentDialogComponent implements OnInit { ...accumulator, [currentValue?.fieldName]: currentValue?.controlsConfig }), {}); - // tslint:disable-next-line:no-string-literal - 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); } - changeSelected($event): void { + changeAttachments($event): void { // tslint:disable-next-line:no-string-literal - this.selectedFindings = this.commentFormGroup.controls['commentRelatedFindings'].value; + // this.commentFormGroup.controls['commentAttachments'].value; } onClickSave(value: any): void { this.dialogRef.close({ title: value.commentTitle, description: value.commentDescription, - relatedFindings: this.selectedFindings ? this.selectedFindings : [] + // relatedFindings: this.selectedFindings ? this.selectedFindings : [] }); } @@ -90,12 +73,8 @@ export class CommentDialogComponent implements OnInit { const newCommentData = this.commentFormGroup.getRawValue(); Object.entries(newCommentData).forEach(entry => { const [key, value] = entry; - // Related Findings form field can be ignored since changes here will be recognised inside commentRelatedFindings of tag-list - if (value === null || key === 'commentRelatedFindings') { - newCommentData[key] = ''; - } }); - const didChange = !deepEqual(oldCommentData, newCommentData) || !deepEqual(this.initialSelectedFindings, this.selectedFindings); + const didChange = !deepEqual(oldCommentData, newCommentData); return didChange; } @@ -109,10 +88,6 @@ export class CommentDialogComponent implements OnInit { const [key, value] = entry; 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') { - commentData[key] = ''; - } }); return commentData; } 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 3e40ba8..c6da978 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 @@ -5,7 +5,7 @@ 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, RelatedFindingOption} from '@shared/models/comment.model'; +import {Comment} from '@shared/models/comment.model'; @Injectable() export class CommentDialogService { @@ -30,18 +30,15 @@ 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); - } + // Preselect attachments + const attachments: string[] = []; + if (comment && comment.attachments.length > 0) { + comment.attachments.forEach(attachment => { + // Load attachment to show }); } // Setup CommentDialogBody @@ -72,22 +69,6 @@ export class CommentDialogService { errors: [ {errorCode: 'required', translationKey: 'comment.validationMessage.descriptionRequired'} ] - }, - commentRelatedFindings: { - fieldName: 'commentRelatedFindings', - type: 'text', - labelKey: 'comment.relatedFindings.label', - placeholder: findingIds.length === 0 ? 'comment.noFindingsInObjectivePlaceholder' : 'comment.relatedFindingsPlaceholder', - controlsConfig: [ - { - value: comment ? selectedRelatedFindings : [], - disabled: findingIds.length === 0 - }, - [] - ], - errors: [ - {errorCode: 'required', translationKey: 'finding.validationMessage.relatedFindings'} - ] } }, options: [] @@ -97,8 +78,7 @@ export class CommentDialogService { { headerLabelKey: 'comment.edit.header', buttonKey: 'global.action.update', - accentColor: 'warning', - additionalData: relatedFindings + accentColor: 'warning' }, ]; } else { @@ -106,8 +86,7 @@ export class CommentDialogService { { headerLabelKey: 'comment.create.header', buttonKey: 'global.action.save', - accentColor: 'info', - additionalData: relatedFindings + accentColor: 'info' }, ]; } diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts index 4640d90..aebeda0 100644 --- a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts @@ -1,7 +1,6 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ExportReportDialogComponent} from './export-report-dialog.component'; -import {CommonModule} from '@angular/common'; import { NB_DIALOG_CONFIG, NbButtonModule, @@ -9,11 +8,10 @@ import { NbDialogRef, NbFormFieldModule, NbInputModule, - NbLayoutModule, NbRadioModule, - NbTagModule + NbRadioModule } from '@nebular/theme'; import {FlexLayoutModule} from '@angular/flex-layout'; -import {NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms'; +import {ReactiveFormsModule} from '@angular/forms'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {ThemeModule} from '@assets/@theme/theme.module'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; @@ -29,7 +27,6 @@ import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.compon import {Project, ProjectPentests} from '@shared/models/project.model'; import {PentestStatus} from '@shared/models/pentest-status.model'; import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module'; -import {forwardRef} from '@angular/core'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; describe('ExportReportDialogComponent', () => { diff --git a/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.ts b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.ts index 94e982f..a776397 100644 --- a/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.ts +++ b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.ts @@ -41,7 +41,7 @@ export class ObjectiveChartComponent implements OnInit { readonly pentestStatusLabels: Array = [ 'pentest.statusText.disabled', 'pentest.statusText.not_started', - 'pentest.statusText.open', + 'pentest.statusText.paused', 'pentest.statusText.in_progress', 'pentest.statusText.completed' ]; @@ -58,7 +58,7 @@ export class ObjectiveChartComponent implements OnInit { const disabledPentests: ProjectPentests[] = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.DISABLED); const openPentests: ProjectPentests[] - = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.OPEN); + = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.PAUSED); const inProgressPentests: ProjectPentests[] = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.IN_PROGRESS); const completedPentests: ProjectPentests[] diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss index 0b9d0f0..1efdd02 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss +++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss @@ -3,7 +3,7 @@ .project-dialog { width: 34rem !important; - height: 42.5rem; + height: 43.5rem; .project-dialog-header { height: 8vh; diff --git a/security-c4po-angular/src/shared/modules/timer/service/timer.service.spec.ts b/security-c4po-angular/src/shared/modules/timer/service/timer.service.spec.ts new file mode 100644 index 0000000..103cf86 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/service/timer.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TimerService } from './timer.service'; + +describe('TimerService', () => { + let service: TimerService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TimerService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/modules/timer/service/timer.service.ts b/security-c4po-angular/src/shared/modules/timer/service/timer.service.ts new file mode 100644 index 0000000..6f165d9 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/service/timer.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class TimerService { + + constructor() { } +} diff --git a/security-c4po-angular/src/shared/modules/timer/timer.component.html b/security-c4po-angular/src/shared/modules/timer/timer.component.html new file mode 100644 index 0000000..d49c1c2 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/timer.component.html @@ -0,0 +1,18 @@ +
+
+ + {{ timer | timerDuration }} +
+ + +
diff --git a/security-c4po-angular/src/shared/modules/timer/timer.component.scss b/security-c4po-angular/src/shared/modules/timer/timer.component.scss new file mode 100644 index 0000000..0fe84d8 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/timer.component.scss @@ -0,0 +1,28 @@ +@import '../../../assets/@theme/styles/themes'; + +.timer-module { + width: 12rem; + max-width: 12rem; + + // height: 3rem; + // max-height: 3rem; + + // overflow: auto; + + background-color: nb-theme(color-basic-transparent-focus); + border-radius: 10px; + + + .time { + //font-family: Courier, serif; + + .stopwatch-icon { + padding-right: 1.5rem; + } + } + + .stopwatch-player-button { + margin-left: 1rem; + width: 3.25rem !important; + } +} diff --git a/security-c4po-angular/src/shared/modules/timer/timer.component.spec.ts b/security-c4po-angular/src/shared/modules/timer/timer.component.spec.ts new file mode 100644 index 0000000..3f420cd --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/timer.component.spec.ts @@ -0,0 +1,54 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {TimerComponent} from './timer.component'; +import {MockPipe} from 'ng-mocks'; +import {TimerDurationPipe} from '@shared/pipes/timer-duration.pipe'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {HttpLoaderFactory} from '../../../app/common-app.module'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {CommonModule} from '@angular/common'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; +import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; +import {NgxsModule} from '@ngxs/store'; + +describe('TimerComponent', () => { + let component: TimerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + TimerComponent, + MockPipe(TimerDurationPipe) + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + NgxsModule.forRoot([]), + HttpClientModule, + HttpClientTestingModule + ], + providers: [ + {provide: NotificationService, useValue: new NotificationServiceMock()} + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/modules/timer/timer.component.ts b/security-c4po-angular/src/shared/modules/timer/timer.component.ts new file mode 100644 index 0000000..bbfee25 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/timer.component.ts @@ -0,0 +1,142 @@ +import {ChangeDetectionStrategy, Component, OnDestroy, OnInit} from '@angular/core'; +import * as FA from '@fortawesome/free-solid-svg-icons'; +import {BehaviorSubject} from 'rxjs'; +import {PentestStatus} from '@shared/models/pentest-status.model'; +import {Store} from '@ngxs/store'; +import {ProjectState} from '@shared/stores/project-state/project-state'; +import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; +import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model'; +import {ChangePentest, UpdatePentestStatus, UpdatePentestTime} from '@shared/stores/project-state/project-state.actions'; +import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service'; +import {PentestService} from '@shared/services/api/pentest.service'; +import {Project} from '@shared/models/project.model'; + +@Component({ + selector: 'app-timer', + templateUrl: './timer.component.html', + styleUrls: ['./timer.component.scss'], + changeDetection: ChangeDetectionStrategy.Default +}) +@UntilDestroy() +export class TimerComponent implements OnInit, OnDestroy { + + readonly fa = FA; + + timer = 0; + interval; + + pentestInfo$: BehaviorSubject = new BehaviorSubject(null); + timerRunning$: BehaviorSubject = new BehaviorSubject(false); + // Needed for initial pentest creation + selectedProjectId$: BehaviorSubject = new BehaviorSubject(''); + + constructor(private pentestService: PentestService, + private notificationService: NotificationService, + private readonly store: Store) { + } + + ngOnInit(): void { + this.store.selectOnce(ProjectState.project).pipe( + untilDestroyed(this) + ).subscribe({ + next: (selectedProject: Project) => { + this.selectedProjectId$.next(selectedProject.id); + }, + error: err => { + console.error(err); + } + }); + + this.store.selectOnce(ProjectState.pentest).pipe( + untilDestroyed(this) + ).subscribe({ + next: (selectedPentest: Pentest) => { + this.pentestInfo$.next(selectedPentest); + // In case pentest time spent is undefined use 0 + this.timer = selectedPentest.timeSpent ? selectedPentest.timeSpent : 0; + if (!selectedPentest.id) { + this.createIntialPentestInBackend(); + } + }, + error: err => { + console.error(err); + } + }); + } + + private createIntialPentestInBackend(): void { + // Save initial Pentest a new + this.pentestInfo$.next({...this.pentestInfo$.getValue(), timeSpent: this.timer}); + this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentestInfo$.getValue())) + .subscribe({ + next: (pentest: Pentest) => { + this.store.dispatch(new ChangePentest(pentest)); + this.notificationService.showPopup('pentest.popup.initial.save.success', PopupType.SUCCESS); + }, + error: err => { + console.log(err); + this.notificationService.showPopup('pentest.popup.initial.save.failed', PopupType.FAILURE); + } + }); + } + + onClickTriggerTimer(): void { + this.timerRunning$.next(!this.timerRunning$.getValue()); + // Start or pause timer + if (this.timerRunning$.getValue()) { + this.startTimer(); + } else { + this.pauseTimer(); + } + } + + // TIMER related functions below + private startTimer(): boolean { + this.interval = setInterval(() => this.timer++, 1000); + this.store.dispatch(new UpdatePentestStatus(PentestStatus.IN_PROGRESS)); + return true; + } + + private pauseTimer(): boolean { + clearInterval(this.interval); + this.store.dispatch(new UpdatePentestTime(this.timer)); + this.store.dispatch(new UpdatePentestStatus(PentestStatus.PAUSED)); + return false; + } + + /** + * @return the correct nb-status for current pentest-status + */ + getStopwatchButtonStatus(): string { + const value: PentestStatus = this.pentestInfo$.getValue().status; + let stopwatchButtonStatus; + switch (value) { + case PentestStatus.NOT_STARTED: { + stopwatchButtonStatus = 'basic'; + break; + } + case PentestStatus.PAUSED: { + stopwatchButtonStatus = 'info'; + break; + } + case PentestStatus.IN_PROGRESS: { + stopwatchButtonStatus = 'warning'; + break; + } + case PentestStatus.COMPLETED: { + stopwatchButtonStatus = 'success'; + break; + } + default: { + stopwatchButtonStatus = 'basic'; + break; + } + } + return stopwatchButtonStatus; + } + + ngOnDestroy(): void { + // Automatically push new time spent to store + this.store.dispatch(new UpdatePentestTime(this.timer)); + } +} diff --git a/security-c4po-angular/src/shared/modules/timer/timer.module.ts b/security-c4po-angular/src/shared/modules/timer/timer.module.ts new file mode 100644 index 0000000..d283ef2 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/timer/timer.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {TimerComponent} from '@shared/modules/timer/timer.component'; +import {TimerService} from '@shared/modules/timer/service/timer.service'; +import {NbButtonModule, NbCardModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {TimerDurationPipe} from '@shared/pipes/timer-duration.pipe'; + +@NgModule({ + declarations: [ + TimerComponent, + TimerDurationPipe + ], + imports: [ + CommonModule, + NbCardModule, + FlexLayoutModule, + FontAwesomeModule, + NbButtonModule + ], + providers: [ + TimerService + ], + exports: [ + TimerComponent + ] +}) +export class TimerModule { } diff --git a/security-c4po-angular/src/shared/pipes/timer-duration.pipe.spec.ts b/security-c4po-angular/src/shared/pipes/timer-duration.pipe.spec.ts new file mode 100644 index 0000000..b753035 --- /dev/null +++ b/security-c4po-angular/src/shared/pipes/timer-duration.pipe.spec.ts @@ -0,0 +1,8 @@ +import { TimerDurationPipe } from './timer-duration.pipe'; + +describe('TimerDurationPipe', () => { + it('create an instance', () => { + const pipe = new TimerDurationPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/pipes/timer-duration.pipe.ts b/security-c4po-angular/src/shared/pipes/timer-duration.pipe.ts new file mode 100644 index 0000000..991567d --- /dev/null +++ b/security-c4po-angular/src/shared/pipes/timer-duration.pipe.ts @@ -0,0 +1,38 @@ +import {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({ + name: 'timerDuration', + pure: false +}) +export class TimerDurationPipe implements PipeTransform { + + /** + * Transforms input time into readable time indication + * @param time of type number + * @param args The unit to be used for calculation + * @returns string in the format `HH:mm:ss` + */ + transform(time: any, ...args: any[]): string { + let hours: string | number = 0; + let minutes: string | number = 0; + let seconds: string | number = 0; + if (time) { + // tslint:disable-next-line:variable-name + const sec_num = parseInt(time, 10); // don't forget the second param + hours = Math.floor(sec_num / 3600); + minutes = Math.floor((sec_num - (hours * 3600)) / 60); + seconds = sec_num - (hours * 3600) - (minutes * 60); + } + + /** + * Add the relevant `0` prefix if any of the numbers are less than 10 + * i.e. 5 -> 05 + */ + seconds = (seconds < 10) ? '0' + seconds : seconds; + minutes = (minutes < 10) ? '0' + minutes : minutes; + hours = (hours < 10) ? '0' + hours : hours; + // Return time in as string in `HH:mm:ss` format + return `${hours}:${minutes}:${seconds}`; + } + +} diff --git a/security-c4po-angular/src/shared/stores/project-state/project-state.actions.ts b/security-c4po-angular/src/shared/stores/project-state/project-state.actions.ts index 5b0fd70..2da4083 100644 --- a/security-c4po-angular/src/shared/stores/project-state/project-state.actions.ts +++ b/security-c4po-angular/src/shared/stores/project-state/project-state.actions.ts @@ -1,6 +1,7 @@ import {Project} from '@shared/models/project.model'; import {Category} from '@shared/models/category.model'; import {Pentest} from '@shared/models/pentest.model'; +import {PentestStatus} from '@shared/models/pentest-status.model'; export class InitProjectState { @@ -34,6 +35,20 @@ export class ChangePentest { } } +export class UpdatePentestStatus { + static readonly type = '[ProjectState] UpdatePentestStatus'; + + constructor(public newPentestStatus: PentestStatus) { + } +} + +export class UpdatePentestTime { + static readonly type = '[ProjectState] UpdatePentestTime'; + + constructor(public time: number) { + } +} + export class UpdatePentestFindings { static readonly type = '[ProjectState] UpdatePentestFindings'; diff --git a/security-c4po-angular/src/shared/stores/project-state/project-state.ts b/security-c4po-angular/src/shared/stores/project-state/project-state.ts index c13e323..86ccde2 100644 --- a/security-c4po-angular/src/shared/stores/project-state/project-state.ts +++ b/security-c4po-angular/src/shared/stores/project-state/project-state.ts @@ -5,11 +5,14 @@ import { ChangeCategory, ChangePentest, ChangeProject, - InitProjectState, UpdatePentestComments, - UpdatePentestFindings + InitProjectState, + UpdatePentestComments, + UpdatePentestFindings, UpdatePentestStatus, + UpdatePentestTime } from '@shared/stores/project-state/project-state.actions'; import {Category} from '@shared/models/category.model'; import {Pentest} from '@shared/models/pentest.model'; +import {PentestStatus} from '@shared/models/pentest-status.model'; export const PROJECT_STATE_NAME = 'project'; @@ -91,6 +94,40 @@ export class ProjectState { }); } + @Action(UpdatePentestStatus) + updatePentestStatus(ctx: StateContext, {newPentestStatus}: UpdatePentestStatus): void { + const state = ctx.getState(); + let stateSelectedPentest: Pentest = state.selectedPentest; + // State object + const statePentestStatus: PentestStatus = stateSelectedPentest.status || PentestStatus.NOT_STARTED; + // overwrites only timeSpent + stateSelectedPentest = { + ...stateSelectedPentest, + status: newPentestStatus + }; + // patch project state + ctx.patchState({ + selectedPentest: stateSelectedPentest + }); + } + + @Action(UpdatePentestTime) + updatePentestTime(ctx: StateContext, {time}: UpdatePentestTime): void { + const state = ctx.getState(); + let stateSelectedPentest: Pentest = state.selectedPentest; + // State object + const statePentestTimeSpent: number = stateSelectedPentest.timeSpent || 0; + // overwrites only timeSpent + stateSelectedPentest = { + ...stateSelectedPentest, + timeSpent: time + }; + // patch project state + ctx.patchState({ + selectedPentest: stateSelectedPentest + }); + } + @Action(UpdatePentestFindings) updatePentestFindings(ctx: StateContext, {findingId}: UpdatePentestFindings): void { const state = ctx.getState(); @@ -109,7 +146,7 @@ export class ProjectState { ...stateSelectedPentest, findingIds: updatedFindingIds }; - // path project state + // patch project state ctx.patchState({ selectedPentest: stateSelectedPentest }); @@ -133,7 +170,7 @@ export class ProjectState { ...stateSelectedPentest, commentIds: updatedCommentIds }; - // path project state + // patch project state ctx.patchState({ selectedPentest: stateSelectedPentest }); diff --git a/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.html b/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.html index dd6f329..36914ac 100644 --- a/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.html +++ b/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.html @@ -1,6 +1,6 @@ - diff --git a/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.ts b/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.ts index 1c07279..2443082 100644 --- a/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.ts +++ b/security-c4po-angular/src/shared/widgets/status-tag/status-tag.component.ts @@ -14,7 +14,7 @@ export class StatusTagComponent implements OnInit { readonly statusTexts: Array = [ {value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'}, {value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'}, - {value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'}, + {value: PentestStatus.PAUSED, translationText: 'pentest.statusText.paused'}, {value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'}, {value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'} ]; diff --git a/security-c4po-api/security-c4po-api.postman_collection.json b/security-c4po-api/security-c4po-api.postman_collection.json index ad62f9e..aab012a 100644 --- a/security-c4po-api/security-c4po-api.postman_collection.json +++ b/security-c4po-api/security-c4po-api.postman_collection.json @@ -550,7 +550,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\",\n \"relatedFindings\": []\n}", + "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\"\n}", "options": { "raw": { "language": "json" @@ -700,7 +700,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Edited Test Comment Description\",\n \"relatedFindings\": []\n}", + "raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Edited Test Comment Description\"\n}", "options": { "raw": { "language": "json" diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt index 905bd82..8d7d888 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt @@ -14,7 +14,8 @@ data class Pentest( val refNumber: String, val status: PentestStatus, var findingIds: List = emptyList(), - var commentIds: List = emptyList() + var commentIds: List = emptyList(), + var timeSpent: Int ) fun buildPentest(body: PentestRequestBody, pentestEntity: PentestEntity): Pentest { @@ -25,7 +26,8 @@ fun buildPentest(body: PentestRequestBody, pentestEntity: PentestEntity): Pentes refNumber = body.refNumber, status = PentestStatus.valueOf(body.status), findingIds = body.findingIds, - commentIds = body.commentIds + commentIds = body.commentIds, + timeSpent = body.timeSpent ) } @@ -49,7 +51,8 @@ fun Pentest.toPentestResponseBody(): ResponseBody { "refNumber" to refNumber, "status" to status, "findingIds" to findingIds, - "commentIds" to commentIds + "commentIds" to commentIds, + "timeSpent" to timeSpent ) } @@ -81,7 +84,8 @@ data class PentestRequestBody( val category: String, val status: String, val findingIds: List, - val commentIds: List + val commentIds: List, + val timeSpent: Int ) /** @@ -107,6 +111,7 @@ fun PentestRequestBody.toPentest(): Pentest { refNumber = this.refNumber, status = PentestStatus.valueOf(this.status), findingIds = this.findingIds, - commentIds = this.commentIds + commentIds = this.commentIds, + timeSpent = this.timeSpent ) } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt index 19ad344..c06c8fc 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt @@ -21,7 +21,8 @@ fun PentestEntity.toPentest(): Pentest { this.data.refNumber, this.data.status, this.data.findingIds, - this.data.commentIds + this.data.commentIds, + this.data.timeSpent ) } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt index bf32eae..eaff477 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt @@ -3,7 +3,7 @@ package com.securityc4po.api.pentest enum class PentestStatus { NOT_STARTED, DISABLED, - OPEN, + PAUSED, IN_PROGRESS, COMPLETED } \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt index 3b7cebd..cb01b21 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/Comment.kt @@ -10,30 +10,28 @@ data class Comment ( val id: String = UUID.randomUUID().toString(), val title: String, val description: String, - val relatedFindings: List? = emptyList() + // List of attachment id's for file upload + val attachments: List? = emptyList() ) fun buildComment(body: CommentRequestBody, commentEntity: CommentEntity): Comment { return Comment( id = commentEntity.data.id, title = body.title, - description = body.description, - relatedFindings = body.relatedFindings + description = body.description ) } data class CommentRequestBody( val title: String, - val description: String, - val relatedFindings: List? = emptyList() + val description: String ) fun Comment.toCommentResponseBody(): ResponseBody { return mapOf( "id" to id, "title" to title, - "description" to description, - "relatedFindings" to relatedFindings + "description" to description ) } @@ -60,7 +58,6 @@ fun CommentRequestBody.toComment(): Comment { return Comment( id = UUID.randomUUID().toString(), title = this.title, - description = this.description, - relatedFindings = this.relatedFindings + description = this.description ) } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt index 79af7b8..2158c68 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/comment/CommentEntity.kt @@ -13,6 +13,6 @@ fun CommentEntity.toComment(): Comment { this.data.id, this.data.title, this.data.description, - this.data.relatedFindings + this.data.attachments ) } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt index 694251c..294f27d 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/Finding.kt @@ -13,7 +13,9 @@ data class Finding ( val impact: String, val affectedUrls: List? = emptyList(), val reproduction: String, - val mitigation: String? + val mitigation: String?, + // List of attachment id's for file upload + val attachments: List? = emptyList() ) fun buildFinding(body: FindingRequestBody, findingEntity: FindingEntity): Finding { diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt index 4928dab..4293924 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/finding/FindingEntity.kt @@ -17,6 +17,7 @@ fun FindingEntity.toFinding(): Finding { this.data.impact, this.data.affectedUrls, this.data.reproduction, - this.data.mitigation + this.data.mitigation, + this.data.attachments ) } diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt index d4044a9..10498ae 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt @@ -70,8 +70,10 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { Preprocessors.prettyPrint() ), RequestDocumentation.relaxedRequestParameters( - RequestDocumentation.parameterWithName("projectId").description("The id of the project you want to get the pentests for"), - RequestDocumentation.parameterWithName("category").description("The category you want to get the pentests for") + RequestDocumentation.parameterWithName("projectId") + .description("The id of the project you want to get the pentests for"), + RequestDocumentation.parameterWithName("category") + .description("The category you want to get the pentests for") ), PayloadDocumentation.relaxedResponseFields( PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING) @@ -87,7 +89,9 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { PayloadDocumentation.fieldWithPath("[].findingIds").type(JsonFieldType.ARRAY) .description("List of ids of the findings in the requested pentest"), PayloadDocumentation.fieldWithPath("[].commentIds").type(JsonFieldType.ARRAY) - .description("List of ids of the comments of the requested pentest") + .description("List of ids of the comments of the requested pentest"), + PayloadDocumentation.fieldWithPath("[].timeSpent").type(JsonFieldType.NUMBER) + .description("Time spent on the pentest") ) ) ) @@ -100,7 +104,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) private val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -109,7 +114,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) private fun getProjectsResponse() = listOf( @@ -143,7 +149,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { Preprocessors.prettyPrint() ), RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("projectId").description("The id of the project you want to save the pentest for") + RequestDocumentation.parameterWithName("projectId") + .description("The id of the project you want to save the pentest for") ), PayloadDocumentation.relaxedResponseFields( PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) @@ -159,7 +166,9 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { PayloadDocumentation.fieldWithPath("findingIds").type(JsonFieldType.ARRAY) .description("List of ids of the findings in the created pentest"), PayloadDocumentation.fieldWithPath("commentIds").type(JsonFieldType.ARRAY) - .description("List of ids of the comments of the created pentest") + .description("List of ids of the comments of the created pentest"), + PayloadDocumentation.fieldWithPath("timeSpent").type(JsonFieldType.NUMBER) + .description("Time spent on the pentest") ) ) ) @@ -171,7 +180,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-CLIENT-001", status = "IN_PROGRESS", findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) } @@ -200,7 +210,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { Preprocessors.prettyPrint() ), RequestDocumentation.relaxedPathParameters( - RequestDocumentation.parameterWithName("pentestId").description("The id of the pentest you want to update") + RequestDocumentation.parameterWithName("pentestId") + .description("The id of the pentest you want to update") ), PayloadDocumentation.relaxedResponseFields( PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) @@ -216,7 +227,9 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { PayloadDocumentation.fieldWithPath("findingIds").type(JsonFieldType.ARRAY) .description("List of ids of the findings in the updated pentest"), PayloadDocumentation.fieldWithPath("commentIds").type(JsonFieldType.ARRAY) - .description("List of ids of the comments of the updated pentest") + .description("List of ids of the comments of the updated pentest"), + PayloadDocumentation.fieldWithPath("timeSpent").type(JsonFieldType.NUMBER) + .description("Time spent on the pentest") ) ) ) @@ -226,9 +239,10 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = "INFORMATION_GATHERING", refNumber = "OTG-INFO-001", - status = "OPEN", + status = "PAUSED", findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) } @@ -252,7 +266,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -261,7 +276,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestThree = Pentest( id = "74eae112-f62c-11ec-b939-0242ac120002", @@ -270,7 +286,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt index daa876c..9cd5d88 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt @@ -77,7 +77,8 @@ class PentestControllerIntTest : BaseIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) private val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -86,7 +87,8 @@ class PentestControllerIntTest : BaseIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) private fun getPentests() = listOf( @@ -122,7 +124,8 @@ class PentestControllerIntTest : BaseIntTest() { refNumber = "OTG-CLIENT-001", status = "IN_PROGRESS", findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) } @@ -143,7 +146,7 @@ class PentestControllerIntTest : BaseIntTest() { .jsonPath("$.projectId").isEqualTo("d2e126ba-f608-11ec-b939-0242ac120025") .jsonPath("$.category").isEqualTo("INFORMATION_GATHERING") .jsonPath("$.refNumber").isEqualTo("OTG-INFO-001") - .jsonPath("$.status").isEqualTo("OPEN") + .jsonPath("$.status").isEqualTo("PAUSED") .jsonPath("$.findingIds").isEmpty .jsonPath("$.commentIds").isEmpty } @@ -152,9 +155,10 @@ class PentestControllerIntTest : BaseIntTest() { projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = "INFORMATION_GATHERING", refNumber = "OTG-INFO-001", - status = "OPEN", + status = "PAUSED", findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 24 ) } @@ -177,7 +181,8 @@ class PentestControllerIntTest : BaseIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -186,7 +191,8 @@ class PentestControllerIntTest : BaseIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestThree = Pentest( id = "16vbc63c-f624-11ec-b939-0242ac120002", @@ -195,7 +201,8 @@ class PentestControllerIntTest : BaseIntTest() { refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) 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 957abf7..d76f4df 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 @@ -302,7 +302,8 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -311,7 +312,8 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = emptyList(), - commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f") + commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + timeSpent = 56 ) val pentestThree = Pentest( id = "74eae112-f62c-11ec-b939-0242ac120002", @@ -320,7 +322,8 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() { refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 124 ) // Comment val commentOne = Comment( diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt index ee09542..0d64ffe 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/comment/CommentControllerIntTest.kt @@ -193,7 +193,8 @@ class CommentControllerIntTest : BaseIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -202,7 +203,8 @@ class CommentControllerIntTest : BaseIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = emptyList(), - commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f") + commentIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), + timeSpent = 56 ) val pentestThree = Pentest( id = "16vbc63c-f624-11ec-b939-0242ac120002", @@ -211,7 +213,8 @@ class CommentControllerIntTest : BaseIntTest() { refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 124 ) // Comment val commentOne = Comment( 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 1863fc5..0da1b91 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 @@ -350,7 +350,8 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -359,7 +360,8 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 56 ) val pentestThree = Pentest( id = "74eae112-f62c-11ec-b939-0242ac120002", @@ -368,7 +370,8 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() { refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 124 ) // Finding val findingOne = Finding( 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 69f202f..01c3135 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 @@ -217,7 +217,8 @@ class FindingControllerIntTest: BaseIntTest() { refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 0 ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", @@ -226,7 +227,8 @@ class FindingControllerIntTest: BaseIntTest() { refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 56 ) val pentestThree = Pentest( id = "16vbc63c-f624-11ec-b939-0242ac120002", @@ -235,7 +237,8 @@ class FindingControllerIntTest: BaseIntTest() { refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), - commentIds = emptyList() + commentIds = emptyList(), + timeSpent = 124 ) // Finding val findingOne = Finding( diff --git a/security-c4po-api/src/test/resources/collections/comments.json b/security-c4po-api/src/test/resources/collections/comments.json index eb532da..5b247eb 100644 --- a/security-c4po-api/src/test/resources/collections/comments.json +++ b/security-c4po-api/src/test/resources/collections/comments.json @@ -1,54 +1,33 @@ [{ "_id": { - "$oid": "63a4530b9bb2aa2c3bc77b52" + "$oid": "6405dbf113ae975803a09901" }, "lastModified": { "$date": { - "$numberLong": "1671713547508" + "$numberLong": "1678105585081" } }, "data": { - "_id": "89703b19-16c7-49e5-8e33-0c706313e5fe", - "title": "Test Comment", - "description": "No related findings", - "relatedFindings": [] + "_id": "85935303-e5b7-48ca-a504-910c1a94fb1f", + "title": "Uninteresting comment", + "description": "Nothing", + "attachments": [] }, - "_class": "com.securityc4po.api.comment.CommentEntity" + "_class": "com.securityc4po.api.pentest.comment.CommentEntity" },{ "_id": { - "$oid": "63a453e4377c3f53f86d27d8" + "$oid": "6405dc0513ae975803a09902" }, "lastModified": { "$date": { - "$numberLong": "1671713764781" + "$numberLong": "1678105605811" } }, "data": { - "_id": "df516de6-ca5e-44a6-ac50-db89bb17aac3", - "title": "New Test Comment", - "description": "Two related findings", - "relatedFindings": [ - "0bda8950-94fa-4ec6-8fa7-e09f5a8cd3e8", - "4ddb84f6-068c-4319-a8ee-1000008bb75a" - ] + "_id": "a785aaf0-1feb-429e-beb1-31bfcf70c404", + "title": "Interesting comment", + "description": "I know where your house lives", + "attachments": [] }, - "_class": "com.securityc4po.api.comment.CommentEntity" -},{ - "_id": { - "$oid": "63a454b5377c3f53f86d27d9" - }, - "lastModified": { - "$date": { - "$numberLong": "1671713973541" - } - }, - "data": { - "_id": "e55e943b-6a48-4d84-8d72-b48d7d9de5b7", - "title": "Another Test Comment", - "description": "One related findings", - "relatedFindings": [ - "5e22d38f-a4f6-4809-84ea-a803b5f1f9fc" - ] - }, - "_class": "com.securityc4po.api.comment.CommentEntity" + "_class": "com.securityc4po.api.pentest.comment.CommentEntity" }] \ No newline at end of file diff --git a/security-c4po-api/src/test/resources/collections/findings.json b/security-c4po-api/src/test/resources/collections/findings.json index 0141b80..f614558 100644 --- a/security-c4po-api/src/test/resources/collections/findings.json +++ b/security-c4po-api/src/test/resources/collections/findings.json @@ -1,357 +1,106 @@ [{ "_id": { - "$oid": "6372223efea5724fd22bae8a" + "$oid": "6405d88b13ae975803a098fb" }, "lastModified": { "$date": { - "$numberLong": "1668424254533" + "$numberLong": "1678104715816" } }, "data": { - "_id": "ef31449d-71ec-4736-952f-8b20e53117d5", + "_id": "a343150a-91c9-4564-9638-d0377eecc7c9", "severity": "LOW", - "title": "Test Title", - "description": "Test Description", - "impact": "Test Impact", - "affectedUrls": [ - "https://akveo.github.io/nebular/docs/components/progress-bar/examples#nbprogressbarcomponent" - ], - "reproduction": "Step 1: Test", - "mitigation": "Test Mitigatin" + "title": "Low Prio Finding", + "description": "This is Low Prio.", + "impact": "Impacts nothing.", + "affectedUrls": [], + "reproduction": "Open App.", + "mitigation": "", + "attachments": [] }, - "_class": "com.securityc4po.api.finding.FindingEntity" + "_class": "com.securityc4po.api.pentest.finding.FindingEntity" },{ "_id": { - "$oid": "63725fa6e612626e2c6956ee" + "$oid": "6405db8a13ae975803a098fe" }, "lastModified": { "$date": { - "$numberLong": "1668439974730" + "$numberLong": "1678105482494" } }, "data": { - "_id": "0bda8950-94fa-4ec6-8fa7-e09f5a8cd3e8", + "_id": "5bf1b2e1-69b7-463b-a1ca-4ac6ac66b10f", + "severity": "MEDIUM", + "title": "Medium Prio Finding", + "description": "Medium Description", + "impact": "Medium Impact", + "affectedUrls": [], + "reproduction": "1. Open App", + "mitigation": "", + "attachments": [] + }, + "_class": "com.securityc4po.api.pentest.finding.FindingEntity" +},{ + "_id": { + "$oid": "6405dba513ae975803a098ff" + }, + "lastModified": { + "$date": { + "$numberLong": "1678105509645" + } + }, + "data": { + "_id": "f6e6c632-ab34-479e-9584-565f61c5862a", "severity": "HIGH", - "title": "High Title", - "description": "High Description", + "title": "High Prio Finding", + "description": "High Prio Description", "impact": "High Impact", - "affectedUrls": [ - "https://angular.io/guide/routing-overview" - ], - "reproduction": "Step 1: Not be High", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374c3c4e0136563b96187b8" - }, - "lastModified": { - "$date": { - "$numberLong": "1668596676210" - } - }, - "data": { - "_id": "58f63b4e-97fb-4fe8-8527-7996896089d2", - "severity": "MEDIUM", - "title": "Medium Finding", - "description": "Medium", - "impact": "Medium", "affectedUrls": [], - "reproduction": "Medium", - "mitigation": "" + "reproduction": "1. Open App\n2. Hack", + "mitigation": "", + "attachments": [] }, - "_class": "com.securityc4po.api.finding.FindingEntity" + "_class": "com.securityc4po.api.pentest.finding.FindingEntity" },{ "_id": { - "$oid": "6374c43be0136563b96187b9" + "$oid": "6405dbcc13ae975803a09900" }, "lastModified": { "$date": { - "$numberLong": "1668596795003" + "$numberLong": "1678105548815" } }, "data": { - "_id": "72886128-b2d9-4a92-bbfe-b54373441321", + "_id": "176f5d93-0fe3-40b1-8a25-f11a6f760148", "severity": "CRITICAL", - "title": "Critical Issue", - "description": "Critical", - "impact": "Critical", + "title": "Critical Prio Finding", + "description": "Critical Description", + "impact": "Critical Impact", "affectedUrls": [], - "reproduction": "Critical", - "mitigation": "" + "reproduction": "1. Open App\n2. Hack\n3. Break everything", + "mitigation": "", + "attachments": [] }, - "_class": "com.securityc4po.api.finding.FindingEntity" + "_class": "com.securityc4po.api.pentest.finding.FindingEntity" },{ "_id": { - "$oid": "6374c488e0136563b96187ba" + "$oid": "640854a01d5b385d85c60ba7" }, "lastModified": { "$date": { - "$numberLong": "1668596872152" + "$numberLong": "1678267552968" } }, "data": { - "_id": "4ddb84f6-068c-4319-a8ee-1000008bb75a", - "severity": "HIGH", - "title": "Anothe High Issues", - "description": "High", - "impact": "High", - "affectedUrls": [], - "reproduction": "High", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374c624e0136563b96187bb" - }, - "lastModified": { - "$date": { - "$numberLong": "1668597284983" - } - }, - "data": { - "_id": "42831151-51fd-4348-b829-6b18ddd14fe1", - "severity": "MEDIUM", - "title": "Another Medium FInding", - "description": "Medium", - "impact": "Medium", - "affectedUrls": [], - "reproduction": "Medium", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374cb33e0136563b96187bc" - }, - "lastModified": { - "$date": { - "$numberLong": "1668598579443" - } - }, - "data": { - "_id": "559cd0ac-9e64-41f9-892a-4c8a9dd30357", + "_id": "1ffc2215-b8ae-43b7-bbb7-bfcfb414d534", "severity": "LOW", - "title": "Another Low One", - "description": "Low", - "impact": "Low", + "title": "Low Prio Title", + "description": "Low Prio Description", + "impact": "Low Prio Impact", "affectedUrls": [], - "reproduction": "Low", - "mitigation": "" + "reproduction": "Do Nothing", + "mitigation": "", + "attachments": [] }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374cb98e0136563b96187bd" - }, - "lastModified": { - "$date": { - "$numberLong": "1668598680140" - } - }, - "data": { - "_id": "5e22d38f-a4f6-4809-84ea-a803b5f1f9fc", - "severity": "LOW", - "title": "common", - "description": "common", - "impact": "common", - "affectedUrls": [], - "reproduction": "common", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374cc51e0136563b96187be" - }, - "lastModified": { - "$date": { - "$numberLong": "1668598865728" - } - }, - "data": { - "_id": "0bfa7511-fe33-4ab5-9af2-d4ed70c1b350", - "severity": "HIGH", - "title": "Highihihi", - "description": "High", - "impact": "High", - "affectedUrls": [], - "reproduction": "High", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374cd00e0136563b96187bf" - }, - "lastModified": { - "$date": { - "$numberLong": "1668599040593" - } - }, - "data": { - "_id": "70e413b9-d736-40d2-b7d6-236768b1230c", - "severity": "MEDIUM", - "title": "Medium Rare", - "description": "Medium", - "impact": "Medium", - "affectedUrls": [], - "reproduction": "Medium", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "6374ec35e0136563b96187c8" - }, - "lastModified": { - "$date": { - "$numberLong": "1668607029072" - } - }, - "data": { - "_id": "672d9f87-fb3d-4fc5-8c6f-cadf97661ca5", - "severity": "HIGH", - "title": "Test", - "description": "Test", - "impact": "Test", - "affectedUrls": [], - "reproduction": "Test", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "637606830687d905ca60af1d" - }, - "lastModified": { - "$date": { - "$numberLong": "1668679299814" - } - }, - "data": { - "_id": "bddf810b-f20e-473e-a63d-34fcba7e48ef", - "severity": "CRITICAL", - "title": "Login SQL Injection ", - "description": "Inside Login Form using the ' or TRUE-- Syntax will enable the user to login as the Admin.", - "impact": "Active User Session with Admin priviledges can affect the whole application.", - "affectedUrls": [ - "http://localhost:3000/#/login" - ], - "reproduction": "Step 1:\nGo to login page.\n\nStep 2:\nEnter ' or TRUE-- in the username field and enter a random password.", - "mitigation": "" - }, - "_class": "com.securityc4po.api.finding.FindingEntity" -},{ - "_id": { - "$oid": "637612280687d905ca60af20" - }, - "lastModified": { - "$date": { - "$numberLong": "1668682280551" - } - }, - "data": { - "_id": "d7c95af7-5434-4768-b62c-5b11f9396276", - "severity": "MEDIUM", - "title": "Searchbar XSS", - "description": "Adding