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.* 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.project.* import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.springframework.stereotype.Service import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.switchIfEmpty 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 ) { var logger = getLoggerFor() /** * Get all [Pentest]s by projectId and category * * @return list of [Pentest] */ fun getPentestsForCategory(projectId: String, category: PentestCategory): Mono> { return pentestRepository.findPentestByProjectIdAndCategory(projectId, category).collectList().map { it.map { pentestEntity -> pentestEntity.toPentest() } } } /** * Save [Pentest] * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be stored * @return saved [Pentest] */ fun savePentest(projectId: String, body: PentestRequestBody): Mono { validate( require = body.isValid(), logging = { logger.warn("Pentest not valid.") }, mappedException = InvalidModelException( "Pentest not valid.", Errorcode.PentestInvalid ) ) val pentest = body.toPentest() val pentestEntity = PentestEntity(pentest) return pentestRepository.insert(pentestEntity).flatMap { newPentestEntity: PentestEntity -> val newPentest = newPentestEntity.toPentest() // After successfully saving pentest add id and status to project val projectPentest = ProjectPentest(pentestId = newPentest.id, status = newPentest.status) projectService.updateProjectTestingProgress(projectId, projectPentest).onErrorMap { TransactionInterruptedException( "Project Pentests could not be updated in Database.", Errorcode.ProjectPentestInsertionFailed ) }.map { newPentest } }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be stored in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be stored.", Errorcode.PentestInsertionFailed ) ) } } /** * Update [Pentest] * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be updated * @return updated [Pentest] */ fun updatePentest(pentestId: String, body: PentestRequestBody): Mono { validate( require = body.isValid(), logging = { logger.warn("Pentest not valid.") }, mappedException = InvalidModelException( "Pentest not valid.", Errorcode.PentestInvalid ) ) return pentestRepository.findPentestById(pentestId).switchIfEmpty { logger.warn("Pentest with id $pentestId not found. Updating not possible.") val msg = "Pentest with id $pentestId not found." val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound) throw ex }.flatMap { currentPentestEntity: PentestEntity -> currentPentestEntity.lastModified = Instant.now() currentPentestEntity.data = buildPentest(body, currentPentestEntity) 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) projectService.updateProjectTestingProgress(body.projectId, projectPentest).onErrorMap { TransactionInterruptedException( "Project Pentest could not be updated in Database.", Errorcode.ProjectPentestInsertionFailed ) }.map { return@map newPentestEntity.toPentest() } }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be updated in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be updated.", Errorcode.PentestInsertionFailed ) ) } } } /** * Get all [Finding]Id's by pentestId * * @return list of [String] */ fun getFindingIdsByPentestId(pentestId: String): Mono> { 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> { 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 * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be updated * @return updated [Pentest] */ fun updatePentestFinding(pentestId: String, findingId: String): Mono { return pentestRepository.findPentestById(pentestId).switchIfEmpty { logger.warn("Pentest with id $pentestId not found. Updating not possible.") val msg = "Pentest with id $pentestId not found." val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound) throw ex }.flatMap { currentPentestEntity: PentestEntity -> if (currentPentestEntity.data.findingIds.find { pentestData -> pentestData == findingId } == null) { currentPentestEntity.data.findingIds += findingId } currentPentestEntity.lastModified = Instant.now() this.pentestRepository.save(currentPentestEntity).map { it.toPentest() }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be updated in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be updated.", Errorcode.PentestInsertionFailed ) ) } } } /** * Update [Pentest] for Finding * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be updated * @return updated [Pentest] */ fun removeFindingFromPentest(pentestId: String, findingId: String): Mono { return pentestRepository.findPentestById(pentestId).switchIfEmpty { logger.warn("Pentest with id $pentestId not found. Updating not possible.") val msg = "Pentest with id $pentestId not found." val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound) throw ex }.flatMap { currentPentestEntity: PentestEntity -> if (currentPentestEntity.data.findingIds.find { pentestData -> pentestData == findingId } != null) { val findingIds = currentPentestEntity.data.findingIds.toMutableList() findingIds.remove(findingId) currentPentestEntity.data.findingIds = findingIds.toList() } currentPentestEntity.lastModified = Instant.now() this.pentestRepository.save(currentPentestEntity).map { it.toPentest() }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be updated in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be updated.", Errorcode.PentestInsertionFailed ) ) } } } /** * Update [Pentest] for Comment * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be updated * @return updated [Pentest] */ fun updatePentestComment(pentestId: String, commentId: String): Mono { return pentestRepository.findPentestById(pentestId).switchIfEmpty { logger.warn("Pentest with id $pentestId not found. Updating not possible.") val msg = "Pentest with id $pentestId not found." val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound) throw ex }.flatMap { currentPentestEntity: PentestEntity -> if (currentPentestEntity.data.commentIds.find { pentestData -> pentestData == commentId } == null) { currentPentestEntity.data.commentIds += commentId } currentPentestEntity.lastModified = Instant.now() this.pentestRepository.save(currentPentestEntity).map { it.toPentest() }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be updated in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be updated.", Errorcode.PentestInsertionFailed ) ) } } } /** * Update [Pentest] for Comment * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be updated * @return updated [Pentest] */ fun removeCommentFromPentest(pentestId: String, commentId: String): Mono { return pentestRepository.findPentestById(pentestId).switchIfEmpty { logger.warn("Pentest with id $pentestId not found. Updating not possible.") val msg = "Pentest with id $pentestId not found." val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound) throw ex }.flatMap { currentPentestEntity: PentestEntity -> if (currentPentestEntity.data.commentIds.find { pentestData -> pentestData == commentId } != null) { val commentIds = currentPentestEntity.data.commentIds.toMutableList() commentIds.remove(commentId) currentPentestEntity.data.commentIds = commentIds.toList() } currentPentestEntity.lastModified = Instant.now() this.pentestRepository.save(currentPentestEntity).map { it.toPentest() }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be updated in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be updated.", Errorcode.PentestInsertionFailed ) ) } } } /** * Enable or disable [Pentest] * * @throws [InvalidModelException] if the [Pentest] is invalid * @throws [TransactionInterruptedException] if the [Pentest] could not be enabled or disabled * @return enabled or disabled [Pentest] */ fun enableOrDisableObjectiveByPentestId(projectId: String, pentestId: String, enable: Boolean): Mono { return pentestRepository.findPentestById(pentestId).switchIfEmpty { logger.warn("Pentest with id $pentestId not found. Enabling not possible.") val msg = "Pentest with id $pentestId not found." val ex = EntityNotFoundException(msg, Errorcode.PentestNotFound) throw ex }.flatMap { currentPentestEntity: PentestEntity -> if (enable) { // Enable Pentest currentPentestEntity.data.enabled = true if (currentPentestEntity.data.findingIds.isEmpty() && currentPentestEntity.data.commentIds.isEmpty()) { currentPentestEntity.data.status = PentestStatus.NOT_STARTED } else { currentPentestEntity.data.status = PentestStatus.PAUSED } } else { // Disable Pentest currentPentestEntity.data.enabled = false currentPentestEntity.data.status = PentestStatus.DISABLED } currentPentestEntity.lastModified = Instant.now() this.pentestRepository.save(currentPentestEntity).flatMap {updatedPentestEntity -> // After successfully enabling or disabling of pentest update id and status to project val projectPentest = ProjectPentest(pentestId = pentestId, status = currentPentestEntity.data.status) projectService.updateProjectTestingProgress(projectId, projectPentest).onErrorMap { TransactionInterruptedException( "Project Pentest could not be updated in Database.", Errorcode.ProjectPentestInsertionFailed ) }.map { return@map updatedPentestEntity.toPentest() } }.doOnError { throw wrappedException( logging = { logger.warn("Pentest could not be enabled or disabled in Database. Thrown exception: ", it) }, mappedException = TransactionInterruptedException( "Pentest could not be enabled or disabled.", Errorcode.PentestInsertionFailed ) ) } } } }