package com.securityc4po.api.pentest import com.github.tomakehurst.wiremock.common.Json import com.securityc4po.api.BaseIntTest import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC import com.securityc4po.api.finding.* import com.securityc4po.api.project.Project import com.securityc4po.api.project.ProjectEntity import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.web.server.LocalServerPort import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.query.Query import org.springframework.test.web.reactive.server.WebTestClient import reactor.core.publisher.Mono import java.time.Duration @SuppressFBWarnings( SIC_INNER_SHOULD_BE_STATIC, NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE ) class PentestControllerIntTest : BaseIntTest() { @LocalServerPort private var port = 0 @Autowired lateinit var mongoTemplate: MongoTemplate @Autowired private lateinit var webTestClient: WebTestClient @BeforeEach fun setupWebClient() { webTestClient = WebTestClient.bindToServer() .baseUrl("http://localhost:$port") .responseTimeout(Duration.ofMillis(10000)) .build() } @BeforeEach fun init() { configureAdminToken() persistBasicTestScenario() } @AfterEach fun destroy() { cleanUp() } @Nested inner class GetPentests { @Test fun `requesting pentests by projectId and category successfully`() { val projectId = "d2e126ba-f608-11ec-b939-0242ac120025" val category = "INFORMATION_GATHERING" webTestClient.get() .uri("/pentests?projectId={projectId}&category={category}", projectId, category) .header("Authorization", "Bearer $tokenAdmin") .exchange() .expectStatus().isOk .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody().json(Json.write(getPentests())) } private val pentestOne = Pentest( id = "9c8af320-f608-11ec-b939-0242ac120002", projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), commentIds = emptyList() ) private val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), commentIds = emptyList() ) private fun getPentests() = listOf( pentestOne.toPentestResponseBody(), pentestTwo.toPentestResponseBody() ) } @Nested inner class SavePentest { @Test fun `save pentest successfully`() { val projectId = "d2e126ba-f608-11ec-b939-0242ac120025" webTestClient.post() .uri("/pentests/{projectId}", projectId) .header("Authorization", "Bearer $tokenAdmin") .body(Mono.just(pentestRequestBody), PentestRequestBody::class.java) .exchange() .expectStatus().isAccepted .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody() .jsonPath("$.projectId").isEqualTo("d2e126ba-f608-11ec-b939-0242ac120025") .jsonPath("$.category").isEqualTo("CLIENT_SIDE_TESTING") .jsonPath("$.refNumber").isEqualTo("OTG-CLIENT-001") .jsonPath("$.status").isEqualTo("IN_PROGRESS") .jsonPath("$.findingIds").isEmpty .jsonPath("$.commentIds").isEmpty } val pentestRequestBody = PentestRequestBody( projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = "CLIENT_SIDE_TESTING", refNumber = "OTG-CLIENT-001", status = "IN_PROGRESS", findingIds = emptyList(), commentIds = emptyList() ) } @Nested inner class UpdatePentest { @Test fun `update pentest successfully`() { val pentestOneId = "9c8af320-f608-11ec-b939-0242ac120002" webTestClient.patch() .uri("/pentests/{pentestId}", pentestOneId) .header("Authorization", "Bearer $tokenAdmin") .body(Mono.just(pentestOneRequestBody), PentestRequestBody::class.java) .exchange() .expectStatus().isAccepted .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody() .jsonPath("$.id").isEqualTo(pentestOneId) .jsonPath("$.projectId").isEqualTo("d2e126ba-f608-11ec-b939-0242ac120025") .jsonPath("$.category").isEqualTo("INFORMATION_GATHERING") .jsonPath("$.refNumber").isEqualTo("OTG-INFO-001") .jsonPath("$.status").isEqualTo("OPEN") .jsonPath("$.findingIds").isEmpty .jsonPath("$.commentIds").isEmpty } val pentestOneRequestBody = PentestRequestBody( projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = "INFORMATION_GATHERING", refNumber = "OTG-INFO-001", status = "OPEN", findingIds = emptyList(), commentIds = emptyList() ) } @Nested inner class GetFindings { @Test fun `requesting findings by pentestId successfully`() { val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" webTestClient.get() .uri("/pentests/{pentestId}/findings", pentestTwoId) .header("Authorization", "Bearer $tokenAdmin") .exchange() .expectStatus().isOk .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody().json(Json.write(getFindings())) } private val findingOne = Finding( id = "ab62d365-1b1d-4da1-89bc-5496616e220f", severity = Severity.LOW, title = "Found Bug", description = "OTG-INFO-002 Bug", impact = "Service", affectedUrls = emptyList(), reproduction = "Step 1: Hack", mitigation = "None" ) private fun getFindings() = listOf( findingOne.toFindingResponseBody() ) } @Nested inner class GetFinding { @Test fun `requesting finding by findingId successfully`() { val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f" webTestClient.get() .uri("/pentests/{findingId}/finding", findingId) .header("Authorization", "Bearer $tokenAdmin") .exchange() .expectStatus().isOk .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody().json(Json.write(findingOne.toFindingResponseBody())) } private val findingOne = Finding( id = "ab62d365-1b1d-4da1-89bc-5496616e220f", severity = Severity.LOW, title = "Found Bug", description = "OTG-INFO-002 Bug", impact = "Service", affectedUrls = emptyList(), reproduction = "Step 1: Hack", mitigation = "None" ) } @Nested inner class SaveFinding { @Test fun `save finding successfully`() { val pentestTwoId = "43fbc63c-f624-11ec-b939-0242ac120002" webTestClient.post() .uri("/pentests/{pentestId}/finding", pentestTwoId) .header("Authorization", "Bearer $tokenAdmin") .body(Mono.just(findingBody), FindingRequestBody::class.java) .exchange() .expectStatus().isAccepted .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody() .jsonPath("$.severity").isEqualTo("MEDIUM") .jsonPath("$.title").isEqualTo("Found another Bug") .jsonPath("$.description").isEqualTo("Another OTG-INFO-002 Bug") .jsonPath("$.impact").isEqualTo("Service") .jsonPath("$.affectedUrls").isEmpty .jsonPath("$.reproduction").isEqualTo("Step 1: Hack more") .jsonPath("$.mitigation").isEqualTo("Another None") } private val findingBody = FindingRequestBody( severity = "MEDIUM", title = "Found another Bug", description = "Another OTG-INFO-002 Bug", impact = "Service", affectedUrls = emptyList(), reproduction = "Step 1: Hack more", mitigation = "Another None" ) } @Nested inner class UpdateFinding { @Test fun `update finding successfully`() { val findingId = "43fbc63c-f624-11ec-b939-0242ac120002" webTestClient.post() .uri("/pentests/{findingId}/finding", findingId) .header("Authorization", "Bearer $tokenAdmin") .body(Mono.just(findingBody), FindingRequestBody::class.java) .exchange() .expectStatus().isAccepted .expectHeader().valueEquals("Application-Name", "SecurityC4PO") .expectBody() .jsonPath("$.severity").isEqualTo("HIGH") .jsonPath("$.title").isEqualTo("Updated Bug") .jsonPath("$.description").isEqualTo("Updated OTG-INFO-002 Bug") .jsonPath("$.impact").isEqualTo("Service") .jsonPath("$.affectedUrls").isEmpty .jsonPath("$.reproduction").isEqualTo("Step 1: Hack") .jsonPath("$.mitigation").isEqualTo("Still None") } private val findingBody = FindingRequestBody( severity = "HIGH", title = "Updated Bug", description = "Updated OTG-INFO-002 Bug", impact = "Service", affectedUrls = emptyList(), reproduction = "Step 1: Hack", mitigation = "Still None" ) } private fun persistBasicTestScenario() { // setup test data // project val projectOne = Project( id = "d2e126ba-f608-11ec-b939-0242ac120025", client = "E Corp", title = "Some Mock API (v1.0) Scanning", createdAt = "2021-01-10T18:05:00Z", tester = "Novatester", createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" ) // pentests val pentestOne = Pentest( id = "9c8af320-f608-11ec-b939-0242ac120002", projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-001", status = PentestStatus.NOT_STARTED, findingIds = emptyList(), commentIds = emptyList() ) val pentestTwo = Pentest( id = "43fbc63c-f624-11ec-b939-0242ac120002", projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = PentestCategory.INFORMATION_GATHERING, refNumber = "OTG-INFO-002", status = PentestStatus.IN_PROGRESS, findingIds = listOf("ab62d365-1b1d-4da1-89bc-5496616e220f"), commentIds = emptyList() ) val pentestThree = Pentest( id = "16vbc63c-f624-11ec-b939-0242ac120002", projectId = "d2e126ba-f608-11ec-b939-0242ac120025", category = PentestCategory.AUTHENTICATION_TESTING, refNumber = "OTG-AUTHN-001", status = PentestStatus.COMPLETED, findingIds = emptyList(), commentIds = emptyList() ) // Finding val findingOne = Finding( id = "ab62d365-1b1d-4da1-89bc-5496616e220f", severity = Severity.LOW, title = "Found Bug", description = "OTG-INFO-002 Bug", impact = "Service", affectedUrls = emptyList(), reproduction = "Step 1: Hack", mitigation = "None" ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) mongoTemplate.save(PentestEntity(pentestOne)) mongoTemplate.save(PentestEntity(pentestTwo)) mongoTemplate.save(PentestEntity(pentestThree)) mongoTemplate.save(FindingEntity(findingOne)) } private fun configureAdminToken() { tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") } private fun cleanUp() { mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) mongoTemplate.findAllAndRemove(Query(), FindingEntity::class.java) tokenAdmin = "n/a" } }