From 79a2493c3792fe92b5c4885824073b7b5d658636 Mon Sep 17 00:00:00 2001 From: Marcel Haag Date: Tue, 21 Feb 2023 15:51:58 +0100 Subject: [PATCH] feat: As a user I want to have an export dialog to download my pentest report --- security-c4po-angular/angular.json | 2 + security-c4po-angular/package-lock.json | 13 ++ security-c4po-angular/package.json | 1 + security-c4po-angular/src/app/app.module.ts | 2 +- .../src/app/common-app.module.ts | 2 +- .../src/app/header/header.component.ts | 2 +- .../src/app/login/login.component.spec.ts | 4 +- .../src/app/login/login.component.ts | 3 +- .../src/app/login/login.module.ts | 2 +- .../objective-header.component.html | 5 +- .../objective-header.component.spec.ts | 11 +- .../objective-header.component.ts | 37 ++++- .../objective-overview.module.ts | 2 + .../objective-table.component.ts | 2 +- .../pentest-comments.component.spec.ts | 4 +- .../pentest-comments.component.ts | 6 +- .../pentest-content.component.spec.ts | 4 +- .../pentest-content.component.ts | 4 +- .../pentest-findings.component.spec.ts | 4 +- .../pentest-findings.component.ts | 6 +- .../pentest-header.component.spec.ts | 4 +- .../pentest-header.component.ts | 4 +- .../project-overview.component.spec.ts | 8 +- .../project-overview.component.ts | 6 +- .../project/project.component.spec.ts | 19 ++- .../src/assets/i18n/de-DE.json | 18 ++- .../src/assets/i18n/en-US.json | 18 ++- .../src/environments/environment.prod.ts | 1 + .../src/environments/environment.ts | 1 + .../src/shared/config/global-variables.ts | 2 +- .../functions/download-file.function.ts | 13 ++ .../shared/interceptors/token.interceptor.ts | 1 + .../src/shared/models/project.model.ts | 16 ++- .../comment-dialog.component.spec.ts | 4 +- .../export-report-dialog.component.html | 67 +++++++++ .../export-report-dialog.component.scss | 84 +++++++++++ .../export-report-dialog.component.spec.ts | 123 ++++++++++++++++ .../export-report-dialog.component.ts | 136 ++++++++++++++++++ .../export-report-dialog.module.ts | 40 ++++++ .../export-report-dialog.service.mock.ts | 17 +++ .../export-report-dialog.service.spec.ts | 30 ++++ .../service/export-report-dialog.service.ts | 51 +++++++ .../finding-dialog.component.spec.ts | 4 +- .../objective-chart.component.html | 3 + .../objective-chart.component.scss | 9 ++ .../objective-chart.component.spec.ts | 60 ++++++++ .../objective-chart.component.ts | 128 +++++++++++++++++ .../objective-chart/objective-chart.module.ts | 18 +++ .../project-dialog.component.scss | 2 +- .../project-dialog.component.spec.ts | 4 +- .../pipes/date-time-format.pipe.spec.ts | 2 +- .../{ => api}/comment.service.spec.ts | 0 .../services/{ => api}/comment.service.ts | 2 +- .../{ => api}/finding.service.spec.ts | 0 .../services/{ => api}/finding.service.ts | 2 +- .../{ => api}/pentest.service.spec.ts | 0 .../services/{ => api}/pentest.service.ts | 2 +- .../{ => api}/project.service.mock.ts | 10 +- .../{ => api}/project.service.spec.ts | 3 +- .../services/{ => api}/project.service.ts | 18 ++- .../reporting/reporting.service.spec.ts | 28 ++++ .../services/reporting/reporting.service.ts | 23 +++ .../notification.service.mock.ts | 0 .../notification.service.spec.ts | 4 +- .../notification.service.ts | 0 .../{ => user-service}/user.service.spec.ts | 4 +- .../{ => user-service}/user.service.ts | 2 +- .../stores/session-state/session-state.ts | 2 +- .../security-c4po-api.postman_collection.json | 39 ++++- .../com/securityc4po/api/project/Project.kt | 20 ++- .../api/project/ProjectController.kt | 14 +- .../src/main/resources/application.properties | 4 +- 72 files changed, 1099 insertions(+), 87 deletions(-) create mode 100644 security-c4po-angular/src/shared/functions/download-file.function.ts create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.html create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.scss create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.mock.ts create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.spec.ts create mode 100644 security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.ts create mode 100644 security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.html create mode 100644 security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.scss create mode 100644 security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.spec.ts create mode 100644 security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.ts create mode 100644 security-c4po-angular/src/shared/modules/objective-chart/objective-chart.module.ts rename security-c4po-angular/src/shared/services/{ => api}/comment.service.spec.ts (100%) rename security-c4po-angular/src/shared/services/{ => api}/comment.service.ts (96%) rename security-c4po-angular/src/shared/services/{ => api}/finding.service.spec.ts (100%) rename security-c4po-angular/src/shared/services/{ => api}/finding.service.ts (96%) rename security-c4po-angular/src/shared/services/{ => api}/pentest.service.spec.ts (100%) rename security-c4po-angular/src/shared/services/{ => api}/pentest.service.ts (97%) rename security-c4po-angular/src/shared/services/{ => api}/project.service.mock.ts (71%) rename security-c4po-angular/src/shared/services/{ => api}/project.service.spec.ts (98%) rename security-c4po-angular/src/shared/services/{ => api}/project.service.ts (69%) create mode 100644 security-c4po-angular/src/shared/services/reporting/reporting.service.spec.ts create mode 100644 security-c4po-angular/src/shared/services/reporting/reporting.service.ts rename security-c4po-angular/src/shared/services/{ => toaster-service}/notification.service.mock.ts (100%) rename security-c4po-angular/src/shared/services/{ => toaster-service}/notification.service.spec.ts (92%) rename security-c4po-angular/src/shared/services/{ => toaster-service}/notification.service.ts (100%) rename security-c4po-angular/src/shared/services/{ => user-service}/user.service.spec.ts (88%) rename security-c4po-angular/src/shared/services/{ => user-service}/user.service.ts (97%) diff --git a/security-c4po-angular/angular.json b/security-c4po-angular/angular.json index 3803805..b3d0e11 100644 --- a/security-c4po-angular/angular.json +++ b/security-c4po-angular/angular.json @@ -36,6 +36,8 @@ "crypto-js/hmac-sha256", "crypto-js/lib-typedarrays", "js-cookie", + "chartjs-plugin-annotation", + "chart.js", "deep-equal", "moment-timezone", "uuid" diff --git a/security-c4po-angular/package-lock.json b/security-c4po-angular/package-lock.json index f38a711..c31c0da 100644 --- a/security-c4po-angular/package-lock.json +++ b/security-c4po-angular/package-lock.json @@ -2951,6 +2951,11 @@ } } }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "@nebular/eva-icons": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@nebular/eva-icons/-/eva-icons-8.0.0.tgz", @@ -5097,6 +5102,14 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chart.js": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz", + "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", diff --git a/security-c4po-angular/package.json b/security-c4po-angular/package.json index df1565c..8f11fc3 100644 --- a/security-c4po-angular/package.json +++ b/security-c4po-angular/package.json @@ -34,6 +34,7 @@ "@ngx-translate/http-loader": "^6.0.0", "@ngxs/storage-plugin": "^3.7.3", "@ngxs/store": "^3.7.3", + "chart.js": "^4.2.1", "eva-icons": "^1.1.3", "i18n-iso-countries": "^6.8.0", "jwt-decode": "^3.1.2", diff --git a/security-c4po-angular/src/app/app.module.ts b/security-c4po-angular/src/app/app.module.ts index 849c510..3d73de7 100644 --- a/security-c4po-angular/src/app/app.module.ts +++ b/security-c4po-angular/src/app/app.module.ts @@ -24,7 +24,7 @@ import {far} from '@fortawesome/free-regular-svg-icons'; import {NgxsModule} from '@ngxs/store'; import {SessionState} from '@shared/stores/session-state/session-state'; import {environment} from '../environments/environment'; -import {NotificationService} from '@shared/services/notification.service'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; import {ThemeModule} from '@assets/@theme/theme.module'; import {HeaderModule} from './header/header.module'; import {HomeModule} from './home/home.module'; diff --git a/security-c4po-angular/src/app/common-app.module.ts b/security-c4po-angular/src/app/common-app.module.ts index b032ca0..f9b32a8 100644 --- a/security-c4po-angular/src/app/common-app.module.ts +++ b/security-c4po-angular/src/app/common-app.module.ts @@ -6,7 +6,7 @@ import {HttpClient, HttpClientModule} from '@angular/common/http'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FlexLayoutModule, FlexModule} from '@angular/flex-layout'; import {MomentModule} from 'ngx-moment'; -import {NotificationService} from '@shared/services/notification.service'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; import {NbOverlayContainerAdapter, NbSpinnerModule, NbToastrModule} from '@nebular/theme'; import {ThemeModule} from '@assets/@theme/theme.module'; import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component'; diff --git a/security-c4po-angular/src/app/header/header.component.ts b/security-c4po-angular/src/app/header/header.component.ts index 053ab9b..23ece2a 100644 --- a/security-c4po-angular/src/app/header/header.component.ts +++ b/security-c4po-angular/src/app/header/header.component.ts @@ -15,7 +15,7 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; export class HeaderComponent implements OnInit{ readonly fa = FA; - readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE; + readonly SECURITYC4PO_TITLE: string = GlobalTitlesVariables.SECURITYC4PO_TITLE; currentTheme = ''; languages = ['en-US', 'de-DE']; diff --git a/security-c4po-angular/src/app/login/login.component.spec.ts b/security-c4po-angular/src/app/login/login.component.spec.ts index faaaa72..121a25d 100644 --- a/security-c4po-angular/src/app/login/login.component.spec.ts +++ b/security-c4po-angular/src/app/login/login.component.spec.ts @@ -21,8 +21,8 @@ import {ReactiveFormsModule} from '@angular/forms'; import {User} from '../../shared/models/user.model'; import {CommonModule} from '@angular/common'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {NotificationService} from '../../shared/services/notification.service'; -import {NotificationServiceMock} from '../../shared/services/notification.service.mock'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; +import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; import {KeycloakService} from 'keycloak-angular'; const DESIRED_STORE_STATE_SESSION: SessionStateModel = { diff --git a/security-c4po-angular/src/app/login/login.component.ts b/security-c4po-angular/src/app/login/login.component.ts index eacd9d5..e5d07ef 100644 --- a/security-c4po-angular/src/app/login/login.component.ts +++ b/security-c4po-angular/src/app/login/login.component.ts @@ -2,7 +2,7 @@ import {Component, OnInit} from '@angular/core'; import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms'; import {Router} from '@angular/router'; import {Store} from '@ngxs/store'; -import {NotificationService, PopupType} from '../../shared/services/notification.service'; +import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {User} from '../../shared/models/user.model'; import {throwError} from 'rxjs'; @@ -22,7 +22,6 @@ import {KeycloakService} from 'keycloak-angular'; export class LoginComponent implements OnInit { readonly MIN_LENGTH: number = 2; readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE; - readonly NOVATEC_NAME = GlobalTitlesVariables.NOVATEC_NAME; // ToDo: Remove after adding real authentication private readonly user = new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US'); diff --git a/security-c4po-angular/src/app/login/login.module.ts b/security-c4po-angular/src/app/login/login.module.ts index 6d550f1..84c0390 100644 --- a/security-c4po-angular/src/app/login/login.module.ts +++ b/security-c4po-angular/src/app/login/login.module.ts @@ -4,7 +4,7 @@ import {LoginComponent} from './login.component'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {HttpLoaderFactory} from '../common-app.module'; import {HttpClient} from '@angular/common/http'; -import {NotificationService} from '../../shared/services/notification.service'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; import {LoginRoutingModule} from './login-routing.module'; import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme'; import {ReactiveFormsModule} from '@angular/forms'; diff --git a/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html b/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html index 13e6aaf..f34c70e 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html +++ b/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html @@ -14,8 +14,9 @@
+ + + + + + diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.scss b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.scss new file mode 100644 index 0000000..4c60e7d --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.scss @@ -0,0 +1,84 @@ +@import "../../../assets/@theme/styles/_dialog.scss"; +@import '../../../assets/@theme/styles/themes'; + +.export-report-dialog { + width: 45.25rem !important; + height: 54.25rem; + + .export-report-header { + height: 8vh; + font-size: 1.5rem; + } + + .languageContainer { + display: flex; + max-width: 8rem; + min-width: 8rem; + + .flag { + // object-fit: contain; + margin-top: 0.5rem; + } + } + + .hint { + padding-bottom: 0.5rem; + color: nb-theme(color-danger-default); + } + + .export-radio-buttons { + float: left; + clear: none; + margin-left: 1rem; + + } + + nb-form-field { + padding: 0.5rem 0 0.75rem; + } + + .label { + display: block; + font-size: 0.95rem; + padding-bottom: 0.5rem; + } + + .project-title { + font-size: 1.5rem; + padding: 0.25rem 0.5rem 0.5rem; + } + + .form-field { + width: 26.75rem; + // width: 30rem !important; + margin-bottom: 0.5rem; + } + + .form-textarea { + width: 26.75rem !important; + // width: 30rem !important; + height: 8rem; + } + + .form-textarea:disabled { + width: 26.75rem !important; + // width: 30rem !important; + background-color: nb-theme(color-basic-transparent-focus); + height: 8rem; + } + + .generate-report-button { + width: calc(45.25rem - 5.75rem) !important; + } + + .element-text { + font-size: 1rem !important; + text-transform: uppercase !important; + padding-left: 0.5rem; + } + + .error-text { + float: left; + color: nb-theme(color-danger-default); + } +} 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 new file mode 100644 index 0000000..4c9d806 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts @@ -0,0 +1,123 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ExportReportDialogComponent} from './export-report-dialog.component'; +import {CommonModule} from '@angular/common'; +import { + NB_DIALOG_CONFIG, + NbButtonModule, + NbCardModule, + NbDialogRef, + NbFormFieldModule, + NbInputModule, + NbLayoutModule, NbRadioModule, + NbTagModule +} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {NG_VALUE_ACCESSOR, 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'; +import {HttpLoaderFactory} from '../../../app/common-app.module'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; +import {NgxsModule} from '@ngxs/store'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; +import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; +import {DialogService} from '@shared/services/dialog-service/dialog.service'; +import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock'; +import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.component.spec'; +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', () => { + let component: ExportReportDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const dialogSpy = createSpyObj('NbDialogRef', ['close']); + + await TestBed.configureTestingModule({ + declarations: [ + ExportReportDialogComponent + ], + imports: [ + NbCardModule, + NbButtonModule, + NbFormFieldModule, + NbInputModule, + FlexLayoutModule, + FontAwesomeModule, + TranslateModule, + ReactiveFormsModule, + NbRadioModule, + ObjectiveChartModule, + ReactiveFormsModule, + BrowserAnimationsModule, + ThemeModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + NgxsModule.forRoot([]), + HttpClientModule, + HttpClientTestingModule + ], + providers: [ + {provide: NotificationService, useValue: new NotificationServiceMock()}, + {provide: DialogService, useClass: DialogServiceMock}, + {provide: NbDialogRef, useValue: dialogSpy}, + {provide: NB_DIALOG_CONFIG, useValue: mockedExportPentestDialogData} + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedExportPentestDialogData}); + fixture = TestBed.createComponent(ExportReportDialogComponent); + component = fixture.componentInstance; + component.selectedProject$.next(mockProject); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); + +const mockedPentests: ProjectPentests[] = [ + { + pentestId: '122', + status: PentestStatus.COMPLETED + } +]; + +const mockProject: Project = { + id: '56c47c56-3bcd-45f1-a05b-c197dbd33111', + client: 'E Corp', + title: 'Some Mock API (v1.0) Scanning', + createdAt: new Date('2019-01-10T09:00:00'), + tester: 'Novatester', + summary: '', + projectPentests: mockedPentests, + testingProgress: 0, + createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' +}; + +export const mockedExportPentestDialogData = { + form: {}, + options: [ + { + headerLabelKey: 'finding.create.header', + buttonKey: 'global.action.save', + accentColor: 'info', + additionalData: mockProject + }, + ] +}; diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts new file mode 100644 index 0000000..c2cf810 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts @@ -0,0 +1,136 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {GenericDialogData} from '@shared/models/generic-dialog-data'; +import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme'; +import {ReportingService} from '@shared/services/reporting/reporting.service'; +import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; +import {Project} from '@shared/models/project.model'; +import * as FA from '@fortawesome/free-solid-svg-icons'; +import {BehaviorSubject, Observable} from 'rxjs'; +import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service'; +import {ProjectService} from '@shared/services/api/project.service'; +import {PentestStatus} from '@shared/models/pentest-status.model'; +import {tap} from 'rxjs/operators'; +import {downloadFile} from '@shared/functions/download-file.function'; + +@Component({ + selector: 'app-export-report-dialog', + templateUrl: './export-report-dialog.component.html', + styleUrls: ['./export-report-dialog.component.scss'] +}) +@UntilDestroy() +export class ExportReportDialogComponent implements OnInit { + + constructor( + @Inject(NB_DIALOG_CONFIG) private data: GenericDialogData, + private projectService: ProjectService, + private reportingService: ReportingService, + private readonly notificationService: NotificationService, + protected dialogRef: NbDialogRef + ) { + } + + // HTML + readonly fa = FA; + // form control elements + exportReportFormatControl = new FormControl(ExportFormatOptions.PDF); + exportReportLanguageControl = new FormControl(ExportLanguageOptions.ENGLISH); + // exports + exportFormats = ExportFormatOptions; + exportLanguages = ExportLanguageOptions; + + dialogData: GenericDialogData; + + selectedProject$: BehaviorSubject = new BehaviorSubject(null); + completedProjectPentests$: BehaviorSubject = new BehaviorSubject({completedObjectivesNumber: 0}); + loading$: BehaviorSubject = new BehaviorSubject(true); + + ngOnInit(): void { + this.dialogData = this.data; + this.loadEvaluatedProject(); + } + + loadEvaluatedProject(): void { + // Get project id from dialog data + const projectId = this.dialogData.options[0].additionalData.id; + // Request project information by id + this.projectService.getEvaluatedProjectById(projectId) + .pipe( + tap(() => this.loading$.next(true)), + untilDestroyed(this) + ) + .subscribe({ + next: (project: Project) => { + this.selectedProject$.next(project); + const completedPentestObjectives = project.projectPentests.filter(pentest => pentest.status === PentestStatus.COMPLETED); + this.completedProjectPentests$.next({completedObjectivesNumber: completedPentestObjectives.length}); + this.loading$.next(false); + }, + error: err => { + console.log(err); + this.notificationService.showPopup('project.popup.not.found', PopupType.FAILURE); + this.loading$.next(false); + } + }); + } + + onClickExport(reportFormat: string, reportLanguage: string): void { + // Loading is true as long as there is a response from the reporting service + this.loading$.next(true); + // Export pentest in choosen format + switch (reportFormat) { + case ExportFormatOptions.PDF: { + this.reportingService.getReportPDFforProjectById(this.selectedProject$.getValue().id).pipe( + untilDestroyed(this) + ).subscribe({ + next: (response) => { + downloadFile(response, 'application/pdf'); + this.loading$.next(false); + this.notificationService.showPopup('report.popup.generation.success', PopupType.SUCCESS); + }, + error: error => { + console.error(error); + this.loading$.next(false); + this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE); + } + }); + break; + } + case ExportFormatOptions.CSV: { + this.loading$.next(false); + this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE); + break; + } + case ExportFormatOptions.HTML: { + this.loading$.next(false); + this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE); + break; + } + default: { + this.loading$.next(false); + this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE); + break; + } + } + } + + onClickClose(): void { + this.dialogRef.close(); + } + + // HTML only + isLoading(): Observable { + return this.loading$.asObservable(); + } +} + +export enum ExportFormatOptions { + PDF = 'PDF', + CSV = 'CSV', + HTML = 'HTML' +} + +export enum ExportLanguageOptions { + ENGLISH = 'en-US', + GERMAN = 'de-DE' +} diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts new file mode 100644 index 0000000..3e94bc2 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts @@ -0,0 +1,40 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {CommonAppModule} from '../../../app/common-app.module'; +import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbRadioModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {TranslateModule} from '@ngx-translate/core'; +import {ReactiveFormsModule} from '@angular/forms'; +import {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/export-report-dialog.component'; +import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service'; +import {ReportingService} from '@shared/services/reporting/reporting.service'; +import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module'; + +@NgModule({ + declarations: [ + ExportReportDialogComponent + ], + imports: [ + CommonModule, + CommonAppModule, + NbCardModule, + NbButtonModule, + NbFormFieldModule, + NbInputModule, + FlexLayoutModule, + FontAwesomeModule, + TranslateModule, + ReactiveFormsModule, + NbRadioModule, + ObjectiveChartModule + ], + providers: [ + ExportReportDialogService, + ReportingService + ], + entryComponents: [ + ExportReportDialogComponent + ] +}) +export class ExportReportDialogModule { } diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.mock.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.mock.ts new file mode 100644 index 0000000..4525ab5 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.mock.ts @@ -0,0 +1,17 @@ +import {ComponentType} from '@angular/cdk/overlay'; +import {NbDialogConfig} from '@nebular/theme'; +import {Observable, of} from 'rxjs'; +import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service'; +import {Project} from '@shared/models/project.model'; + +export class ExportReportDialogServiceMock implements Required { + + dialog: any; + + openExportReportDialog( + componentOrTemplateRef: ComponentType, + project: Project | undefined, + config: Partial | string>> | undefined): Observable { + return of(undefined); + } +} diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.spec.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.spec.ts new file mode 100644 index 0000000..84ec095 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.spec.ts @@ -0,0 +1,30 @@ +import { TestBed } from '@angular/core/testing'; + +import { ExportReportDialogService } from './export-report-dialog.service'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {NbDialogModule, NbDialogRef} from '@nebular/theme'; +import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock'; + +describe('ExportReportDialogService', () => { + let service: ExportReportDialogService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + BrowserAnimationsModule, + NbDialogModule.forChild() + ], + providers: [ + {provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock}, + {provide: NbDialogRef, useValue: {}}, + ] + }); + service = TestBed.inject(ExportReportDialogService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.ts new file mode 100644 index 0000000..f9528ea --- /dev/null +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/service/export-report-dialog.service.ts @@ -0,0 +1,51 @@ +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 {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/export-report-dialog.component'; +import {Project} from '@shared/models/project.model'; + +@Injectable() +export class ExportReportDialogService { + constructor(private readonly dialog: NbDialogService) { + } + + private readonly MIN_LENGTH: number = 4; + + static addDataToDialogConfig( + dialogOptions?: Partial | string>>, + reportData?: GenericDialogData + ): Partial | string>> { + return { + context: {data: reportData}, + closeOnEsc: dialogOptions?.closeOnEsc || false, + hasScroll: dialogOptions?.hasScroll || false, + autoFocus: dialogOptions?.autoFocus || false, + closeOnBackdropClick: dialogOptions?.closeOnBackdropClick || false + }; + } + + public openExportReportDialog(componentOrTemplateRef: ComponentType, + project?: Project, + config?: Partial | string>>): Observable { + let dialogOptions: Partial | string>>; + let dialogData: GenericDialogData; + // Setup ExportReportDialogBody + dialogData = { + form: { + }, + options: [ + { + headerLabelKey: 'report.dialog.header', + buttonKey: 'report.generate', + accentColor: 'info', + additionalData: project + }, + ] + }; + // Merge dialog config with finding data + dialogOptions = ExportReportDialogService.addDataToDialogConfig(config, dialogData); + return this.dialog.open(ExportReportDialogComponent, dialogOptions).onClose; + } +} diff --git a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts index 0deec9e..bd6c95e 100644 --- a/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts +++ b/security-c4po-angular/src/shared/modules/finding-dialog/finding-dialog.component.spec.ts @@ -19,8 +19,8 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {HttpLoaderFactory} from '../../../app/common-app.module'; import {HttpClient, HttpClientModule} from '@angular/common/http'; import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {NotificationService} from '@shared/services/notification.service'; -import {NotificationServiceMock} from '@shared/services/notification.service.mock'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; +import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock'; import {Severity} from '@shared/models/severity.enum'; diff --git a/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.html b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.html new file mode 100644 index 0000000..3103149 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.html @@ -0,0 +1,3 @@ +
+ {{ chart }} +
diff --git a/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.scss b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.scss new file mode 100644 index 0000000..6eeb234 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.scss @@ -0,0 +1,9 @@ +.objective-chart { + width: 40rem !important; + height: 16rem; + + .chart { + // height: 100%; + // width: 100%; + } +} diff --git a/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.spec.ts b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.spec.ts new file mode 100644 index 0000000..4a059be --- /dev/null +++ b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.spec.ts @@ -0,0 +1,60 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ObjectiveChartComponent} from './objective-chart.component'; +import {CommonModule} from '@angular/common'; +import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule, NbTagModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; +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'; +import {HttpLoaderFactory} from '../../../app/common-app.module'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; +import {NgxsModule} from '@ngxs/store'; +import {ProjectState} from '@shared/stores/project-state/project-state'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; + +describe('ObjectiveChartComponent', () => { + let component: ObjectiveChartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + ObjectiveChartComponent + ], + imports: [ + CommonModule, + NbLayoutModule, + NbCardModule, + FlexLayoutModule, + NbInputModule, + NbTagModule, + ReactiveFormsModule, + BrowserAnimationsModule, + ThemeModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + NgxsModule.forRoot([ProjectState]), + HttpClientModule, + HttpClientTestingModule + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ObjectiveChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 0000000..94e982f --- /dev/null +++ b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.component.ts @@ -0,0 +1,128 @@ +import {Component, Input, OnInit} from '@angular/core'; +import * as FA from '@fortawesome/free-solid-svg-icons'; +import {GlobalTitlesVariables} from '@shared/config/global-variables'; +import {ProjectPentests} from '@shared/models/project.model'; +import Chart from 'chart.js/auto'; +import {PentestStatus} from '@shared/models/pentest-status.model'; +import {TranslateService} from '@ngx-translate/core'; +import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; + +@Component({ + selector: 'app-objective-chart', + templateUrl: './objective-chart.component.html', + styleUrls: ['./objective-chart.component.scss'] +}) +@UntilDestroy() +export class ObjectiveChartComponent implements OnInit { + + readonly fa = FA; + readonly TOTAL_OWASP_OBJECTIVES: number = GlobalTitlesVariables.TOTAL_OWASP_OBJECTIVES; + + @Input() projectPentestData: ProjectPentests[] = []; + + chart: any; + + data: any; + options: any; + + pentestStatusColors = { + disabledDefault: '#000', + notStartedDefault: '#8f9bb3', + info: '#34a4fe', + warning: '#ffab00', + success: '#01d68f' + }; + + constructor(private translateService: TranslateService) { + } + + // HTML only + status = PentestStatus; + readonly pentestStatusLabels: Array = [ + 'pentest.statusText.disabled', + 'pentest.statusText.not_started', + 'pentest.statusText.open', + 'pentest.statusText.in_progress', + 'pentest.statusText.completed' + ]; + + translatedPentestStatusLabels: Array = []; + + ngOnInit(): void { + this.translateLabels(); + this.createChart(); + } + + createChart(): void { + // Sort objectives by status + const disabledPentests: ProjectPentests[] + = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.DISABLED); + const openPentests: ProjectPentests[] + = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.OPEN); + const inProgressPentests: ProjectPentests[] + = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.IN_PROGRESS); + const completedPentests: ProjectPentests[] + = this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.COMPLETED); + // Find not started pentest by removing other pentests from total objective count + const notStartedPentests: number + = this.TOTAL_OWASP_OBJECTIVES - this.projectPentestData.length; + + // Setup data for chart + const pentestData = [ + disabledPentests.length, + notStartedPentests, + openPentests.length, + inProgressPentests.length, + completedPentests.length + ]; + // increase-legend-spacing + const increseLegenStylePlugin = { + id: 'increase-legend-spacing', + beforeInit(chart: any): void { + // Get reference to the original fit function + const originalFit = chart.legend.fit; + // Override the fit function + chart.legend.fit = function fit(): void { + // Call original function and bind scope in order to use `this` correctly inside it + originalFit.bind(chart.legend)(); + // Change the height as suggested in another answers + this.height += 25; + }; + } + }; + // Build Chart + this.chart = new Chart('PentestObjectiveChart', { + type: 'pie', // this denotes tha type of chart + + data: {// values on X-Axis + labels: this.translatedPentestStatusLabels.length ? this.translatedPentestStatusLabels : this.pentestStatusLabels, + datasets: [{ + label: ' ', + data: pentestData, + backgroundColor: [ + this.pentestStatusColors.disabledDefault, + this.pentestStatusColors.notStartedDefault, + this.pentestStatusColors.info, + this.pentestStatusColors.warning, + this.pentestStatusColors.success, + ], + hoverOffset: 1 + }], + }, + options: { + aspectRatio: 2.5, + }, + plugins: [increseLegenStylePlugin] + }); + } + + private translateLabels(): void { + this.pentestStatusLabels.forEach((label, index) => { + this.translateService.get(label) + .pipe(untilDestroyed(this)) + .subscribe((translated: string): void => { + this.translatedPentestStatusLabels[index] = translated; + }); + }); + } +} diff --git a/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.module.ts b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.module.ts new file mode 100644 index 0000000..9a20107 --- /dev/null +++ b/security-c4po-angular/src/shared/modules/objective-chart/objective-chart.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {ObjectiveChartComponent} from '@shared/modules/objective-chart/objective-chart.component'; +import {FlexLayoutModule} from '@angular/flex-layout'; + +@NgModule({ + declarations: [ + ObjectiveChartComponent + ], + imports: [ + CommonModule, + FlexLayoutModule + ], + exports: [ + ObjectiveChartComponent + ] +}) +export class ObjectiveChartModule { } 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 1ccf963..0b9d0f0 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 @@ -2,7 +2,7 @@ @import '../../../assets/@theme/styles/themes'; .project-dialog { - width: 40rem !important; + width: 34rem !important; height: 42.5rem; .project-dialog-header { diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts index 2ec55d2..8b6218c 100644 --- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts +++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts @@ -17,8 +17,8 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {HttpLoaderFactory} from '../../../app/common-app.module'; import {HttpClient, HttpClientModule} from '@angular/common/http'; import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {NotificationService} from '@shared/services/notification.service'; -import {NotificationServiceMock} from '@shared/services/notification.service.mock'; +import {NotificationService} from '@shared/services/toaster-service/notification.service'; +import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock'; import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock'; import {ReactiveFormsModule, Validators} from '@angular/forms'; diff --git a/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts b/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts index dadcf23..6c5b875 100644 --- a/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts +++ b/security-c4po-angular/src/shared/pipes/date-time-format.pipe.spec.ts @@ -3,7 +3,7 @@ import {User} from '@shared/models/user.model'; import {NumberAndDateFormatSystem} from '@shared/models/number-and-date-time-format.model'; import {SESSION_STATE_NAME, SessionState, SessionStateModel} from '@shared/stores/session-state/session-state'; import {NgxsModule, Store} from '@ngxs/store'; -import {UserService} from '@shared/services/user.service'; +import {UserService} from '@shared/services/user-service/user.service'; import {inject, TestBed} from '@angular/core/testing'; import {HttpClient} from '@angular/common/http'; import {HttpLoaderFactory} from '../../app/common-app.module'; diff --git a/security-c4po-angular/src/shared/services/comment.service.spec.ts b/security-c4po-angular/src/shared/services/api/comment.service.spec.ts similarity index 100% rename from security-c4po-angular/src/shared/services/comment.service.spec.ts rename to security-c4po-angular/src/shared/services/api/comment.service.spec.ts diff --git a/security-c4po-angular/src/shared/services/comment.service.ts b/security-c4po-angular/src/shared/services/api/comment.service.ts similarity index 96% rename from security-c4po-angular/src/shared/services/comment.service.ts rename to security-c4po-angular/src/shared/services/api/comment.service.ts index 4b7bc29..7d0f1c5 100644 --- a/security-c4po-angular/src/shared/services/comment.service.ts +++ b/security-c4po-angular/src/shared/services/api/comment.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import {environment} from '../../environments/environment'; +import {environment} from '../../../environments/environment'; import {HttpClient} from '@angular/common/http'; import {Store} from '@ngxs/store'; import {Observable} from 'rxjs'; diff --git a/security-c4po-angular/src/shared/services/finding.service.spec.ts b/security-c4po-angular/src/shared/services/api/finding.service.spec.ts similarity index 100% rename from security-c4po-angular/src/shared/services/finding.service.spec.ts rename to security-c4po-angular/src/shared/services/api/finding.service.spec.ts diff --git a/security-c4po-angular/src/shared/services/finding.service.ts b/security-c4po-angular/src/shared/services/api/finding.service.ts similarity index 96% rename from security-c4po-angular/src/shared/services/finding.service.ts rename to security-c4po-angular/src/shared/services/api/finding.service.ts index 16cd9e5..35f0cb4 100644 --- a/security-c4po-angular/src/shared/services/finding.service.ts +++ b/security-c4po-angular/src/shared/services/api/finding.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import {environment} from '../../environments/environment'; +import {environment} from '../../../environments/environment'; import {HttpClient} from '@angular/common/http'; import {Store} from '@ngxs/store'; import {Observable} from 'rxjs'; diff --git a/security-c4po-angular/src/shared/services/pentest.service.spec.ts b/security-c4po-angular/src/shared/services/api/pentest.service.spec.ts similarity index 100% rename from security-c4po-angular/src/shared/services/pentest.service.spec.ts rename to security-c4po-angular/src/shared/services/api/pentest.service.spec.ts diff --git a/security-c4po-angular/src/shared/services/pentest.service.ts b/security-c4po-angular/src/shared/services/api/pentest.service.ts similarity index 97% rename from security-c4po-angular/src/shared/services/pentest.service.ts rename to security-c4po-angular/src/shared/services/api/pentest.service.ts index 4e23836..7b88e55 100644 --- a/security-c4po-angular/src/shared/services/pentest.service.ts +++ b/security-c4po-angular/src/shared/services/api/pentest.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {environment} from '../../environments/environment'; +import {environment} from '../../../environments/environment'; import {HttpClient, HttpParams} from '@angular/common/http'; import {Observable, of} from 'rxjs'; import {Category} from '@shared/models/category.model'; diff --git a/security-c4po-angular/src/shared/services/project.service.mock.ts b/security-c4po-angular/src/shared/services/api/project.service.mock.ts similarity index 71% rename from security-c4po-angular/src/shared/services/project.service.mock.ts rename to security-c4po-angular/src/shared/services/api/project.service.mock.ts index 4ce3c1f..b927c1f 100644 --- a/security-c4po-angular/src/shared/services/project.service.mock.ts +++ b/security-c4po-angular/src/shared/services/api/project.service.mock.ts @@ -1,4 +1,4 @@ -import {ProjectService} from '@shared/services/project.service'; +import {ProjectService} from '@shared/services/api/project.service'; import {HttpClient} from '@angular/common/http'; import {Observable, of} from 'rxjs'; import {Project, ProjectDialogBody} from '@shared/models/project.model'; @@ -12,6 +12,14 @@ export class ProjectServiceMock implements Required { return of([]); } + getCompletedProjectById(projectId: string): Observable { + return of(); + } + + getEvaluatedProjectById(projectId: string): Observable { + return of(); + } + saveProject(saveProject: ProjectDialogBody): Observable { return of(); } diff --git a/security-c4po-angular/src/shared/services/project.service.spec.ts b/security-c4po-angular/src/shared/services/api/project.service.spec.ts similarity index 98% rename from security-c4po-angular/src/shared/services/project.service.spec.ts rename to security-c4po-angular/src/shared/services/api/project.service.spec.ts index 09d7461..a3865df 100644 --- a/security-c4po-angular/src/shared/services/project.service.spec.ts +++ b/security-c4po-angular/src/shared/services/api/project.service.spec.ts @@ -5,7 +5,7 @@ import {HttpClientTestingModule, HttpTestingController} from '@angular/common/ht import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {KeycloakService} from 'keycloak-angular'; import {Project, ProjectDialogBody} from '@shared/models/project.model'; -import {environment} from '../../environments/environment'; +import {environment} from '../../../environments/environment'; import {throwError} from 'rxjs'; describe('ProjectService', () => { @@ -83,6 +83,7 @@ describe('ProjectService', () => { client: 'E Corp', title: 'Some Mock API (v1.0) Scanning', tester: 'Novatester', + summary: '' }; const mockProject: Project = { diff --git a/security-c4po-angular/src/shared/services/project.service.ts b/security-c4po-angular/src/shared/services/api/project.service.ts similarity index 69% rename from security-c4po-angular/src/shared/services/project.service.ts rename to security-c4po-angular/src/shared/services/api/project.service.ts index ecda953..d8187ab 100644 --- a/security-c4po-angular/src/shared/services/project.service.ts +++ b/security-c4po-angular/src/shared/services/api/project.service.ts @@ -1,7 +1,7 @@ import {Injectable} from '@angular/core'; -import {environment} from '../../environments/environment'; +import {environment} from '../../../environments/environment'; import {HttpClient} from '@angular/common/http'; -import {Project, ProjectDialogBody} from '../models/project.model'; +import {Project, ProjectDialogBody} from '../../models/project.model'; import {Observable} from 'rxjs'; @Injectable({ @@ -21,6 +21,20 @@ export class ProjectService { return this.http.get(`${this.apiBaseURL}`); } + /** + * Get completed project by id + */ + public getCompletedProjectById(projectId: string): Observable { + return this.http.get(`${this.apiBaseURL}/${projectId}`); + } + + /** + * Get evaluated project by id + */ + public getEvaluatedProjectById(projectId: string): Observable { + return this.http.get(`${this.apiBaseURL}/evaluation/${projectId}`); + } + /** * Save Project * @param project the information of the project diff --git a/security-c4po-angular/src/shared/services/reporting/reporting.service.spec.ts b/security-c4po-angular/src/shared/services/reporting/reporting.service.spec.ts new file mode 100644 index 0000000..57f0a08 --- /dev/null +++ b/security-c4po-angular/src/shared/services/reporting/reporting.service.spec.ts @@ -0,0 +1,28 @@ +import { TestBed } from '@angular/core/testing'; + +import { ReportingService } from './reporting.service'; +import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {environment} from '../../../environments/environment'; + +describe('ReportingService', () => { + let service: ReportingService; + let httpMock: HttpTestingController; + + const reportBaseURL = `${environment.reportEndpoint}/reports`; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + BrowserAnimationsModule, + ] + }); + service = TestBed.inject(ReportingService); + httpMock = TestBed.inject(HttpTestingController); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/services/reporting/reporting.service.ts b/security-c4po-angular/src/shared/services/reporting/reporting.service.ts new file mode 100644 index 0000000..1d04fe8 --- /dev/null +++ b/security-c4po-angular/src/shared/services/reporting/reporting.service.ts @@ -0,0 +1,23 @@ +import {Injectable} from '@angular/core'; +import {environment} from '../../../environments/environment'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ReportingService { + + constructor(private http: HttpClient) { + } + + private reportBaseURL = `${environment.reportEndpoint}/reports`; + + /** + * Get PDF Report by project id + */ + public getReportPDFforProjectById(projectId: string): Observable { + // @ts-ignore + return this.http.get(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'}) + } +} diff --git a/security-c4po-angular/src/shared/services/notification.service.mock.ts b/security-c4po-angular/src/shared/services/toaster-service/notification.service.mock.ts similarity index 100% rename from security-c4po-angular/src/shared/services/notification.service.mock.ts rename to security-c4po-angular/src/shared/services/toaster-service/notification.service.mock.ts diff --git a/security-c4po-angular/src/shared/services/notification.service.spec.ts b/security-c4po-angular/src/shared/services/toaster-service/notification.service.spec.ts similarity index 92% rename from security-c4po-angular/src/shared/services/notification.service.spec.ts rename to security-c4po-angular/src/shared/services/toaster-service/notification.service.spec.ts index f87036c..f812adf 100644 --- a/security-c4po-angular/src/shared/services/notification.service.spec.ts +++ b/security-c4po-angular/src/shared/services/toaster-service/notification.service.spec.ts @@ -5,10 +5,10 @@ import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate import {Observable, of} from 'rxjs'; import {HttpClientTestingModule} from '@angular/common/http/testing'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {HttpLoaderFactory} from '../../app/common-app.module'; +import {HttpLoaderFactory} from '../../../app/common-app.module'; import {HttpClient} from '@angular/common/http'; import {NgxsModule} from '@ngxs/store'; -import {SessionState} from '../stores/session-state/session-state'; +import {SessionState} from '../../stores/session-state/session-state'; import {KeycloakService} from 'keycloak-angular'; import {NbToastrModule, NbToastrService} from '@nebular/theme'; diff --git a/security-c4po-angular/src/shared/services/notification.service.ts b/security-c4po-angular/src/shared/services/toaster-service/notification.service.ts similarity index 100% rename from security-c4po-angular/src/shared/services/notification.service.ts rename to security-c4po-angular/src/shared/services/toaster-service/notification.service.ts diff --git a/security-c4po-angular/src/shared/services/user.service.spec.ts b/security-c4po-angular/src/shared/services/user-service/user.service.spec.ts similarity index 88% rename from security-c4po-angular/src/shared/services/user.service.spec.ts rename to security-c4po-angular/src/shared/services/user-service/user.service.spec.ts index 99109bc..d296506 100644 --- a/security-c4po-angular/src/shared/services/user.service.spec.ts +++ b/security-c4po-angular/src/shared/services/user-service/user.service.spec.ts @@ -4,10 +4,10 @@ import { UserService } from './user.service'; import {HttpClientTestingModule} from '@angular/common/http/testing'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; -import {HttpLoaderFactory} from '../../app/common-app.module'; +import {HttpLoaderFactory} from '../../../app/common-app.module'; import {HttpClient} from '@angular/common/http'; import {NgxsModule} from '@ngxs/store'; -import {SessionState} from '../stores/session-state/session-state'; +import {SessionState} from '../../stores/session-state/session-state'; import {KeycloakService} from 'keycloak-angular'; describe('UserService', () => { diff --git a/security-c4po-angular/src/shared/services/user.service.ts b/security-c4po-angular/src/shared/services/user-service/user.service.ts similarity index 97% rename from security-c4po-angular/src/shared/services/user.service.ts rename to security-c4po-angular/src/shared/services/user-service/user.service.ts index a72a3d9..b6bd4f9 100644 --- a/security-c4po-angular/src/shared/services/user.service.ts +++ b/security-c4po-angular/src/shared/services/user-service/user.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; -import {User} from '../models/user.model'; +import {User} from '../../models/user.model'; import {from, Observable, Subscriber} from 'rxjs'; import {Store} from '@ngxs/store'; import {KeycloakService} from 'keycloak-angular'; diff --git a/security-c4po-angular/src/shared/stores/session-state/session-state.ts b/security-c4po-angular/src/shared/stores/session-state/session-state.ts index 2c44a21..1833477 100644 --- a/security-c4po-angular/src/shared/stores/session-state/session-state.ts +++ b/security-c4po-angular/src/shared/stores/session-state/session-state.ts @@ -5,7 +5,7 @@ import {TranslateService} from '@ngx-translate/core'; import {FetchUser, InitSession, ResetSession, UpdateIsAuthenticated, UpdateUser, UpdateUserSettings} from './session-state.actions'; import deepEqual from 'deep-equal'; import moment from 'moment'; -import {UserService} from '../../services/user.service'; +import {UserService} from '../../services/user-service/user.service'; export interface SessionStateModel { userAccount: User; diff --git a/security-c4po-api/security-c4po-api.postman_collection.json b/security-c4po-api/security-c4po-api.postman_collection.json index 37e5b03..ad62f9e 100644 --- a/security-c4po-api/security-c4po-api.postman_collection.json +++ b/security-c4po-api/security-c4po-api.postman_collection.json @@ -54,14 +54,14 @@ "response": [] }, { - "name": "getProjectById", + "name": "getCompletedProjectById", "request": { "auth": { "type": "bearer", "bearer": [ { "key": "token", - "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzY5NzMxMTAsImlhdCI6MTY3Njk3MjgxMCwianRpIjoiNDFkMDAwNzEtNjAyYy00NmEzLThjYjctMTJlZTExYWYyZDBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI5MTA5ZWU0Ni03OGEzLTRmMDUtODdhYi03NzIxNGJmNzNlZWMiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiOTEwOWVlNDYtNzhhMy00ZjA1LTg3YWItNzcyMTRiZjczZWVjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.hZhUBi4cGQdn3lZ1Xm1Kz2WpboiBBJCFrtODD_c4N0ZiymB0MWVc1jXzU1fQ25mZ_I9VJXqg97x_gnCM7mKJrncFxs6cj75zIeH3so1BhlcDf7q2pjIkCH1yCerPWSLtrK2pWyxTr1GyO1Cp_wqQms_7_rmpzajLzmqBGKF8vd4yAk8kHmBoGBJhRU_gVCsDnIe74in3a032---IgCJ2XA0E5yxP9oBe6_9xPuCsk82YDihbfK1ZEO-9YZt0g1Iv3y30-hG10eflftWJEMSi8Bso4H_2WSJLqy4YRuGQR0EKDiomM0deVCK9IkuaoIsdIZ8kd65YuxSnj-_ue17QTA", + "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzcwNjI4MTAsImlhdCI6MTY3NzA2MjUxMCwianRpIjoiM2NlNWIyNjEtZGZkNy00NzIwLThlODMtOTczYjg0NTIxZWU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiJlYWU4MTBkOS04OWU4LTQxNmEtOGExNS1kYzI5MWY5YmY0NGQiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiZWFlODEwZDktODllOC00MTZhLThhMTUtZGMyOTFmOWJmNDRkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.u2NP-PV9uNUtE8IyqOEjwFs_tEF55yU4F0KbdIb8P_1yl_ExiIGFBzEm_n5uUA-3AhXid56RAlrysEGjsTVKWm9c37MGc4oyzZzHoXoBFOquA-mb7K0bazmyFCSLCYszOM6oLcSrKelStu_aoga0MAqL1yrl8qYpZ902O32hG_5BJwEJo0uN-60dcDXwQMed8GqkraFoMCPBQF5vGZrGeNAMGwHXN1__E9JJU8ehPMKV_vnv11HrzK6OJfx4esWkF_5aNi-MASF2vaREbGVM0d0PGfgWDWphtJRK8acc4oHfqzWBW8M2qudZ7FDgMDJgk1mgFfUcQ_TEl-gdO_5PYQ", "type": "string" }, { @@ -87,6 +87,41 @@ }, "response": [] }, + { + "name": "getEvaluatedProjectById", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzcwNjI4MTAsImlhdCI6MTY3NzA2MjUxMCwianRpIjoiM2NlNWIyNjEtZGZkNy00NzIwLThlODMtOTczYjg0NTIxZWU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiJlYWU4MTBkOS04OWU4LTQxNmEtOGExNS1kYzI5MWY5YmY0NGQiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiZWFlODEwZDktODllOC00MTZhLThhMTUtZGMyOTFmOWJmNDRkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.u2NP-PV9uNUtE8IyqOEjwFs_tEF55yU4F0KbdIb8P_1yl_ExiIGFBzEm_n5uUA-3AhXid56RAlrysEGjsTVKWm9c37MGc4oyzZzHoXoBFOquA-mb7K0bazmyFCSLCYszOM6oLcSrKelStu_aoga0MAqL1yrl8qYpZ902O32hG_5BJwEJo0uN-60dcDXwQMed8GqkraFoMCPBQF5vGZrGeNAMGwHXN1__E9JJU8ehPMKV_vnv11HrzK6OJfx4esWkF_5aNi-MASF2vaREbGVM0d0PGfgWDWphtJRK8acc4oHfqzWBW8M2qudZ7FDgMDJgk1mgFfUcQ_TEl-gdO_5PYQ", + "type": "string" + }, + { + "key": "undefined", + "type": "any" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8443/projects/evaluation/5a4f126c-9471-43b8-80b9-6eb02b7c35d0", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8443", + "path": [ + "projects", + "evaluation", + "5a4f126c-9471-43b8-80b9-6eb02b7c35d0" + ] + } + }, + "response": [] + }, { "name": "saveProject", "request": { diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt index 2c1722b..7f37785 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt @@ -66,6 +66,20 @@ fun Project.toProjectCompletedPentestResponseBody(): ResponseBody { ) } +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION) +fun Project.toProjectEvaluatedPentestResponseBody(): ResponseBody { + return mapOf( + "id" to id, + "client" to client, + "title" to title, + "createdAt" to createdAt, + "tester" to tester, + "summary" to summary, + "projectPentests" to projectPentests, + "createdBy" to createdBy + ) +} + fun Project.toProjectDeleteResponseBody(): ResponseBody { return mapOf( "id" to id @@ -76,9 +90,9 @@ fun Project.toProjectDeleteResponseBody(): ResponseBody { fun Project.calculateProgress(): BigDecimal { // Total number of pentests listet in the OWASP testing guide // https://owasp.org/www-project-web-security-testing-guide/assets/archive/OWASP_Testing_Guide_v4.pdf - // @Value("\${owasp.web.pentests}") + // @Value("\${owasp.web.objectives}") // lateinit var TOTALPENTESTS: Int - val TOTALPENTESTS = 95.0 + val TOTAL_OWASP_OBJECTIVES = 95.0 return if (projectPentests.isEmpty()) BigDecimal.ZERO @@ -92,7 +106,7 @@ fun Project.calculateProgress(): BigDecimal { completedPentests += 0.5 } } - val progress = (completedPentests * 100) / TOTALPENTESTS + val progress = (completedPentests * 100) / TOTAL_OWASP_OBJECTIVES BigDecimal(progress).setScale(2, RoundingMode.HALF_UP) } } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt index c35f7b4..117a600 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt @@ -35,7 +35,7 @@ class ProjectController(private val projectService: ProjectService) { } @GetMapping("/{projectId}") - fun getProjectById( + fun getCompletedProjectById( @PathVariable(value = "projectId") projectId: String ): Mono> { return projectService.getProjectById(projectId).map { @@ -46,6 +46,18 @@ class ProjectController(private val projectService: ProjectService) { } } + @GetMapping("/evaluation/{projectId}") + fun getProjectById( + @PathVariable(value = "projectId") projectId: String + ): Mono> { + return projectService.getProjectById(projectId).map { + it.toProjectEvaluatedPentestResponseBody() + }.map { + if (it.isEmpty()) ResponseEntity.noContent().build() + else ResponseEntity.ok(it) + } + } + @PostMapping fun saveProject( @RequestBody body: ProjectRequestBody diff --git a/security-c4po-api/src/main/resources/application.properties b/security-c4po-api/src/main/resources/application.properties index f1ac14a..33b1cbe 100644 --- a/security-c4po-api/src/main/resources/application.properties +++ b/security-c4po-api/src/main/resources/application.properties @@ -22,6 +22,6 @@ keycloakhost=localhost keycloak.client.url=http://localhost:8080 keycloak.client.realm.path=auth/realms/c4po_realm_local/ -## Total number of pentests listet in the OWASP testing guide +## Total number of pentests / objectives listet in the OWASP testing guide ## https://owasp.org/www-project-web-security-testing-guide/assets/archive/OWASP_Testing_Guide_v4.pdf -owasp.web.pentests=95 +owasp.web.objectives=95