feat: As a user I want to get project data by projectId that is filled with all the necessary data for report creation

This commit is contained in:
Marcel Haag 2023-02-17 09:13:08 +01:00
parent 280948c470
commit 88c252dd7e
32 changed files with 434 additions and 136 deletions

View File

@ -20,7 +20,7 @@
(click)="onClickEditPentestProject()">
<fa-icon [icon]="fa.faEdit"
class="element-icon fa-lg"></fa-icon>
<span class="element-text">{{ 'global.action.edit' | translate }}</span>
<!--<span class="element-text">{{ 'global.action.edit' | translate }}</span>-->
</button>
</nb-action>
<nb-action>

View File

@ -71,7 +71,7 @@
</nb-form-field>
</div>
<!-- Severity Layout -->
<!-- Severity Form Field -->
<!-- Severity Dropdown -->
<div fxFlex class="severity-dialog">
<label for="{{formArray[1].fieldName}}" class="label">
{{formArray[1].labelKey | translate}}

View File

@ -78,6 +78,7 @@
}
.severity-0 {
background-color: nb-theme(color-success-default);
}
.severity-1 {
background-color: nb-theme(color-info-default);

View File

@ -110,7 +110,7 @@ export class FindingDialogComponent implements OnInit {
let severityFillStatus;
switch (value) {
case 0: {
severityFillStatus = 'basic';
severityFillStatus = 'success';
break;
}
case 1: {
@ -126,7 +126,7 @@ export class FindingDialogComponent implements OnInit {
break;
}
default: {
severityFillStatus = 'control';
severityFillStatus = 'basic';
break;
}
}

View File

@ -3,7 +3,7 @@
.project-dialog {
width: 40rem !important;
height: 42.25rem;
height: 42.5rem;
.project-dialog-header {
height: 8vh;
@ -32,6 +32,13 @@
height: 8rem;
}
.form-textarea:disabled {
width: 26.75rem !important;
// width: 30rem !important;
background-color: nb-theme(color-basic-transparent-focus);
height: 8rem;
}
.error-text {
float: left;
color: nb-theme(color-danger-default);

View File

@ -1,6 +1,6 @@
<ng-container [ngSwitch]="currentSeverity">
<nb-tag-list>
<nb-tag *ngSwitchCase="severity.LOW" status="basic" appearance="filled"
<nb-tag *ngSwitchCase="severity.LOW" status="success" appearance="filled"
text="{{getTranslationKey() | translate}}"></nb-tag>
<nb-tag *ngSwitchCase="severity.MEDIUM" status="info" appearance="filled"
text=" {{getTranslationKey() | translate}}"></nb-tag>

View File

@ -53,6 +53,40 @@
},
"response": []
},
{
"name": "getProjectById",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzY5NzMxMTAsImlhdCI6MTY3Njk3MjgxMCwianRpIjoiNDFkMDAwNzEtNjAyYy00NmEzLThjYjctMTJlZTExYWYyZDBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI5MTA5ZWU0Ni03OGEzLTRmMDUtODdhYi03NzIxNGJmNzNlZWMiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiOTEwOWVlNDYtNzhhMy00ZjA1LTg3YWItNzcyMTRiZjczZWVjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.hZhUBi4cGQdn3lZ1Xm1Kz2WpboiBBJCFrtODD_c4N0ZiymB0MWVc1jXzU1fQ25mZ_I9VJXqg97x_gnCM7mKJrncFxs6cj75zIeH3so1BhlcDf7q2pjIkCH1yCerPWSLtrK2pWyxTr1GyO1Cp_wqQms_7_rmpzajLzmqBGKF8vd4yAk8kHmBoGBJhRU_gVCsDnIe74in3a032---IgCJ2XA0E5yxP9oBe6_9xPuCsk82YDihbfK1ZEO-9YZt0g1Iv3y30-hG10eflftWJEMSi8Bso4H_2WSJLqy4YRuGQR0EKDiomM0deVCK9IkuaoIsdIZ8kd65YuxSnj-_ue17QTA",
"type": "string"
},
{
"key": "undefined",
"type": "any"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:8443/projects/5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
"protocol": "http",
"host": [
"localhost"
],
"port": "8443",
"path": [
"projects",
"5a4f126c-9471-43b8-80b9-6eb02b7c35d0"
]
}
},
"response": []
},
{
"name": "saveProject",
"request": {
@ -699,6 +733,40 @@
},
"response": []
},
{
"name": "getCompletedPentestById",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzY5Nzk1NjYsImlhdCI6MTY3Njk3OTI2NiwianRpIjoiNzZlYTI3N2MtNmNiMC00OGQyLTgxNzktMDEyZDE2MWU5M2EzIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI0ZWI0NjQwYi00MmEyLTQwZDQtOGY0Yy1lZDViN2FkNTM3YjciLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiNGViNDY0MGItNDJhMi00MGQ0LThmNGMtZWQ1YjdhZDUzN2I3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.utN710Mq5OBUb3FBktvOZSbvRV4Mgsiel0EiNt-1hcgg_J83e_Gc2Szk1L4KZteFPjpWcvwrj_J9JdyECu2H23TbDVvtx9yU3CIFikWjwAH75YK08h0Sqv6aGZza0t7fqMe1PIeN70bV_cIvWGA6OdwwGmE3mrllBcCHrnq7_45APig7jT6-tJplt8Lf2zTf9sMm5PhvqtPL78rLdtgnLXstxr1kAKsbcc8RKOPh65ve1sBMNH8uXgCER8JDPngam5Jt-iZ6m6g_Bmy6RJWFApfFj1mE1dGoVQCsUhGkWZnApIy0PDFGs6UtsNM0pLOxuG9vQjeeq6R_IAq9Zo_3eA",
"type": "string"
},
{
"key": "undefined",
"type": "any"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:8443/pentests/11601f51-bc17-47fd-847d-0c53df5405b5",
"protocol": "http",
"host": [
"localhost"
],
"port": "8443",
"path": [
"pentests",
"11601f51-bc17-47fd-847d-0c53df5405b5"
]
}
},
"response": []
},
{
"name": "savePentest",
"request": {

View File

@ -1,6 +1,8 @@
package com.securityc4po.api.pentest
import com.securityc4po.api.ResponseBody
import com.securityc4po.api.pentest.comment.Comment
import com.securityc4po.api.pentest.finding.Finding
import org.springframework.data.mongodb.core.index.Indexed
import java.util.UUID
@ -51,6 +53,28 @@ fun Pentest.toPentestResponseBody(): ResponseBody {
)
}
data class CompletedPentest(
val id: String,
val projectId: String,
val category: PentestCategory,
val refNumber: String,
val status: PentestStatus,
var findings: MutableList<Finding>,
var comments: MutableList<Comment>
)
fun CompletedPentest.toCompletedPentestResponseBody(): ResponseBody {
return mapOf(
"id" to id,
"projectId" to projectId,
"category" to category,
"refNumber" to refNumber,
"status" to status,
"findings" to findings,
"comments" to comments
)
}
data class PentestRequestBody(
val projectId: String,
val refNumber: String,

View File

@ -4,6 +4,8 @@ import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.extensions.getLoggerFor
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import com.securityc4po.api.ResponseBody
import com.securityc4po.api.pentest.comment.CommentService
import com.securityc4po.api.pentest.finding.FindingService
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.noContent
import org.springframework.web.bind.annotation.*
@ -18,7 +20,7 @@ import reactor.core.publisher.Mono
methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST, RequestMethod.PATCH]
)
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION)
class PentestController(private val pentestService: PentestService) {
class PentestController(private val pentestService: PentestService, private val pentestReportService: PentestReportService) {
var logger = getLoggerFor<PentestController>()
@ -37,15 +39,14 @@ class PentestController(private val pentestService: PentestService) {
}
}
/* Todo: Add API
@GetMapping
fun getPentestById(
@RequestParam("pentestId") pentestId: String
): Mono<ResponseEntity<List<ResponseBody>>> {
return pentestService.getPentest(pentestId).map {
ResponseEntity.ok(it)
@GetMapping("/{pentestId}")
fun getCompletedPentestById(
@PathVariable(value = "pentestId") pentestId: String
): Mono<ResponseEntity<ResponseBody>> {
return pentestReportService.getCompletedPentest(pentestId).map {
ResponseEntity.ok(it.toCompletedPentestResponseBody())
}
}
}*/
@PostMapping("/{projectId}")
fun savePentest(

View File

@ -3,6 +3,8 @@ package com.securityc4po.api.pentest
import com.securityc4po.api.BaseEntity
import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.pentest.comment.Comment
import com.securityc4po.api.pentest.finding.Finding
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.springframework.data.mongodb.core.mapping.Document
@ -23,6 +25,18 @@ fun PentestEntity.toPentest(): Pentest {
)
}
fun PentestEntity.toCompletedPentest(): CompletedPentest {
return CompletedPentest(
this.data.id,
this.data.projectId,
this.data.category,
this.data.refNumber,
this.data.status,
mutableListOf<Finding>(),
mutableListOf<Comment>()
)
}
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
fun List<PentestEntity>.toPentests(): List<Pentest> {
return this.map {

View File

@ -0,0 +1,54 @@
package com.securityc4po.api.pentest
import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.configuration.error.handler.EntityNotFoundException
import com.securityc4po.api.configuration.error.handler.Errorcode
import com.securityc4po.api.extensions.getLoggerFor
import com.securityc4po.api.pentest.comment.CommentService
import com.securityc4po.api.pentest.finding.Finding
import com.securityc4po.api.pentest.finding.FindingService
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import reactor.kotlin.core.publisher.switchIfEmpty
import reactor.kotlin.core.publisher.toMono
@Service
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
class PentestReportService(
private val pentestRepository: PentestRepository,
private val findingService: FindingService,
private val commentService: CommentService
) {
var logger = getLoggerFor<PentestReportService>()
/**
* Get [CompletedPentest]s by pentestId
*
* @return [CompletedPentest]
*/
fun getCompletedPentest(pentestId: String): Mono<CompletedPentest> {
return pentestRepository.findPentestById(pentestId).publishOn(Schedulers.boundedElastic())
.flatMap { pentestEntity ->
val completedPentest = pentestEntity.toCompletedPentest()
// Add all findings to completed Pentest
this.findingService.getFindingsByIds(pentestEntity.data.findingIds).flatMap { listOfFindings ->
completedPentest.findings.addAll(listOfFindings)
// Add all comments to completed Pentest
this.commentService.getCommentsByIds(pentestEntity.data.commentIds).map { listOfComments ->
completedPentest.comments.addAll(listOfComments)
// Return completed Pentest
return@map completedPentest
}
}
}.switchIfEmpty {
logger.warn("Pentest for id $pentestId not found. Collecting pentest information not possible.")
val msg = "Pentest for id $pentestId not found."
val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound)
throw ex
}
}
}

View File

@ -6,6 +6,8 @@ import com.securityc4po.api.configuration.error.handler.*
import com.securityc4po.api.configuration.error.handler.InvalidModelException
import com.securityc4po.api.configuration.error.handler.TransactionInterruptedException
import com.securityc4po.api.extensions.getLoggerFor
import com.securityc4po.api.pentest.comment.CommentService
import com.securityc4po.api.pentest.finding.FindingService
import com.securityc4po.api.project.*
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.springframework.stereotype.Service
@ -15,7 +17,10 @@ import java.time.Instant
@Service
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
class PentestService(private val pentestRepository: PentestRepository, private val projectService: ProjectService) {
class PentestService(
private val pentestRepository: PentestRepository,
private val projectService: ProjectService
) {
var logger = getLoggerFor<PentestService>()
@ -25,12 +30,7 @@ class PentestService(private val pentestRepository: PentestRepository, private v
* @return list of [Pentest]
*/
fun getPentestsForCategory(projectId: String, category: PentestCategory): Mono<List<Pentest>> {
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 {
return pentestRepository.findPentestByProjectIdAndCategory(projectId, category).collectList().map {
it.map { pentestEntity -> pentestEntity.toPentest() }
}
}
@ -98,7 +98,7 @@ class PentestService(private val pentestRepository: PentestRepository, private v
}.flatMap { currentPentestEntity: PentestEntity ->
currentPentestEntity.lastModified = Instant.now()
currentPentestEntity.data = buildPentest(body, currentPentestEntity)
pentestRepository.save(currentPentestEntity).flatMap {newPentestEntity: PentestEntity ->
pentestRepository.save(currentPentestEntity).flatMap { newPentestEntity: PentestEntity ->
val pentest = newPentestEntity.toPentest()
// After successfully saving pentest add id and status to project
val projectPentest = ProjectPentest(pentestId = pentest.id, status = pentest.status)
@ -122,6 +122,34 @@ 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 }
}
/**
* Get all [Comment]Id's by pentestId
*
* @return list of [String]
*/
fun getCommentIdsByPentestId(pentestId: String): Mono<List<String>> {
return this.pentestRepository.findPentestById(pentestId).switchIfEmpty {
logger.warn("Pentest with id $pentestId not found. Collecting comments not possible.")
val msg = "Pentest with id $pentestId not found."
val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound)
throw ex
}.map { pentestEntity -> pentestEntity.data.commentIds }
}
/**
* Update [Pentest] for Finding
*
@ -188,20 +216,6 @@ 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 }
}
/**
* Update [Pentest] for Comment
*
@ -234,20 +248,6 @@ class PentestService(private val pentestRepository: PentestRepository, private v
}
}
/**
* Get all [Comment]Id's by pentestId
*
* @return list of [String]
*/
fun getCommentIdsByPentestId(pentestId: String): Mono<List<String>> {
return this.pentestRepository.findPentestById(pentestId).switchIfEmpty {
logger.warn("Pentest with id $pentestId not found. Collecting comments not possible.")
val msg = "Pentest with id $pentestId not found."
val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound)
throw ex
}.map { pentestEntity -> pentestEntity.data.commentIds }
}
/**
* Update [Pentest] for Comment
*

View File

@ -2,7 +2,10 @@ package com.securityc4po.api.project
import com.fasterxml.jackson.annotation.JsonFormat
import com.securityc4po.api.ResponseBody
import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.configuration.MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.pentest.PentestStatus
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.springframework.data.mongodb.core.index.Indexed
import java.math.BigDecimal
import java.math.RoundingMode
@ -49,6 +52,20 @@ fun Project.toProjectResponseBody(): ResponseBody {
)
}
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
fun Project.toProjectCompletedPentestResponseBody(): ResponseBody {
return mapOf(
"id" to id,
"client" to client,
"title" to title,
"createdAt" to createdAt,
"tester" to tester,
"summary" to summary,
"projectPentests" to projectPentests.filter { pentest -> pentest.status == PentestStatus.COMPLETED },
"createdBy" to createdBy
)
}
fun Project.toProjectDeleteResponseBody(): ResponseBody {
return mapOf(
"id" to id

View File

@ -34,7 +34,17 @@ class ProjectController(private val projectService: ProjectService) {
}
}
// ToDo: Add getProjectReportDataById Endpoint with return type ProjectReport
@GetMapping("/{projectId}")
fun getProjectById(
@PathVariable(value = "projectId") projectId: String
): Mono<ResponseEntity<ResponseBody>> {
return projectService.getProjectById(projectId).map {
it.toProjectCompletedPentestResponseBody()
}.map {
if (it.isEmpty()) ResponseEntity.noContent().build()
else ResponseEntity.ok(it)
}
}
@PostMapping
fun saveProject(

View File

@ -35,6 +35,23 @@ class ProjectService(private val projectRepository: ProjectRepository) {
}
}
/**
* Get [Project] by id
*
* @throws [EntityNotFoundException] if there is no [Project] in collection
* @return [Project]
*/
fun getProjectById(projectId: String): Mono<Project> {
return projectRepository.findProjectById(projectId).map {
it.toProject()
}.switchIfEmpty {
val msg = "Project not found."
val ex = EntityNotFoundException(msg, Errorcode.ProjectNotFound)
logger.warn(msg, ex)
throw ex
}
}
/**
* Save [Project]
*

View File

@ -101,6 +101,20 @@
{
"name": "getReportPDFforProjectById",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzY5ODY0NTgsImlhdCI6MTY3Njk4NjE1OCwianRpIjoiMzk2NjcwM2UtMTk4My00MWU4LTkyNjAtYjhmNDExOWRmYzYwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiIxZDBmMTk4YS03MTg5LTQ4ZmItYWQ0Ny00NTllNjVkOWU3ZjciLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiMWQwZjE5OGEtNzE4OS00OGZiLWFkNDctNDU5ZTY1ZDllN2Y3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.m1aOgFK_8ADYS6EdPLWT_wQNTsptSM9XipQQcttSd1lZKdZu6FgFVRSsW39fdgVLGurKxaglGoNgsywGDeomPS7hJuowzmYIEoDZr13MXCBqX9-YAsEDbwvrGcUI4jVXwKl8E-rTLpl3c5Ckj4tbDeUD3EuLk7yTYbkUnijqwsFlZpwJ_gbjZAfNiZCZpJlvh95cQKvFBbyzP7sfxkYikzpxY-1UHSUpoHBxJaOcJ6hoh-PIQVUw-8mvuoOyd5dMmavl5njvijr716_2loj6B6YHLueHIGlenI5Aob9DEOcL17SXivvcEyM5xTKyfqx3Jt1XY7jQ8mOIT4_iqxTc_w",
"type": "string"
},
{
"key": "undefined",
"type": "any"
}
]
},
"method": "GET",
"header": [
{
@ -110,7 +124,7 @@
}
],
"url": {
"raw": "http://localhost:8444/reports/195809ed-9722-4ad5-a84b-0099a9a01652/pdf",
"raw": "http://localhost:8444/reports/5a4f126c-9471-43b8-80b9-6eb02b7c35d0/pdf",
"protocol": "http",
"host": [
"localhost"
@ -118,7 +132,7 @@
"port": "8444",
"path": [
"reports",
"195809ed-9722-4ad5-a84b-0099a9a01652",
"5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
"pdf"
]
}

View File

@ -5,7 +5,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import java.util.stream.Collectors
class Appuser internal constructor() : UserDetails {
class Appuser internal constructor(sub: String, username: String, val token: String) : UserDetails {
override fun getAuthorities(): Collection<GrantedAuthority> {
return listOf("user").stream().map {

View File

@ -9,20 +9,25 @@ import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.toMono
import java.util.stream.Collectors
class AppuserJwtAuthConverter(private val appuserDetailsService: UserAccountDetailsService) :
class AppuserJwtAuthConverter :
Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken> {
val authorities = extractAuthorities(jwt)
/*val authorities = extractAuthorities(jwt)
// val sub = extractSub(jwt)
val username = extractUserName(jwt)
return appuserDetailsService
.findByUsername(username)
.map { user ->
UsernamePasswordAuthenticationToken(user, "n/a", authorities);
}
}*/
val authorities = extractAuthorities(jwt)
val sub = extractSub(jwt)
val username = extractUserName(jwt)
return UsernamePasswordAuthenticationToken(Appuser(sub, username, jwt.tokenValue!!), "n/a", authorities).toMono()
}
private fun extractSub(jwt: Jwt): String {

View File

@ -1,14 +0,0 @@
package com.securityc4po.reporting.configuration.security
import org.springframework.security.core.userdetails.ReactiveUserDetailsService
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.toMono
@Service
class UserAccountDetailsService : ReactiveUserDetailsService {
override fun findByUsername(username: String): Mono<UserDetails> {
return Appuser().toMono()
}
}

View File

@ -22,7 +22,7 @@ import org.springframework.web.cors.CorsConfiguration
@EnableReactiveMethodSecurity
@Configuration
@ComponentScan
class WebSecurityConfiguration(private val userAccountDetailsService: UserAccountDetailsService) {
class WebSecurityConfiguration {
@Value("\${external.issuer-uri}")
var externalIssuerUri: String? = null
@ -59,9 +59,10 @@ class WebSecurityConfiguration(private val userAccountDetailsService: UserAccoun
@Bean
fun appuserJwtAuthenticationConverter(): AppuserJwtAuthConverter {
return AppuserJwtAuthConverter(userAccountDetailsService)
return AppuserJwtAuthConverter()
}
@Bean
@Profile("COMPOSE")
fun jwtDecoder(): ReactiveJwtDecoder {

View File

@ -1,6 +1,8 @@
package com.securityc4po.reporting.remote
import com.securityc4po.reporting.http.ApplicationHeaders
import com.securityc4po.reporting.remote.model.PentestReport
import com.securityc4po.reporting.remote.model.api.Project
import com.securityc4po.reporting.remote.model.ProjectReport
import org.springframework.core.ParameterizedTypeReference
import org.springframework.stereotype.Component
@ -28,10 +30,10 @@ class APIClient(
* @param token of String
* @return of [ProjectReport]
*/
fun retrieveProjectDataById(projectId: String, token: String): Mono<ProjectReport> {
fun retrieveProjectDataById(projectId: String, token: String): Mono<Project> {
val projectByProjectIdUriBuilder = UriComponentsBuilder
.fromPath(apiClientCfg.projectReport.path)
.queryParam("projectId", projectId)
.fromPath(apiClientCfg.projects.path)
.pathSegment(projectId)
return webClient.get()
.uri(projectByProjectIdUriBuilder.toUriString())
@ -39,6 +41,27 @@ class APIClient(
it.add(ApplicationHeaders.AUTHORIZATION, "Bearer $token")
}
.retrieve()
.bodyToMono(object : ParameterizedTypeReference<ProjectReport>() {})
.bodyToMono(object : ParameterizedTypeReference<Project>() {})
}
/**
* Retrieves the [PentestReport] data from api service getProjectReportDataById()
*
* @param pentestId String id
* @param token of String
* @return of [PentestReport]
*/
fun retrievePentestDataById(pentestId: String, token: String): Mono<PentestReport> {
val projectByProjectIdUriBuilder = UriComponentsBuilder
.fromPath(apiClientCfg.pentests.path)
.pathSegment(pentestId)
return webClient.get()
.uri(projectByProjectIdUriBuilder.toUriString())
.headers {
it.add(ApplicationHeaders.AUTHORIZATION, "Bearer $token")
}
.retrieve()
.bodyToMono(object : ParameterizedTypeReference<PentestReport>() {})
}
}

View File

@ -11,10 +11,7 @@ import java.net.URL
class APIClientCfg {
lateinit var url: URL
var projects = ApiPath()
var projectReport = ApiPath()
var pentests = ApiPath()
val findings = ApiPath()
val comments = ApiPath()
}
class ApiPath {

View File

@ -1,20 +1,62 @@
package com.securityc4po.reporting.remote
import com.securityc4po.reporting.remote.model.ProjectReport
import com.securityc4po.reporting.extensions.getLoggerFor
import com.securityc4po.reporting.remote.model.*
import com.securityc4po.reporting.remote.model.api.*
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@Service
class APIService(private val apiClient: APIClient) {
var logger = getLoggerFor<APIService>()
/**
* Requests the complete project report data by project id
*
* @param id of String
* @param token of String
* @return [ProjectReport]
*/
fun requestProjectReportDataById(projectId: String, token: String): Mono<ProjectReport> {
var completedProjectReport: ProjectReport
return this.requestProjectDataById(projectId, token).flatMap { project: Project ->
// Setup completed [ProjectReport] object
completedProjectReport = project.toProjectReport()
// Request completed pentest data and add id to [ProjectReport] object
project.projectPentests?.let {
Flux.fromIterable(it.asIterable()).parallel().flatMap { projectPentest ->
this.requestPentestDataById(projectPentest.pentestId, token).map { completedPentest ->
completedPentest
}
}.sequential().collectList()
}?.map {
completedProjectReport.projectPentestReport.addAll(it)
completedProjectReport
} ?: Mono.just(completedProjectReport)
}
}
/**
* Requests the project data by id
*
* @param id of String
* @param token of String
* @return [ProjectReport]
* @return [Project]
*/
fun requestProjectDataById(projectId: String, token: String): Mono<ProjectReport> {
fun requestProjectDataById(projectId: String, token: String): Mono<Project> {
return apiClient.retrieveProjectDataById(projectId, token)
}
/**
* Requests the pentest report data by pentest id
*
* @param id of String
* @param token of String
* @return [PentestReport]
*/
fun requestPentestDataById(pentestId: String, token: String): Mono<PentestReport> {
return apiClient.retrievePentestDataById(pentestId, token)
}
}

View File

@ -1,32 +1,14 @@
package com.securityc4po.reporting.remote.model
import com.securityc4po.reporting.remote.model.api.Comment
import com.securityc4po.reporting.remote.model.api.Finding
import com.securityc4po.reporting.remote.model.api.PentestStatus
data class PentestReport(
val id: String,
val category: String, // ToDo: Change to be PentestCategory enum if it can be read by Jasper
val category: String,
val refNumber: String,
val findings: List<Finding>,
val comments: List<Comment>,
var status: PentestStatus
)
enum class PentestStatus {
NOT_STARTED,
DISABLED,
OPEN,
IN_PROGRESS,
COMPLETED
}
enum class PentestCategory {
INFORMATION_GATHERING,
CONFIGURATION_AND_DEPLOY_MANAGEMENT_TESTING,
IDENTITY_MANAGEMENT_TESTING,
AUTHENTICATION_TESTING,
AUTHORIZATION_TESTING,
SESSION_MANAGEMENT_TESTING,
INPUT_VALIDATION_TESTING,
ERROR_HANDLING,
CRYPTOGRAPHY,
BUSINESS_LOGIC_TESTING,
CLIENT_SIDE_TESTING
}

View File

@ -1,16 +1,12 @@
package com.securityc4po.reporting.remote.model
import com.fasterxml.jackson.annotation.JsonFormat
import java.time.Instant
data class ProjectReport(
val id: String,
val client: String,
val title: String,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
val createdAt: String = Instant.now().toString(),
val createdAt: String,
val tester: String,
val summary: String? = null,
var projectPentestReport: List<PentestReport> = emptyList(),
var projectPentestReport: MutableList<PentestReport> = mutableListOf<PentestReport>(),
val createdBy: String
)

View File

@ -1,4 +1,4 @@
package com.securityc4po.reporting.remote.model
package com.securityc4po.reporting.remote.model.api
data class Comment (
val id: String,

View File

@ -1,4 +1,4 @@
package com.securityc4po.reporting.remote.model
package com.securityc4po.reporting.remote.model.api
data class Finding (
val id: String,

View File

@ -0,0 +1,9 @@
package com.securityc4po.reporting.remote.model.api
enum class PentestStatus {
NOT_STARTED,
DISABLED,
OPEN,
IN_PROGRESS,
COMPLETED
}

View File

@ -0,0 +1,33 @@
package com.securityc4po.reporting.remote.model.api
import com.securityc4po.reporting.remote.model.PentestReport
import com.securityc4po.reporting.remote.model.ProjectReport
data class Project(
val id: String,
val client: String,
val title: String,
val createdAt: String,
val tester: String,
val summary: String? = null,
var projectPentests: List<ProjectPentest>? = emptyList(),
val createdBy: String
)
fun Project.toProjectReport(): ProjectReport {
return ProjectReport(
id = this.id,
client = this.client,
title = this.title,
createdAt = this.createdAt,
tester = this.tester,
summary = this.summary,
projectPentestReport = mutableListOf<PentestReport>(),
createdBy = this.createdBy
)
}
data class ProjectPentest(
val pentestId: String,
var status: String
)

View File

@ -6,7 +6,6 @@ import com.securityc4po.reporting.configuration.security.Appuser
import com.securityc4po.reporting.extensions.getLoggerFor
import com.securityc4po.reporting.remote.APIService
import com.securityc4po.reporting.remote.model.ProjectReport
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.notFound
@ -34,13 +33,14 @@ class ReportController(private val apiService: APIService, private val reportSer
)
fun downloadPentestReportPDF(@PathVariable(value = "projectId") projectId: String, @AuthenticationPrincipal user: Appuser): Mono<ResponseEntity<ByteArray>> {
// Todo: Create Report with Jasper
// this.apiService.requestProjectDataById(projectId, user.token)
return this.apiService.requestProjectReportDataById(projectId, user.token).flatMap {projectReport ->
/* ToDo: remove if jsonProjectReportCollection not needed for report generation */
val jsonProjectReportString: String =
File("./src/test/resources/ProjectReportData.json").readText(Charsets.UTF_8)
val jsonProjectReportCollection: ProjectReport =
jacksonObjectMapper().readValue<ProjectReport>(jsonProjectReportString)
// Setup headers
return this.reportService.createReport(jsonProjectReportCollection, "pdf").map { reportClassLoaderFilePath ->
/* jsonProjectReportCollection */
this.reportService.createReport(projectReport, "pdf").map { reportClassLoaderFilePath ->
ResponseEntity.ok().body(reportClassLoaderFilePath)
}.switchIfEmpty {
Mono.just(notFound().build<ByteArray>())
@ -48,6 +48,7 @@ class ReportController(private val apiService: APIService, private val reportSer
this.reportService.cleanUpFiles()
}
}
}
// ToDo: Add download API for csv report
/*

View File

@ -2,6 +2,8 @@ package com.securityc4po.reporting.report
import com.securityc4po.reporting.extensions.getLoggerFor
import com.securityc4po.reporting.remote.model.*
import com.securityc4po.reporting.remote.model.api.Comment
import com.securityc4po.reporting.remote.model.api.Finding
import net.sf.jasperreports.engine.*
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
import org.apache.commons.io.FileUtils
@ -326,19 +328,16 @@ class ReportService {
for (i in 0 until projectReportCollection.projectPentestReport.size) {
val projectSinglePentestReportDataSource: JRBeanCollectionDataSource =
JRBeanCollectionDataSource(mutableListOf(projectReportCollection.projectPentestReport[i]))
// Setup Sub-dataset for Findings of Pentest
val pentestFindingsDataSource: JRBeanCollectionDataSource =
JRBeanCollectionDataSource(projectReportCollection.projectPentestReport[i].findings)
// Setup Sub-dataset for Comments of Pentest
// val pentestCommentsDataSource =
// Setup Parameter & add Sub-datasets
val parameters = HashMap<String, Any>()
// Setup Sub-dataset for Findings of Pentest
parameters["PentestFindingsDataSource"] =
if (projectReportCollection.projectPentestReport[i].findings.isNotEmpty()) {
JRBeanCollectionDataSource(projectReportCollection.projectPentestReport[i].findings)
} else {
JRBeanCollectionDataSource(emptyList<Finding>())
}
// Setup Sub-dataset for Comments of Pentest
parameters["PentestCommentsDataSource"] =
if (projectReportCollection.projectPentestReport[i].comments.isNotEmpty()) {
JRBeanCollectionDataSource(projectReportCollection.projectPentestReport[i].comments)

View File

@ -14,10 +14,7 @@ management.endpoints.web.exposure.include=info, health, metrics
## C4PO_ApiService ##
api.client.url=http://localhost:8443/
api.client.projects.path=projects
api.client.projectReport.path=projects/report
api.client.pentests.path=pentests
api.client.findings.path=pentests/findings
api.client.comments.path=pentests/comments
## IdentityProvider (Keycloak) ##
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/auth/realms/c4po_realm_local