feat: As a user I want to have an export dialog to download my pentest report
This commit is contained in:
parent
bb544c71a0
commit
79a2493c37
|
@ -36,6 +36,8 @@
|
|||
"crypto-js/hmac-sha256",
|
||||
"crypto-js/lib-typedarrays",
|
||||
"js-cookie",
|
||||
"chartjs-plugin-annotation",
|
||||
"chart.js",
|
||||
"deep-equal",
|
||||
"moment-timezone",
|
||||
"uuid"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
<div class="button-container">
|
||||
<nb-actions size="medium">
|
||||
<nb-action>
|
||||
<!--status="button-outline-basic-text-color"-->
|
||||
<button nbButton
|
||||
status="button-outline-basic-text-color"
|
||||
status="primary"
|
||||
shape="round"
|
||||
(click)="onClickEditPentestProject()">
|
||||
<fa-icon [icon]="fa.faEdit"
|
||||
|
@ -27,7 +28,7 @@
|
|||
<button nbButton hero
|
||||
status="info"
|
||||
shape="round"
|
||||
(click)="onClickExportPentest()">
|
||||
(click)="onClickExportPentestReport()">
|
||||
<fa-icon [icon]="fa.faFileExport"
|
||||
class="element-icon fa-lg"></fa-icon>
|
||||
<span class="element-text">{{ 'global.action.export' | translate }}</span>
|
||||
|
|
|
@ -12,16 +12,18 @@ import {NgxsModule, Store} from '@ngxs/store';
|
|||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
import {NbActionsModule, NbIconModule} from '@nebular/theme';
|
||||
import {ProjectService} from '@shared/services/project.service';
|
||||
import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
||||
import {ProjectService} from '@shared/services/api/project.service';
|
||||
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||
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 {Category} from '@shared/models/category.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
|
||||
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
selectedProject: {
|
||||
|
@ -78,6 +80,7 @@ describe('ObjectiveHeaderComponent', () => {
|
|||
providers: [
|
||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},
|
||||
{provide: DialogService, useClass: DialogServiceMock},
|
||||
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
||||
]
|
||||
|
|
|
@ -9,11 +9,13 @@ import {BehaviorSubject} from 'rxjs';
|
|||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||
import {filter, mergeMap} from 'rxjs/operators';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {ProjectService} from '@shared/services/project.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||
import {ProjectService} from '@shared/services/api/project.service';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||
import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
|
||||
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||
import {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/export-report-dialog.component';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
@ -28,9 +30,10 @@ export class ObjectiveHeaderComponent implements OnInit {
|
|||
|
||||
constructor(private store: Store,
|
||||
private readonly notificationService: NotificationService,
|
||||
private projectService: ProjectService,
|
||||
private dialogService: DialogService,
|
||||
private projectDialogService: ProjectDialogService,
|
||||
private projectService: ProjectService,
|
||||
private exportReportDialogService: ExportReportDialogService,
|
||||
private readonly router: Router) {
|
||||
}
|
||||
|
||||
|
@ -87,8 +90,30 @@ export class ObjectiveHeaderComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
onClickExportPentest(): void {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info('To be implemented..');
|
||||
onClickExportPentestReport(): void {
|
||||
this.exportReportDialogService.openExportReportDialog(
|
||||
ExportReportDialogComponent,
|
||||
this.selectedProject$.getValue(),
|
||||
{
|
||||
closeOnEsc: true,
|
||||
hasScroll: false,
|
||||
autoFocus: true,
|
||||
closeOnBackdropClick: true
|
||||
}
|
||||
).pipe(
|
||||
filter(value => !!value),
|
||||
/*ToDo: Needed?*/
|
||||
/*mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(this.selectedProject$.getValue().id, value)),*/
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: () => {
|
||||
// ToDo: Open report in new Tab or just download it?
|
||||
// this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
|
||||
},
|
||||
error: error => {
|
||||
console.error(error);
|
||||
// this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
|||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {CommonAppModule} from '../common-app.module';
|
||||
import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.module';
|
||||
import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/export-report-dialog.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -50,6 +51,7 @@ import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.modul
|
|||
FontAwesomeModule,
|
||||
FlexLayoutModule,
|
||||
NbActionsModule,
|
||||
ExportReportDialogModule,
|
||||
ObjectiveOverviewRoutingModule
|
||||
],
|
||||
exports: [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Component, OnInit} from '@angular/core';
|
||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||
import {Pentest, ObjectiveEntry, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {PentestService} from '@shared/services/api/pentest.service';
|
||||
import {Store} from '@ngxs/store';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
|
|
|
@ -14,8 +14,8 @@ import {ThemeModule} from '@assets/@theme/theme.module';
|
|||
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 {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 {MockComponent} from 'ng-mocks';
|
||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
|
|
|
@ -3,7 +3,7 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
|||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||
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 {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||
import {
|
||||
|
@ -19,11 +19,11 @@ import {Store} from '@ngxs/store';
|
|||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
|
||||
import {CommentService} from '@shared/services/comment.service';
|
||||
import {CommentService} from '@shared/services/api/comment.service';
|
||||
import {UpdatePentestComments} from '@shared/stores/project-state/project-state.actions';
|
||||
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
||||
import {Finding} from '@shared/models/finding.model';
|
||||
import {FindingService} from '@shared/services/finding.service';
|
||||
import {FindingService} from '@shared/services/api/finding.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
|
|
@ -11,8 +11,8 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
|||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {Category} from '@shared/models/category.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
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';
|
||||
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
selectedProject: {
|
||||
|
|
|
@ -5,8 +5,8 @@ import {Store} from '@ngxs/store';
|
|||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {NotificationService} from '@shared/services/notification.service';
|
||||
import {PentestService} from '@shared/services/api/pentest.service';
|
||||
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
|
|
@ -9,8 +9,8 @@ import {HttpClient} from '@angular/common/http';
|
|||
import {NgxsModule, Store} from '@ngxs/store';
|
||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||
import {NbButtonModule, NbTreeGridModule} from '@nebular/theme';
|
||||
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 {CommonModule} from '@angular/common';
|
||||
import {MockComponent} from 'ng-mocks';
|
||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {Component, OnInit} from '@angular/core';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {PentestService} from '@shared/services/api/pentest.service';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||
import {
|
||||
Finding,
|
||||
FindingDialogBody,
|
||||
|
@ -22,7 +22,7 @@ import {Store} from '@ngxs/store';
|
|||
import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {FindingService} from '@shared/services/finding.service';
|
||||
import {FindingService} from '@shared/services/api/finding.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
|
|
@ -11,8 +11,8 @@ import {NgxsModule, Store} from '@ngxs/store';
|
|||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||
import {Category} from '@shared/models/category.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
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';
|
||||
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
selectedProject: {
|
||||
|
|
|
@ -9,9 +9,9 @@ import {BehaviorSubject} from 'rxjs';
|
|||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {Project} from '@shared/models/project.model';
|
||||
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {PentestService} from '@shared/services/api/pentest.service';
|
||||
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
|
||||
|
||||
@UntilDestroy()
|
||||
|
|
|
@ -8,7 +8,7 @@ import {NbButtonModule, NbCardModule, NbProgressBarModule, NbSpinnerModule} from
|
|||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||
import {ProjectService} from '@shared/services/project.service';
|
||||
import {ProjectService} from '@shared/services/api/project.service';
|
||||
import {HttpLoaderFactory} from '../common-app.module';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
@ -16,9 +16,9 @@ import {NgxsModule} from '@ngxs/store';
|
|||
import {SessionState} from '@shared/stores/session-state/session-state';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {NotificationService} from '@shared/services/notification.service';
|
||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
||||
import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
||||
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||
import {KeycloakService} from 'keycloak-angular';
|
||||
|
|
|
@ -3,8 +3,8 @@ import * as FA from '@fortawesome/free-solid-svg-icons';
|
|||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
import {ProjectService} from '@shared/services/project.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {ProjectService} from '@shared/services/api/project.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||
|
@ -17,7 +17,7 @@ import {ProjectDialogService} from '@shared/modules/project-dialog/service/proje
|
|||
styleUrls: ['./project-overview.component.scss']
|
||||
})
|
||||
export class ProjectOverviewComponent implements OnInit {
|
||||
|
||||
// HTML only
|
||||
readonly fa = FA;
|
||||
|
||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
|
|
|
@ -10,20 +10,23 @@ import {RouterTestingModule} from '@angular/router/testing';
|
|||
import {NgxsModule, Store} from '@ngxs/store';
|
||||
import {SessionState} from '@shared/stores/session-state/session-state';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {NbCardModule, NbLayoutModule} from '@nebular/theme';
|
||||
import {NbCardModule, NbDialogRef, NbLayoutModule} from '@nebular/theme';
|
||||
import {KeycloakService} from 'keycloak-angular';
|
||||
import {ObjectiveOverviewModule} from '../../objective-overview';
|
||||
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 {ProjectService} from '@shared/services/project.service';
|
||||
import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
||||
import {ProjectService} from '@shared/services/api/project.service';
|
||||
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||
import {Category} from '@shared/models/category.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.component.spec';
|
||||
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
|
||||
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
selectedProject: {
|
||||
|
@ -58,6 +61,8 @@ describe('ProjectComponent', () => {
|
|||
let store: Store;
|
||||
|
||||
beforeEach(async () => {
|
||||
const dialogSpy = createSpyObj('NbDialogRef', ['close']);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
ProjectComponent
|
||||
|
@ -83,8 +88,10 @@ describe('ProjectComponent', () => {
|
|||
providers: [
|
||||
KeycloakService,
|
||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||
{provide: DialogService, useClass: DialogServiceMock},
|
||||
{provide: NbDialogRef, useValue: dialogSpy},
|
||||
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},
|
||||
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||
{provide: NotificationService, useClass: NotificationServiceMock}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
"action.update": "Speichern",
|
||||
"action.edit": "Editieren",
|
||||
"action.export": "Exportieren",
|
||||
"action.download": "Herunterladen",
|
||||
"action.report": "Bericht",
|
||||
"action.reset": "Zurücksetzen",
|
||||
"action.yes": "Ja",
|
||||
"action.no": "Nein",
|
||||
|
@ -19,7 +21,8 @@
|
|||
"no.progress": "Kein Fortschritt",
|
||||
"validationMessage": {
|
||||
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
||||
}
|
||||
},
|
||||
"project": "Projekt"
|
||||
},
|
||||
"languageKeys":{
|
||||
"de-DE": "Deutsch",
|
||||
|
@ -43,6 +46,19 @@
|
|||
"failed": "Benutzername oder Passwort falsch",
|
||||
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
|
||||
},
|
||||
"report": {
|
||||
"dialog": {
|
||||
"header": "Penetrationstestbereicht exportieren",
|
||||
"formatLabel": "Wählen Sie das Exportformat für den Bericht:",
|
||||
"languageLabel": "Wählen Sie die Berichtssprache:"
|
||||
},
|
||||
"popup": {
|
||||
"generation.success": "Bericht erfolgreich generiert",
|
||||
"generation.failed": "Bericht konnte nicht generiert werden"
|
||||
},
|
||||
"generate": "Bericht generieren",
|
||||
"hint": "{{completedObjectivesNumber}} Ihrer abgeschlossenen Ziele wird in den Pentestbericht aufgenommen."
|
||||
},
|
||||
"project": {
|
||||
"title.label": "Projekt Titel",
|
||||
"client.label": "Name des Auftraggebers",
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
"action.update": "Update",
|
||||
"action.edit": "Edit",
|
||||
"action.export": "Export",
|
||||
"action.download": "Download",
|
||||
"action.report": "Report",
|
||||
"action.reset": "Reset",
|
||||
"action.yes": "Yes",
|
||||
"action.no": "No",
|
||||
|
@ -19,7 +21,8 @@
|
|||
"no.progress": "No progress",
|
||||
"validationMessage": {
|
||||
"inputNotMatching": "Input does not match!"
|
||||
}
|
||||
},
|
||||
"project": "Project"
|
||||
},
|
||||
"languageKeys":{
|
||||
"de-DE": "German",
|
||||
|
@ -43,6 +46,19 @@
|
|||
"failed": "Wrong username or password",
|
||||
"unauthorized": "User not found. Please register and try again"
|
||||
},
|
||||
"report": {
|
||||
"dialog": {
|
||||
"header": "Export Penetrationtest Report",
|
||||
"formatLabel": "Pick export format for report:",
|
||||
"languageLabel": "Pick report language:"
|
||||
},
|
||||
"popup": {
|
||||
"generation.success": "Report generation successful",
|
||||
"generation.failed": "Report could not be generated"
|
||||
},
|
||||
"generate": "Generate Report",
|
||||
"hint": "{{completedObjectivesNumber}} of your completed objective(s) will be included in the pentest report."
|
||||
},
|
||||
"project": {
|
||||
"title.label": "Project Title",
|
||||
"client.label": "Name of Client",
|
||||
|
|
|
@ -8,4 +8,5 @@ export const environment = {
|
|||
|
||||
// backend service
|
||||
apiEndpoint: 'http://localhost:8443',
|
||||
reportEndpoint: 'http://localhost:8444'
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ export const environment = {
|
|||
|
||||
// backend service
|
||||
apiEndpoint: 'http://localhost:8443',
|
||||
reportEndpoint: 'http://localhost:8444'
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const GlobalTitlesVariables = {
|
||||
SECURITYC4PO_TITLE: 'Security C4PO',
|
||||
NOVATEC_NAME: 'Novatec'
|
||||
TOTAL_OWASP_OBJECTIVES: 95
|
||||
};
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Method is used to download file.
|
||||
* @param data - Array Buffer data
|
||||
* @param type - type of the document.
|
||||
*/
|
||||
export function downloadFile(data: any, type: string): void {
|
||||
const blob = new Blob([data], {type});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const pwa = window.open(url);
|
||||
if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') {
|
||||
alert('Please disable your Pop-up blocker and try again.');
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ export class TokenInterceptor implements HttpInterceptor {
|
|||
private static listOfKeycloakRelevantHosts(): { origin: string }[] {
|
||||
const relevantList = new Array<{ origin: string }>();
|
||||
relevantList.push({origin: getOriginByUrl(environment.apiEndpoint)});
|
||||
relevantList.push({origin: getOriginByUrl(environment.reportEndpoint)});
|
||||
relevantList.push({origin: getOriginByUrl(environment.keycloakURL)});
|
||||
return relevantList;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
|
||||
export class Project {
|
||||
id: string;
|
||||
client: string;
|
||||
|
@ -5,7 +7,8 @@ export class Project {
|
|||
createdAt: Date;
|
||||
tester: string;
|
||||
summary: string;
|
||||
testingProgress: number;
|
||||
projectPentests?: Array<ProjectPentests>;
|
||||
testingProgress?: number;
|
||||
createdBy: string;
|
||||
|
||||
constructor(id: string,
|
||||
|
@ -13,7 +16,8 @@ export class Project {
|
|||
title: string,
|
||||
createdAt: Date,
|
||||
tester: string,
|
||||
testingProgress: number,
|
||||
projectPentests?: Array<ProjectPentests>,
|
||||
testingProgress?: number,
|
||||
summary?: string,
|
||||
createdBy?: string) {
|
||||
this.id = id;
|
||||
|
@ -21,6 +25,7 @@ export class Project {
|
|||
this.title = title;
|
||||
this.createdAt = createdAt;
|
||||
this.tester = tester;
|
||||
this.projectPentests = projectPentests;
|
||||
this.testingProgress = testingProgress;
|
||||
this.summary = summary;
|
||||
this.createdBy = createdBy;
|
||||
|
@ -31,5 +36,10 @@ export interface ProjectDialogBody {
|
|||
title: string;
|
||||
client: string;
|
||||
tester: string;
|
||||
// ToDo: summary: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export class ProjectPentests {
|
||||
pentestId: string;
|
||||
status: PentestStatus;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,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 Mock = jest.Mock;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="export-report-dialog">
|
||||
<nb-card-header fxLayoutAlign="start center" class="export-report-header">
|
||||
{{ dialogData?.options[0].headerLabelKey | translate }}
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
||||
<label class="export-format-label">
|
||||
{{ 'global.project' | translate }}:
|
||||
</label>
|
||||
<b class="project-title">
|
||||
{{selectedProject$.getValue()?.title}}
|
||||
</b>
|
||||
<!--Chart Objective Component-->
|
||||
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
|
||||
<app-objective-chart [projectPentestData]="selectedProject$.getValue().projectPentests"></app-objective-chart>
|
||||
<span
|
||||
class="hint"> {{'popup.info' | translate}} {{ 'report.hint' | translate: this.completedProjectPentests$?.getValue() }}
|
||||
</span>
|
||||
</div>
|
||||
<!--Export Language Radio Selection-->
|
||||
<label class="export-language-label">
|
||||
{{ 'report.dialog.languageLabel' | translate }}
|
||||
</label>
|
||||
<nb-radio-group name="language" [formControl]="exportReportLanguageControl"
|
||||
class="export-radio-buttons languageContainer" status="info">
|
||||
<nb-radio value="{{exportLanguages.ENGLISH}}">
|
||||
<img src="../../assets/images/flags/{{exportLanguages.ENGLISH}}.svg" class="flag" width="25rem" height="16rem"
|
||||
alt="">
|
||||
</nb-radio>
|
||||
<nb-radio disabled value="{{exportLanguages.GERMAN}}">
|
||||
<img src="../../assets/images/flags/{{exportLanguages.GERMAN}}.svg" class="flag" width="25rem" height="16rem"
|
||||
alt="">
|
||||
</nb-radio>
|
||||
</nb-radio-group>
|
||||
<!--Export Format Radio Selection-->
|
||||
<label class="export-format-label">
|
||||
{{ 'report.dialog.formatLabel' | translate }}
|
||||
</label>
|
||||
<nb-radio-group name="format" [formControl]="exportReportFormatControl" class="export-radio-buttons"
|
||||
status="info">
|
||||
<nb-radio value="{{exportFormats.PDF}}">
|
||||
{{exportFormats.PDF}}
|
||||
</nb-radio>
|
||||
<nb-radio disabled value="{{exportFormats.CSV}}">
|
||||
{{exportFormats.CSV}}
|
||||
</nb-radio>
|
||||
<nb-radio disabled value="{{exportFormats.HTML}}">
|
||||
{{exportFormats.HTML}}
|
||||
</nb-radio>
|
||||
</nb-radio-group>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
|
||||
<button nbButton status="info" size="small" class="dialog-button generate-report-button"
|
||||
[disabled]="(loading$.getValue() === true || completedProjectPentests$?.getValue().completedObjectivesNumber < 1)"
|
||||
(click)="onClickExport(exportReportFormatControl.value, exportReportLanguageControl.value)">
|
||||
<fa-icon [icon]="fa.faFileExport"
|
||||
class="element-icon fa-lg"></fa-icon>
|
||||
<span class="element-text"> {{ dialogData?.options[0].buttonKey | translate}} </span>
|
||||
</button>
|
||||
<button nbButton status="danger" size="small" class="dialog-button" (click)="onClickClose()">
|
||||
{{ 'global.action.cancel' | translate }}
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
|
||||
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<ExportReportDialogComponent>;
|
||||
|
||||
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
|
||||
},
|
||||
]
|
||||
};
|
|
@ -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<ExportReportDialogComponent>
|
||||
) {
|
||||
}
|
||||
|
||||
// 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<Project> = new BehaviorSubject<Project>(null);
|
||||
completedProjectPentests$: BehaviorSubject<any> = new BehaviorSubject<any>({completedObjectivesNumber: 0});
|
||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(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<boolean> {
|
||||
return this.loading$.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
export enum ExportFormatOptions {
|
||||
PDF = 'PDF',
|
||||
CSV = 'CSV',
|
||||
HTML = 'HTML'
|
||||
}
|
||||
|
||||
export enum ExportLanguageOptions {
|
||||
ENGLISH = 'en-US',
|
||||
GERMAN = 'de-DE'
|
||||
}
|
|
@ -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 { }
|
|
@ -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<ExportReportDialogService> {
|
||||
|
||||
dialog: any;
|
||||
|
||||
openExportReportDialog(
|
||||
componentOrTemplateRef: ComponentType<any>,
|
||||
project: Project | undefined,
|
||||
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
||||
return of(undefined);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<NbDialogConfig<Partial<any> | string>>,
|
||||
reportData?: GenericDialogData
|
||||
): Partial<NbDialogConfig<Partial<any> | 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<any>,
|
||||
project?: Project,
|
||||
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
||||
let dialogOptions: Partial<NbDialogConfig<Partial<any> | 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;
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="objective-chart" fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
||||
<canvas id="PentestObjectiveChart" class="chart" >{{ chart }}</canvas>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
.objective-chart {
|
||||
width: 40rem !important;
|
||||
height: 16rem;
|
||||
|
||||
.chart {
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
}
|
||||
}
|
|
@ -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<ObjectiveChartComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<string> = [
|
||||
'pentest.statusText.disabled',
|
||||
'pentest.statusText.not_started',
|
||||
'pentest.statusText.open',
|
||||
'pentest.statusText.in_progress',
|
||||
'pentest.statusText.completed'
|
||||
];
|
||||
|
||||
translatedPentestStatusLabels: Array<string> = [];
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 { }
|
|
@ -2,7 +2,7 @@
|
|||
@import '../../../assets/@theme/styles/themes';
|
||||
|
||||
.project-dialog {
|
||||
width: 40rem !important;
|
||||
width: 34rem !important;
|
||||
height: 42.5rem;
|
||||
|
||||
.project-dialog-header {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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<ProjectService> {
|
|||
return of([]);
|
||||
}
|
||||
|
||||
getCompletedProjectById(projectId: string): Observable<Project> {
|
||||
return of();
|
||||
}
|
||||
|
||||
getEvaluatedProjectById(projectId: string): Observable<Project> {
|
||||
return of();
|
||||
}
|
||||
|
||||
saveProject(saveProject: ProjectDialogBody): Observable<Project> {
|
||||
return of();
|
||||
}
|
|
@ -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 = {
|
|
@ -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<Project[]>(`${this.apiBaseURL}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get completed project by id
|
||||
*/
|
||||
public getCompletedProjectById(projectId: string): Observable<Project> {
|
||||
return this.http.get<Project>(`${this.apiBaseURL}/${projectId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get evaluated project by id
|
||||
*/
|
||||
public getEvaluatedProjectById(projectId: string): Observable<Project> {
|
||||
return this.http.get<Project>(`${this.apiBaseURL}/evaluation/${projectId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Project
|
||||
* @param project the information of the project
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<ArrayBuffer> {
|
||||
// @ts-ignore
|
||||
return this.http.get<ArrayBuffer>(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'})
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
@ -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', () => {
|
|
@ -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';
|
|
@ -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;
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class ProjectController(private val projectService: ProjectService) {
|
|||
}
|
||||
|
||||
@GetMapping("/{projectId}")
|
||||
fun getProjectById(
|
||||
fun getCompletedProjectById(
|
||||
@PathVariable(value = "projectId") projectId: String
|
||||
): Mono<ResponseEntity<ResponseBody>> {
|
||||
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<ResponseEntity<ResponseBody>> {
|
||||
return projectService.getProjectById(projectId).map {
|
||||
it.toProjectEvaluatedPentestResponseBody()
|
||||
}.map {
|
||||
if (it.isEmpty()) ResponseEntity.noContent().build()
|
||||
else ResponseEntity.ok(it)
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
fun saveProject(
|
||||
@RequestBody body: ProjectRequestBody
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue