feat: As a developer, I want to get findings by pentestId
This commit is contained in:
parent
b2f430f9fd
commit
75d91391ab
|
@ -64,7 +64,7 @@ export class PentestContentComponent implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.store.selectOnce(ProjectState.pentest).pipe(
|
this.store.select(ProjectState.pentest).pipe(
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (selectedPentest: Pentest) => {
|
next: (selectedPentest: Pentest) => {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-severity">
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-severity">
|
||||||
{{ 'finding.severity' | translate }}
|
{{ 'finding.severity' | translate }}
|
||||||
</th>
|
</th>
|
||||||
<td nbTreeGridCell *nbTreeGridCellDef="let finding" class="cell-severity" fxFill fxLayoutAlign="center none">
|
<td nbTreeGridCell *nbTreeGridCellDef="let finding" class="cell-severity border-style" fxFill fxLayoutAlign="center center">
|
||||||
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
|
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
.finding-cell {
|
.finding-cell {
|
||||||
// Add style here
|
// Add style here
|
||||||
|
height: 4.5rem !important;
|
||||||
|
max-height: 4.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.finding-cell:hover {
|
.finding-cell:hover {
|
||||||
|
@ -16,6 +18,13 @@
|
||||||
.cell-severity {
|
.cell-severity {
|
||||||
width: 125px;
|
width: 125px;
|
||||||
max-width: 125px;
|
max-width: 125px;
|
||||||
|
// border-style: none;
|
||||||
|
height: 4.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-style {
|
||||||
|
border-top-style: none;
|
||||||
|
border-left-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-actions {
|
.cell-actions {
|
||||||
|
|
|
@ -91,6 +91,7 @@ describe('PentestFindingsComponent', () => {
|
||||||
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
|
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
|
||||||
});
|
});
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
component.pentestInfo$.next(DESIRED_PROJECT_STATE_SESSION.selectedPentest);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
filter(value => !!value),
|
||||||
tap((value) => console.warn('FindingDialogBody: ', value)),
|
/* tap((value) => console.warn('FindingDialogBody: ', value))*/
|
||||||
mergeMap((value: FindingDialogBody) =>
|
mergeMap((value: FindingDialogBody) =>
|
||||||
this.pentestService.saveFinding(
|
this.pentestService.saveFinding(
|
||||||
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
||||||
|
@ -105,6 +105,7 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
|
// ToDo: Parse new Counter to overview / -> dispatch to store maybe already update it
|
||||||
this.loadFindingsData();
|
this.loadFindingsData();
|
||||||
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
|
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import {v4 as UUID} from 'uuid';
|
import {v4 as UUID} from 'uuid';
|
||||||
import {Severity} from '@shared/models/severity.enum';
|
import {Severity} from '@shared/models/severity.enum';
|
||||||
import {Category} from '@shared/models/category.model';
|
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
|
||||||
|
|
||||||
export class Finding {
|
export class Finding {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -47,13 +45,13 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin
|
||||||
findings.forEach((value: Finding) => {
|
findings.forEach((value: Finding) => {
|
||||||
findingEntries.push({
|
findingEntries.push({
|
||||||
findingId: value.id,
|
findingId: value.id,
|
||||||
severity: value.severity,
|
severity: typeof value.severity !== 'number' ? Severity[value.severity] : value.severity,
|
||||||
title: value.title,
|
title: value.title,
|
||||||
impact: value.impact,
|
impact: value.impact,
|
||||||
kind: 'cell',
|
kind: 'cell',
|
||||||
childEntries: null,
|
childEntries: null,
|
||||||
expanded: false
|
expanded: false
|
||||||
} as FindingEntry);
|
} as unknown as FindingEntry);
|
||||||
});
|
});
|
||||||
return findingEntries;
|
return findingEntries;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@
|
||||||
{{formArray[1].labelKey | translate}}
|
{{formArray[1].labelKey | translate}}
|
||||||
</label>
|
</label>
|
||||||
<nb-select class="severities" placeholder="{{formArray[1].placeholder | translate}} *"
|
<nb-select class="severities" placeholder="{{formArray[1].placeholder | translate}} *"
|
||||||
|
type="severity-select"
|
||||||
[(selected)]="formArray[1].controlsConfig[0].value"
|
[(selected)]="formArray[1].controlsConfig[0].value"
|
||||||
shape="round" status="{{getSeverityFillStatus(formArray[1].controlsConfig[0].value)}}" filled>
|
shape="round" status="{{getSeverityFillStatus(formArray[1].controlsConfig[0].value)}}" filled>
|
||||||
<nb-option *ngFor="let severity of severityTexts" [value]="severity.value">
|
<nb-option *ngFor="let severity of severityTexts" [value]="severity.value">
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class FindingDialogComponent implements OnInit {
|
||||||
onClickSave(value: any): void {
|
onClickSave(value: any): void {
|
||||||
this.dialogRef.close({
|
this.dialogRef.close({
|
||||||
title: value.findingTitle,
|
title: value.findingTitle,
|
||||||
severity: value.findingSeverity,
|
severity: this.formArray[1].controlsConfig[0].value,
|
||||||
description: value.findingDescription,
|
description: value.findingDescription,
|
||||||
impact: value.findingImpact,
|
impact: value.findingImpact,
|
||||||
affectedUrls: this.affectedUrls ? this.affectedUrls : [],
|
affectedUrls: this.affectedUrls ? this.affectedUrls : [],
|
||||||
|
|
|
@ -85,7 +85,6 @@ export class PentestService {
|
||||||
* @param pentestId the id of the project
|
* @param pentestId the id of the project
|
||||||
*/
|
*/
|
||||||
public getFindingsByPentestId(pentestId: string): Observable<Finding[]> {
|
public getFindingsByPentestId(pentestId: string): Observable<Finding[]> {
|
||||||
console.warn('Findings for:', pentestId);
|
|
||||||
if (pentestId) {
|
if (pentestId) {
|
||||||
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
|
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,7 +133,6 @@ export class PentestService {
|
||||||
* @param finding the information of the finding
|
* @param finding the information of the finding
|
||||||
*/
|
*/
|
||||||
public saveFinding(pentestId: string, finding: Finding): Observable<Finding> {
|
public saveFinding(pentestId: string, finding: Finding): Observable<Finding> {
|
||||||
console.warn('Finding: ', finding);
|
|
||||||
return this.http.post<Finding>(`${this.apiBaseURL}/${pentestId}/finding`, finding);
|
return this.http.post<Finding>(`${this.apiBaseURL}/${pentestId}/finding`, finding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@
|
||||||
"name": "pentests",
|
"name": "pentests",
|
||||||
"item": [
|
"item": [
|
||||||
{
|
{
|
||||||
"name": "Finding",
|
"name": "findings",
|
||||||
"item": [
|
"item": [
|
||||||
{
|
{
|
||||||
"name": "saveFinding",
|
"name": "saveFinding",
|
||||||
|
@ -304,6 +304,41 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getFindingsForPentesId",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2Njg0MzUzNDAsImlhdCI6MTY2ODQzNTA0MCwianRpIjoiMTRiYjYyNTUtMTc5Zi00MTkyLWFmNGMtYjdiNTc3NTVmNmIxIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiIyYmI2NTU5Yi04MThlLTQxNjgtOGE5Yy1lYmNlZjVmN2M4NjUiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.DQRSUcwW4Im2wxp9t8Jm4rFsS3ZFydGsNEEZ0-yfoq0B46kgLD_dOfLzVCkhZfZHsbmFIZv704j_dzUDafqtzVilUV5LM5LCqKs0ByRYB9WA-wXKiRsbKfob_OnwlVrXu2ull2_7o4SXgTnF50yyAONkzegfP-I4cJko0yeKDmeYdWrZpwHJcDtZjZl6rZbQk3BLbICcNMO6F57LtU6tHfFIIxrvlbKGqA49PH7S6n5grTNoA9_fzHnn46DJvsRw0RtzFR-QTrCy3HNdPeClgXYJvSudvwUIuaKjbfpUNU3BzGSBOjvlDpWqkbuiUX1COhJbk83PQk8-mPoltiGSFA",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "undefined",
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:8443/pentests/11601f51-bc17-47fd-847d-0c53df5405b5/findings",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"pentests",
|
||||||
|
"11601f51-bc17-47fd-847d-0c53df5405b5",
|
||||||
|
"findings"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,8 @@ enum class Errorcode(val code: Int) {
|
||||||
ProjectsNotFound(1001),
|
ProjectsNotFound(1001),
|
||||||
ProjectNotFound(1002),
|
ProjectNotFound(1002),
|
||||||
PentestNotFound(1003),
|
PentestNotFound(1003),
|
||||||
|
FindingsNotFound(1004),
|
||||||
|
FindingNotFound(1005),
|
||||||
|
|
||||||
// 2XXX Already Changed
|
// 2XXX Already Changed
|
||||||
ProjectAlreadyChanged(2001),
|
ProjectAlreadyChanged(2001),
|
||||||
|
|
|
@ -29,6 +29,7 @@ data class FindingRequestBody(
|
||||||
fun Finding.toFindingResponseBody(): ResponseBody {
|
fun Finding.toFindingResponseBody(): ResponseBody {
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"id" to id,
|
"id" to id,
|
||||||
|
"severity" to severity,
|
||||||
"title" to title,
|
"title" to title,
|
||||||
"description" to description,
|
"description" to description,
|
||||||
"impact" to impact,
|
"impact" to impact,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.securityc4po.api.finding
|
||||||
import org.springframework.data.mongodb.repository.Query
|
import org.springframework.data.mongodb.repository.Query
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -10,4 +11,7 @@ interface FindingRepository : ReactiveMongoRepository<FindingEntity, String> {
|
||||||
|
|
||||||
@Query("{'data._id' : ?0}")
|
@Query("{'data._id' : ?0}")
|
||||||
fun findFindingById(id: String): Mono<FindingEntity>
|
fun findFindingById(id: String): Mono<FindingEntity>
|
||||||
|
|
||||||
|
@Query("{'data._id' :{\$in: ?0 }}")
|
||||||
|
fun findFindingsByIds(id: List<String>): Flux<FindingEntity>
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.securityc4po.api.pentest.PentestService
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.kotlin.core.publisher.switchIfEmpty
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
||||||
|
@ -55,4 +56,34 @@ class FindingService(private val findingRepository: FindingRepository, private v
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get [Finding] by findingId
|
||||||
|
*
|
||||||
|
* @return of [Finding]
|
||||||
|
*/
|
||||||
|
fun getFindingById(findingId: String): Mono<Finding> {
|
||||||
|
return this.findingRepository.findFindingById(findingId).switchIfEmpty {
|
||||||
|
logger.warn("Finding with id $findingId not found.")
|
||||||
|
val msg = "Finding with id $findingId not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.FindingNotFound)
|
||||||
|
throw ex
|
||||||
|
}.map { it.toFinding() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all [Finding]s by findingsId's
|
||||||
|
*
|
||||||
|
* @return list of [Finding]s
|
||||||
|
*/
|
||||||
|
fun getFindingsByIds(findingIds: List<String>): Mono<List<Finding>> {
|
||||||
|
return findingRepository.findFindingsByIds(findingIds).collectList().map {
|
||||||
|
it.map { findingEntity -> findingEntity.toFinding() }
|
||||||
|
}.switchIfEmpty {
|
||||||
|
val msg = "Findings not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.FindingsNotFound)
|
||||||
|
logger.warn(msg, ex)
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -82,4 +82,17 @@ class PentestController(private val pentestService: PentestService, private val
|
||||||
ResponseEntity.accepted().body(it.toFindingResponseBody())
|
ResponseEntity.accepted().body(it.toFindingResponseBody())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// ToDo: Add Documentation & Tests
|
||||||
|
@GetMapping("/{pentestId}/findings")
|
||||||
|
fun getFindings(@PathVariable(value = "pentestId") pentestId: String): Mono<ResponseEntity<List<ResponseBody>>> {
|
||||||
|
return this.pentestService.getFindingIdsByPentestId(pentestId).flatMap { findingIds: List<String> ->
|
||||||
|
this.findingService.getFindingsByIds(findingIds).map { findingList ->
|
||||||
|
findingList.map { it.toFindingResponseBody() }
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
if (it.isEmpty()) ResponseEntity.noContent().build()
|
||||||
|
else ResponseEntity.ok(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,12 @@ class PentestService(private val pentestRepository: PentestRepository, private v
|
||||||
* @return list of [Pentest]
|
* @return list of [Pentest]
|
||||||
*/
|
*/
|
||||||
fun getPentestsForCategory(projectId: String, category: PentestCategory): Mono<List<Pentest>> {
|
fun getPentestsForCategory(projectId: String, category: PentestCategory): Mono<List<Pentest>> {
|
||||||
return pentestRepository.findPentestByProjectIdAndCategory(projectId, category).collectList().map {
|
return pentestRepository.findPentestByProjectIdAndCategory(projectId, category)/*.switchIfEmpty {
|
||||||
|
logger.warn("Pentests for project id $projectId not found. Collecting pentests not possible.")
|
||||||
|
val msg = "Pentests for project id $projectId not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound)
|
||||||
|
throw ex
|
||||||
|
}*/.collectList().map {
|
||||||
it.map { pentestEntity -> pentestEntity.toPentest() }
|
it.map { pentestEntity -> pentestEntity.toPentest() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,4 +153,18 @@ class PentestService(private val pentestRepository: PentestRepository, private v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all [Finding]Id's by pentestId
|
||||||
|
*
|
||||||
|
* @return list of [String]
|
||||||
|
*/
|
||||||
|
fun getFindingIdsByPentestId(pentestId: String): Mono<List<String>> {
|
||||||
|
return this.pentestRepository.findPentestById(pentestId).switchIfEmpty {
|
||||||
|
logger.warn("Pentest with id $pentestId not found. Collecting findings not possible.")
|
||||||
|
val msg = "Pentest with id $pentestId not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound)
|
||||||
|
throw ex
|
||||||
|
}.map { pentestEntity -> pentestEntity.data.findingIds }
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ class ProjectService(private val projectRepository: ProjectRepository) {
|
||||||
return projectRepository.findAll().collectList().map {
|
return projectRepository.findAll().collectList().map {
|
||||||
it.map { projectEntity -> projectEntity.toProject() }
|
it.map { projectEntity -> projectEntity.toProject() }
|
||||||
}.switchIfEmpty {
|
}.switchIfEmpty {
|
||||||
val msg = "No projects not found."
|
val msg = "Projects not found."
|
||||||
val ex = EntityNotFoundException(msg, Errorcode.ProjectsNotFound)
|
val ex = EntityNotFoundException(msg, Errorcode.ProjectsNotFound)
|
||||||
logger.warn(msg, ex)
|
logger.warn(msg, ex)
|
||||||
throw ex
|
throw ex
|
||||||
|
|
Loading…
Reference in New Issue