feat: As a developer, I want to get findings by pentestId

This commit is contained in:
Marcel Haag 2022-11-14 16:12:21 +01:00
parent b2f430f9fd
commit 75d91391ab
17 changed files with 127 additions and 14 deletions

View File

@ -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) => {

View File

@ -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>

View File

@ -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 {

View File

@ -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();
}); });

View File

@ -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);
}, },

View File

@ -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;
} }

View File

@ -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">

View File

@ -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 : [],

View File

@ -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);
} }

View File

@ -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": []
} }
] ]
}, },

View File

@ -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),

View File

@ -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,

View File

@ -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>
} }

View File

@ -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
}
}
} }

View File

@ -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)
}
}
}

View File

@ -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 }
}
} }

View File

@ -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