feat: refactor project model in order to calculate progress
This commit is contained in:
parent
24dccb3e8f
commit
4a2024dd69
20
README.md
20
README.md
|
@ -1,16 +1,10 @@
|
||||||
# security-c4po
|
# security-c4po
|
||||||
|
|
||||||
### Chief Innovator
|
## Application Architecture
|
||||||
> Daniel Mader
|

|
||||||
|
|
||||||
### Project Leads
|
## Data Structure
|
||||||
* Andreas Falk
|

|
||||||
* Christina Paule
|
|
||||||
|
|
||||||
### Developers
|
|
||||||
* Marcel Haag
|
|
||||||
* Norman Schmidt
|
|
||||||
* Stipe Knez
|
|
||||||
|
|
||||||
### Technical Requirements
|
### Technical Requirements
|
||||||
* Docker / Docker-compose
|
* Docker / Docker-compose
|
||||||
|
@ -22,12 +16,6 @@
|
||||||
* mongoDB Compass
|
* mongoDB Compass
|
||||||
* Postman
|
* Postman
|
||||||
|
|
||||||
## Application Architecture
|
|
||||||

|
|
||||||
|
|
||||||
## Data Structure
|
|
||||||

|
|
||||||
|
|
||||||
### Conventions
|
### Conventions
|
||||||
* Branch: `<initial>_c4po_<issuenumber>`
|
* Branch: `<initial>_c4po_<issuenumber>`
|
||||||
* Commit: `feat: <What was implemented?>` or `fix: <What got fixed?>`
|
* Commit: `feat: <What was implemented?>` or `fix: <What got fixed?>`
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
.export-button-container {
|
.export-button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: flex-end;
|
align-content: flex-end;
|
||||||
margin-right: 1rem;
|
margin-right: 0.5rem;
|
||||||
|
|
||||||
.export-element-icon {
|
.export-element-icon {
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,17 +20,14 @@ import {RouterModule} from '@angular/router';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
PentestHeaderComponent,
|
PentestHeaderComponent,
|
||||||
PentestCategoriesComponent,
|
PentestCategoriesComponent,
|
||||||
PentestTableComponent
|
PentestTableComponent,
|
||||||
],
|
// LoadingSpinnerComponent
|
||||||
exports: [
|
|
||||||
PentestHeaderComponent,
|
|
||||||
PentestCategoriesComponent,
|
|
||||||
PentestTableComponent
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -52,6 +49,12 @@ import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
NbActionsModule
|
NbActionsModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
PentestHeaderComponent,
|
||||||
|
PentestCategoriesComponent,
|
||||||
|
PentestTableComponent,
|
||||||
|
// LoadingSpinnerComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class PentestOverviewModule {
|
export class PentestOverviewModule {
|
||||||
|
|
|
@ -48,8 +48,11 @@
|
||||||
</th>
|
</th>
|
||||||
<td nbTreeGridCell *nbTreeGridCellDef="let pentest">
|
<td nbTreeGridCell *nbTreeGridCellDef="let pentest">
|
||||||
<app-findig-widget [numberOfFindigs]="pentest.data['findings']"></app-findig-widget>
|
<app-findig-widget [numberOfFindigs]="pentest.data['findings']"></app-findig-widget>
|
||||||
<!--{{pentest.data['findings'] || '-'}}-->
|
<!--ToDo: Add comments {{pentest.data['comments'] || '-'}}-->
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</table>
|
</table>
|
||||||
</nb-card>
|
</nb-card>
|
||||||
|
|
||||||
|
<!--ToDo: Add loading spinner after routing fix to avoid circular dependency issues
|
||||||
|
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>-->
|
||||||
|
|
|
@ -5,8 +5,8 @@ import {PentestService} from '@shared/services/pentest.service';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {PROJECT_STATE_NAME, ProjectState} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {catchError, switchMap} from 'rxjs/operators';
|
import {catchError, switchMap, tap} from 'rxjs/operators';
|
||||||
import {of} from 'rxjs';
|
import {BehaviorSubject, Observable, of} from 'rxjs';
|
||||||
import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-key-for-ref-number.function';
|
import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-key-for-ref-number.function';
|
||||||
import {Route} from '@shared/models/route.enum';
|
import {Route} from '@shared/models/route.enum';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
|
@ -20,6 +20,7 @@ import {ChangePentest} from '@shared/stores/project-state/project-state.actions'
|
||||||
})
|
})
|
||||||
export class PentestTableComponent implements OnInit {
|
export class PentestTableComponent implements OnInit {
|
||||||
|
|
||||||
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
columns: Array<PentestColumns> = [PentestColumns.TEST_ID, PentestColumns.TITLE, PentestColumns.STATUS, PentestColumns.FINDINGS];
|
columns: Array<PentestColumns> = [PentestColumns.TEST_ID, PentestColumns.TITLE, PentestColumns.STATUS, PentestColumns.FINDINGS];
|
||||||
dataSource: NbTreeGridDataSource<PentestEntry>;
|
dataSource: NbTreeGridDataSource<PentestEntry>;
|
||||||
|
|
||||||
|
@ -47,14 +48,17 @@ export class PentestTableComponent implements OnInit {
|
||||||
loadPentestData(): void {
|
loadPentestData(): void {
|
||||||
this.store.select(ProjectState.selectedCategory).pipe(
|
this.store.select(ProjectState.selectedCategory).pipe(
|
||||||
switchMap(category => this.pentestService.loadPentests(category)),
|
switchMap(category => this.pentestService.loadPentests(category)),
|
||||||
|
tap(() => this.loading$.next(true)),
|
||||||
catchError(_ => of(null)),
|
catchError(_ => of(null)),
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (pentests: Pentest[]) => {
|
next: (pentests: Pentest[]) => {
|
||||||
this.data = transformPentestsToEntries(pentests);
|
this.data = transformPentestsToEntries(pentests);
|
||||||
this.dataSource.setData(this.data, this.getters);
|
this.dataSource.setData(this.data, this.getters);
|
||||||
|
this.loading$.next(false);
|
||||||
},
|
},
|
||||||
error: error => {
|
error: error => {
|
||||||
|
this.loading$.next(false);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -78,6 +82,11 @@ export class PentestTableComponent implements OnInit {
|
||||||
getTitle(refNumber: string): string {
|
getTitle(refNumber: string): string {
|
||||||
return getTitleKeyForRefNumber(refNumber);
|
return getTitleKeyForRefNumber(refNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML only
|
||||||
|
isLoading(): Observable<boolean> {
|
||||||
|
return this.loading$.asObservable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PentestColumns {
|
enum PentestColumns {
|
||||||
|
|
|
@ -34,13 +34,20 @@
|
||||||
</span>
|
</span>
|
||||||
</nb-card-body>
|
</nb-card-body>
|
||||||
<nb-card-footer>
|
<nb-card-footer>
|
||||||
<!--ToDo: Display correct progress of project-->
|
|
||||||
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
|
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
|
||||||
<nb-progress-bar class="project-progress"
|
<div class="project-progress">
|
||||||
status="warning"
|
|
||||||
[value]="40"
|
<nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar"
|
||||||
[displayValue]="true">
|
status="warning"
|
||||||
</nb-progress-bar>
|
[value]="project.testingProgress"
|
||||||
|
[displayValue]="true">
|
||||||
|
</nb-progress-bar>
|
||||||
|
|
||||||
|
<ng-template #altProgressBar>
|
||||||
|
{{'popup.info' | translate}} {{'global.no.progress' | translate}}
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button nbButton
|
<button nbButton
|
||||||
status="primary"
|
status="primary"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -61,7 +68,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="projects.getValue().length === 0 || !isLoading()" fxLayout="row" fxLayoutAlign="center center">
|
<div *ngIf="projects.getValue().length === 0 && loading$.getValue() === false" fxLayout="row" fxLayoutAlign="center center">
|
||||||
<p class="error-text">
|
<p class="error-text">
|
||||||
{{'project.overview.no.projects' | translate}}
|
{{'project.overview.no.projects' | translate}}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -131,6 +131,7 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML only
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
return this.loading$.asObservable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {NgModule} from '@angular/core';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {ProjectOverviewComponent} from './project-overview.component';
|
import {ProjectOverviewComponent} from './project-overview.component';
|
||||||
import {ProjectOverviewRoutingModule} from './project-overview-routing.module';
|
import {ProjectOverviewRoutingModule} from './project-overview-routing.module';
|
||||||
import {NbButtonModule, NbCardModule, NbMenuModule, NbProgressBarModule, NbSidebarModule, NbSpinnerModule} from '@nebular/theme';
|
import {NbButtonModule, NbCardModule, NbProgressBarModule, NbSpinnerModule} from '@nebular/theme';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {NgModule} from '@angular/core';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {RouterModule} from '@angular/router';
|
import {RouterModule} from '@angular/router';
|
||||||
import {ProjectComponent} from './project.component';
|
import {ProjectComponent} from './project.component';
|
||||||
import {NbCardModule, NbLayoutModule, NbMenuModule, NbSidebarModule} from '@nebular/theme';
|
import {NbCardModule, NbLayoutModule} from '@nebular/theme';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
|
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
|
||||||
|
@ -13,9 +13,6 @@ import {PentestOverviewModule} from '../../pentest-overview';
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectComponent
|
ProjectComponent
|
||||||
],
|
],
|
||||||
exports: [
|
|
||||||
ProjectComponent
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
|
@ -29,6 +26,9 @@ import {PentestOverviewModule} from '../../pentest-overview';
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
ProjectDialogModule,
|
ProjectDialogModule,
|
||||||
PentestOverviewModule
|
PentestOverviewModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ProjectComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ProjectModule {
|
export class ProjectModule {
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"action.yes": "Ja",
|
"action.yes": "Ja",
|
||||||
"action.no": "Nein",
|
"action.no": "Nein",
|
||||||
"username": "Nutzername",
|
"username": "Nutzername",
|
||||||
"password": "Passwort"
|
"password": "Passwort",
|
||||||
|
"no.progress": "Kein Fortschritt"
|
||||||
},
|
},
|
||||||
"languageKeys":{
|
"languageKeys":{
|
||||||
"de-DE": "Deutsch",
|
"de-DE": "Deutsch",
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"action.yes": "Yes",
|
"action.yes": "Yes",
|
||||||
"action.no": "No",
|
"action.no": "No",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password"
|
"password": "Password",
|
||||||
|
"no.progress": "No progress"
|
||||||
},
|
},
|
||||||
"languageKeys":{
|
"languageKeys":{
|
||||||
"de-DE": "German",
|
"de-DE": "German",
|
||||||
|
|
|
@ -4,19 +4,22 @@ export class Project {
|
||||||
title: string;
|
title: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
tester: string;
|
tester: string;
|
||||||
|
testingProgress: number;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
|
|
||||||
constructor(id: string,
|
constructor(id: string,
|
||||||
client: string,
|
client: string,
|
||||||
title: string,
|
title: string,
|
||||||
createdAt: Date,
|
createdAt: Date,
|
||||||
tester?: string,
|
tester: string,
|
||||||
|
testingProgress: number,
|
||||||
createdBy?: string) {
|
createdBy?: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
this.tester = tester;
|
this.tester = tester;
|
||||||
|
this.testingProgress = testingProgress;
|
||||||
this.createdBy = createdBy;
|
this.createdBy = createdBy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j pentest\",\n \"tester\" : \"Stipe\",\n \"createdBy\" : \"10e06d7a-8dd0-4ecd-8963-056b45079c4f\"\n}",
|
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j pentest\",\n \"tester\" : \"Stipe\"\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"client\": \"updatedProject\",\n \"title\": \"log4j pentest\",\n \"tester\" : \"Stipe\"\n}",
|
"raw": "{\n \"client\": \"Dio Stonemask Inc.\",\n \"title\": \"log4jj bizarre adventure\",\n \"tester\" : \"Jojo\"\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "http://localhost:8443/projects/f2738715-4005-4aca-8d34-27ce9b8efffe",
|
"raw": "http://localhost:8443/projects/5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
"host": [
|
"host": [
|
||||||
"localhost"
|
"localhost"
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
"port": "8443",
|
"port": "8443",
|
||||||
"path": [
|
"path": [
|
||||||
"projects",
|
"projects",
|
||||||
"f2738715-4005-4aca-8d34-27ce9b8efffe"
|
"5a4f126c-9471-43b8-80b9-6eb02b7c35d0"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -261,10 +261,24 @@
|
||||||
{
|
{
|
||||||
"name": "getPentestsByProjectIdAndCategory",
|
"name": "getPentestsByProjectIdAndCategory",
|
||||||
"request": {
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NjAxNDI5NjMsImlhdCI6MTY2MDE0MjY2MywianRpIjoiNzk2YzY5NzYtZjBlYS00ZTM0LTk2MTItMjI5ZmE0ODgzOTM0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiIyYWU1MmQyYy01MjA5LTQzMjEtOWY5OS0wMTQ2YjRkMmNkY2YiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.EO5CC1VXZzybIx-lndq3b61TZpWOnYDI4F2CUFuxj5ECxrlIfm_tlv0TbErDTX311YsA_nhzNHYSaffRzx0OkmmUKSyyH8k9aPRKXUTUmY7Y9PLv3UCKEmAFHAnJkr5kZV08g3UMYG2blpryYBg82abEVMxeMUbh-T4M-Z9dcgQyiZ4nyNMUs1bbfH_2kAtqfEXmP_9eZ42Kwa2ixFWFZDcvOp775bjkYcGvwSnHqmyBXivONzTxyPN6Ug7uFCvMTbeo10ctgOFfXJUZfoxRt-hCspTPJR8C4TzIK41fiy19uRpGjeezG5Ghwy9upXsomunwB4knTAn1otmj4afIxw",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "undefined",
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "http://localhost:8443/pentests?projectId=8bc16303-f652-418a-b745-8a03d89356fb&category=INFORMATION_GATHERING",
|
"raw": "http://localhost:8443/pentests?projectId=5a4f126c-9471-43b8-80b9-6eb02b7c35d0&category=INFORMATION_GATHERING",
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
"host": [
|
"host": [
|
||||||
"localhost"
|
"localhost"
|
||||||
|
@ -276,7 +290,7 @@
|
||||||
"query": [
|
"query": [
|
||||||
{
|
{
|
||||||
"key": "projectId",
|
"key": "projectId",
|
||||||
"value": "8bc16303-f652-418a-b745-8a03d89356fb"
|
"value": "5a4f126c-9471-43b8-80b9-6eb02b7c35d0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "category",
|
"key": "category",
|
||||||
|
|
|
@ -12,8 +12,8 @@ data class Pentest(
|
||||||
val title: String,
|
val title: String,
|
||||||
val refNumber: String,
|
val refNumber: String,
|
||||||
val status: PentestStatus,
|
val status: PentestStatus,
|
||||||
val findingIds: String,
|
val findingIds: List<String> = emptyList(),
|
||||||
val commentIds: String
|
val commentIds: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Pentest.toPentestResponseBody(): ResponseBody {
|
fun Pentest.toPentestResponseBody(): ResponseBody {
|
||||||
|
|
|
@ -2,7 +2,10 @@ package com.securityc4po.api.project
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat
|
import com.fasterxml.jackson.annotation.JsonFormat
|
||||||
import com.securityc4po.api.ResponseBody
|
import com.securityc4po.api.ResponseBody
|
||||||
|
import com.securityc4po.api.pentest.PentestStatus
|
||||||
import org.springframework.data.mongodb.core.index.Indexed
|
import org.springframework.data.mongodb.core.index.Indexed
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.text.DecimalFormat
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
@ -14,6 +17,7 @@ data class Project(
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
|
||||||
val createdAt: String = Instant.now().toString(),
|
val createdAt: String = Instant.now().toString(),
|
||||||
val tester: String? = null,
|
val tester: String? = null,
|
||||||
|
val projectPentests: List<ProjectPentest> = emptyList(),
|
||||||
val createdBy: String
|
val createdBy: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,6 +28,7 @@ fun buildProject(body: ProjectRequestBody, projectEntity: ProjectEntity): Projec
|
||||||
title = body.title,
|
title = body.title,
|
||||||
createdAt = projectEntity.data.createdAt,
|
createdAt = projectEntity.data.createdAt,
|
||||||
tester = body.tester,
|
tester = body.tester,
|
||||||
|
projectPentests = projectEntity.data.projectPentests,
|
||||||
createdBy = projectEntity.data.createdBy
|
createdBy = projectEntity.data.createdBy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -35,6 +40,8 @@ fun Project.toProjectResponseBody(): ResponseBody {
|
||||||
"title" to title,
|
"title" to title,
|
||||||
"createdAt" to createdAt,
|
"createdAt" to createdAt,
|
||||||
"tester" to tester,
|
"tester" to tester,
|
||||||
|
/* ToDo: Calculate percentage in BE type: float */
|
||||||
|
"testingProgress" to calculateProgress(),
|
||||||
"createdBy" to createdBy
|
"createdBy" to createdBy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -45,16 +52,31 @@ fun Project.toProjectDeleteResponseBody(): ResponseBody {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Project.calculateProgress(): Float {
|
||||||
|
// 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
|
||||||
|
val TOTALPENTESTS = 95
|
||||||
|
|
||||||
|
return if (projectPentests.isEmpty())
|
||||||
|
0F
|
||||||
|
else {
|
||||||
|
var completedPentests = 0
|
||||||
|
projectPentests.forEach { projectPentest ->
|
||||||
|
if (projectPentest.status == PentestStatus.TRIAGED) {
|
||||||
|
completedPentests++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val df = DecimalFormat("#.##")
|
||||||
|
df.roundingMode = RoundingMode.DOWN
|
||||||
|
val progress = completedPentests / TOTALPENTESTS
|
||||||
|
df.format(progress).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class ProjectOverview(
|
data class ProjectOverview(
|
||||||
val projects: List<Project>
|
val projects: List<Project>
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ProjectOverview.toProjectOverviewResponseBody(): ResponseBody {
|
|
||||||
return mapOf(
|
|
||||||
"projects" to projects
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ProjectRequestBody(
|
data class ProjectRequestBody(
|
||||||
val client: String,
|
val client: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
|
|
|
@ -30,7 +30,8 @@ class ProjectController(private val projectService: ProjectService) {
|
||||||
it.toProjectResponseBody()
|
it.toProjectResponseBody()
|
||||||
}
|
}
|
||||||
}.map {
|
}.map {
|
||||||
ResponseEntity.ok(it)
|
if (it.isEmpty()) ResponseEntity.noContent().build()
|
||||||
|
else ResponseEntity.ok(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ fun ProjectEntity.toProject() : Project {
|
||||||
this.data.title,
|
this.data.title,
|
||||||
this.data.createdAt,
|
this.data.createdAt,
|
||||||
this.data.tester,
|
this.data.tester,
|
||||||
|
this.data.projectPentests,
|
||||||
this.data.createdBy
|
this.data.createdBy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.securityc4po.api.project
|
||||||
|
|
||||||
|
import com.securityc4po.api.pentest.PentestStatus
|
||||||
|
|
||||||
|
data class ProjectPentest(
|
||||||
|
val pentestId: String,
|
||||||
|
val status: PentestStatus
|
||||||
|
)
|
|
@ -102,8 +102,10 @@ class ProjectService(private val projectRepository: ProjectRepository) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return projectRepository.findProjectById(id).switchIfEmpty{
|
return projectRepository.findProjectById(id).switchIfEmpty{
|
||||||
logger.info("Project with id $id not found. Updating not possible.")
|
logger.warn("Project with id $id not found. Updating not possible.")
|
||||||
Mono.empty()
|
val msg = "Project with id $id not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.ProjectNotFound)
|
||||||
|
throw ex
|
||||||
}.flatMap{projectEntity: ProjectEntity ->
|
}.flatMap{projectEntity: ProjectEntity ->
|
||||||
projectEntity.lastModified = Instant.now()
|
projectEntity.lastModified = Instant.now()
|
||||||
projectEntity.data = buildProject(body, projectEntity)
|
projectEntity.data = buildProject(body, projectEntity)
|
||||||
|
@ -120,4 +122,15 @@ class ProjectService(private val projectRepository: ProjectRepository) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update testing progress of [Project]
|
||||||
|
*
|
||||||
|
* @throws [TransactionInterruptedException] if the [Project] could not be updated
|
||||||
|
* @return updated list of [ProjectPentest]s
|
||||||
|
*/
|
||||||
|
fun updateProjectTestingProgress(projectId: String, projectPentests: ProjectPentest)/*: Mono<List<ProjectPentest>>*/ {
|
||||||
|
// ToDo: update Project Entity with progress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,8 +97,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Search engine discovery/reconnaissance",
|
title = "Search engine discovery/reconnaissance",
|
||||||
refNumber = "OTG-INFO-001",
|
refNumber = "OTG-INFO-001",
|
||||||
status = PentestStatus.NOT_STARTED,
|
status = PentestStatus.NOT_STARTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
private val pentestTwo = Pentest(
|
private val pentestTwo = Pentest(
|
||||||
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
||||||
|
@ -107,8 +107,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Fingerprint Web Server",
|
title = "Fingerprint Web Server",
|
||||||
refNumber = "OTG-INFO-002",
|
refNumber = "OTG-INFO-002",
|
||||||
status = PentestStatus.REPORTED,
|
status = PentestStatus.REPORTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getProjectsResponse() = listOf(
|
private fun getProjectsResponse() = listOf(
|
||||||
|
@ -126,8 +126,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Search engine discovery/reconnaissance",
|
title = "Search engine discovery/reconnaissance",
|
||||||
refNumber = "OTG-INFO-001",
|
refNumber = "OTG-INFO-001",
|
||||||
status = PentestStatus.NOT_STARTED,
|
status = PentestStatus.NOT_STARTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
val pentestTwo = Pentest(
|
val pentestTwo = Pentest(
|
||||||
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
||||||
|
@ -136,8 +136,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Fingerprint Web Server",
|
title = "Fingerprint Web Server",
|
||||||
refNumber = "OTG-INFO-002",
|
refNumber = "OTG-INFO-002",
|
||||||
status = PentestStatus.REPORTED,
|
status = PentestStatus.REPORTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
val pentestThree = Pentest(
|
val pentestThree = Pentest(
|
||||||
id = "74eae112-f62c-11ec-b939-0242ac120002",
|
id = "74eae112-f62c-11ec-b939-0242ac120002",
|
||||||
|
@ -146,8 +146,8 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Testing for Credentials Transported over an Encrypted Channel",
|
title = "Testing for Credentials Transported over an Encrypted Channel",
|
||||||
refNumber = "OTG-AUTHN-001",
|
refNumber = "OTG-AUTHN-001",
|
||||||
status = PentestStatus.CHECKED,
|
status = PentestStatus.CHECKED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
// persist test data in database
|
// persist test data in database
|
||||||
mongoTemplate.save(PentestEntity(pentestOne))
|
mongoTemplate.save(PentestEntity(pentestOne))
|
||||||
|
|
|
@ -72,8 +72,8 @@ class PentestControllerIntTest : BaseIntTest() {
|
||||||
title = "Search engine discovery/reconnaissance",
|
title = "Search engine discovery/reconnaissance",
|
||||||
refNumber = "OTG-INFO-001",
|
refNumber = "OTG-INFO-001",
|
||||||
status = PentestStatus.NOT_STARTED,
|
status = PentestStatus.NOT_STARTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
private val pentestTwo = Pentest(
|
private val pentestTwo = Pentest(
|
||||||
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
||||||
|
@ -82,8 +82,8 @@ class PentestControllerIntTest : BaseIntTest() {
|
||||||
title = "Fingerprint Web Server",
|
title = "Fingerprint Web Server",
|
||||||
refNumber = "OTG-INFO-002",
|
refNumber = "OTG-INFO-002",
|
||||||
status = PentestStatus.REPORTED,
|
status = PentestStatus.REPORTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getPentests() = listOf(
|
private fun getPentests() = listOf(
|
||||||
|
@ -101,8 +101,8 @@ class PentestControllerIntTest : BaseIntTest() {
|
||||||
title = "Search engine discovery/reconnaissance",
|
title = "Search engine discovery/reconnaissance",
|
||||||
refNumber = "OTG-INFO-001",
|
refNumber = "OTG-INFO-001",
|
||||||
status = PentestStatus.NOT_STARTED,
|
status = PentestStatus.NOT_STARTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
val pentestTwo = Pentest(
|
val pentestTwo = Pentest(
|
||||||
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
id = "43fbc63c-f624-11ec-b939-0242ac120002",
|
||||||
|
@ -111,8 +111,8 @@ class PentestControllerIntTest : BaseIntTest() {
|
||||||
title = "Fingerprint Web Server",
|
title = "Fingerprint Web Server",
|
||||||
refNumber = "OTG-INFO-002",
|
refNumber = "OTG-INFO-002",
|
||||||
status = PentestStatus.REPORTED,
|
status = PentestStatus.REPORTED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
val pentestThree = Pentest(
|
val pentestThree = Pentest(
|
||||||
id = "74eae112-f62c-11ec-b939-0242ac120002",
|
id = "74eae112-f62c-11ec-b939-0242ac120002",
|
||||||
|
@ -121,8 +121,8 @@ class PentestControllerIntTest : BaseIntTest() {
|
||||||
title = "Testing for Credentials Transported over an Encrypted Channel",
|
title = "Testing for Credentials Transported over an Encrypted Channel",
|
||||||
refNumber = "OTG-AUTHN-001",
|
refNumber = "OTG-AUTHN-001",
|
||||||
status = PentestStatus.CHECKED,
|
status = PentestStatus.CHECKED,
|
||||||
findingIds = "",
|
findingIds = emptyList(),
|
||||||
commentIds = ""
|
commentIds = emptyList()
|
||||||
)
|
)
|
||||||
// persist test data in database
|
// persist test data in database
|
||||||
mongoTemplate.save(PentestEntity(pentestOne))
|
mongoTemplate.save(PentestEntity(pentestOne))
|
||||||
|
|
|
@ -86,6 +86,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Some Mock API (v1.0) Scanning",
|
title = "Some Mock API (v1.0) Scanning",
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
val projectTwo = Project(
|
val projectTwo = Project(
|
||||||
|
@ -94,6 +95,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "CashMyData (iOS)",
|
title = "CashMyData (iOS)",
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Elliot",
|
tester = "Elliot",
|
||||||
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -218,6 +220,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Some Mock API (v1.0) Scanning",
|
title = "Some Mock API (v1.0) Scanning",
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -268,6 +271,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "log4j Pentest_updated",
|
title = "log4j Pentest_updated",
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Stipe_updated",
|
tester = "Stipe_updated",
|
||||||
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -280,6 +284,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "Some Mock API (v1.0) Scanning",
|
title = "Some Mock API (v1.0) Scanning",
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
val projectTwo = Project(
|
val projectTwo = Project(
|
||||||
|
@ -288,6 +293,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
title = "CashMyData (iOS)",
|
title = "CashMyData (iOS)",
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Elliot",
|
tester = "Elliot",
|
||||||
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// persist test data in database
|
// persist test data in database
|
||||||
|
|
|
@ -1,55 +1,58 @@
|
||||||
[{
|
[{
|
||||||
"_id": {
|
"_id": {
|
||||||
"$oid": "62c4018f18f1f463ed1e11be"
|
"$oid": "62f3c50c7acde34f740ba737"
|
||||||
},
|
},
|
||||||
"lastModified": {
|
"lastModified": {
|
||||||
"$date": {
|
"$date": {
|
||||||
"$numberLong": "1657012623629"
|
"$numberLong": "1660142860140"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"_id": "41051d0a-63ef-4290-b984-e6fbd736f218",
|
"_id": "5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
|
||||||
"client": "E Corp",
|
"client": "E Corp",
|
||||||
"title": "Some Mock API (v1.0) Scanning",
|
"title": "Some Mock API (v1.0) Scanning",
|
||||||
"createdAt": "2022-07-05T09:17:03.629331Z",
|
"createdAt": "2022-08-10T14:47:40.140406Z",
|
||||||
"tester": "Novatester",
|
"tester": "Novatester",
|
||||||
"createdBy": "ca447a34-cac3-495e-9295-0a5bf5de502a"
|
"projectPentests": [],
|
||||||
|
"createdBy": "3c4ae87f-0d56-4634-a824-b4883c403c8a"
|
||||||
},
|
},
|
||||||
"_class": "com.securityc4po.api.project.ProjectEntity"
|
"_class": "com.securityc4po.api.project.ProjectEntity"
|
||||||
},{
|
},{
|
||||||
"_id": {
|
"_id": {
|
||||||
"$oid": "62c401b718f1f463ed1e11bf"
|
"$oid": "62f3c5317acde34f740ba738"
|
||||||
},
|
},
|
||||||
"lastModified": {
|
"lastModified": {
|
||||||
"$date": {
|
"$date": {
|
||||||
"$numberLong": "1657012663386"
|
"$numberLong": "1660142897912"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"_id": "0fa17ddb-7094-4f9c-b295-3433244c32c2",
|
"_id": "42b8c0df-b70e-4526-8ed0-7c022195fe85",
|
||||||
"client": "Allsafe",
|
"client": "Allsafe",
|
||||||
"title": "CashMyData (iOS)",
|
"title": "CashMyData (iOS)",
|
||||||
"createdAt": "2022-07-05T09:17:43.386782Z",
|
"createdAt": "2022-08-10T14:48:17.912592Z",
|
||||||
"tester": "Elliot",
|
"tester": "Elliot",
|
||||||
"createdBy": "1ac9753a-5a62-4bf7-8412-6fde779ea33a"
|
"projectPentests": [],
|
||||||
|
"createdBy": "6740ad72-8f42-486a-bcf3-e057e6afb0de"
|
||||||
},
|
},
|
||||||
"_class": "com.securityc4po.api.project.ProjectEntity"
|
"_class": "com.securityc4po.api.project.ProjectEntity"
|
||||||
},{
|
},{
|
||||||
"_id": {
|
"_id": {
|
||||||
"$oid": "62c401e318f1f463ed1e11c0"
|
"$oid": "62f3c5427acde34f740ba739"
|
||||||
},
|
},
|
||||||
"lastModified": {
|
"lastModified": {
|
||||||
"$date": {
|
"$date": {
|
||||||
"$numberLong": "1657012707557"
|
"$numberLong": "1660142914204"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"_id": "85aa8d79-5899-4b68-894c-d07f3b168cd4",
|
"_id": "1120bfa1-0d2b-4e42-a209-0289a1256266",
|
||||||
"client": "Novatec",
|
"client": "Novatec",
|
||||||
"title": "Openspace log4J",
|
"title": "Openspace log4J",
|
||||||
"createdAt": "2022-07-05T09:18:27.557740Z",
|
"createdAt": "2022-08-10T14:48:34.204234Z",
|
||||||
"tester": "mhg",
|
"tester": "mhg",
|
||||||
"createdBy": "3201f6f8-10a4-4826-9b49-b6bdef28b152"
|
"projectPentests": [],
|
||||||
|
"createdBy": "5a4a8032-0726-4851-a105-9f079c3989b9"
|
||||||
},
|
},
|
||||||
"_class": "com.securityc4po.api.project.ProjectEntity"
|
"_class": "com.securityc4po.api.project.ProjectEntity"
|
||||||
}]
|
}]
|
Loading…
Reference in New Issue