From 24dccb3e8f574fc592cb21e9aa068a1a6139b473 Mon Sep 17 00:00:00 2001 From: norman-schmidt <60552466+norman-schmidt@users.noreply.github.com> Date: Fri, 5 Aug 2022 11:00:15 +0200 Subject: [PATCH] feat: added pentest endpoint to get pentests by projectId & category --- .../src/shared/services/pentest.service.ts | 5 +- .../security-c4po-api.postman_collection.json | 36 +++- .../src/main/asciidoc/SecurityC4PO.adoc | 30 +++- .../security/WebSecurityConfiguration.kt | 2 +- .../com/securityc4po/api/pentest/Pentest.kt | 30 ++++ .../api/pentest/PentestCategory.kt | 15 ++ .../api/pentest/PentestController.kt | 40 +++++ .../securityc4po/api/pentest/PentestEntity.kt | 32 ++++ .../api/pentest/PentestRepository.kt | 14 ++ .../api/pentest/PentestService.kt | 26 +++ .../securityc4po/api/pentest/PentestStatus.kt | 11 ++ .../PentestControllerDocumentationTest.kt | 167 ++++++++++++++++++ .../api/pentest/PentestControllerIntTest.kt | 142 +++++++++++++++ .../ProjectControllerDocumentationTest.kt | 6 +- 14 files changed, 549 insertions(+), 7 deletions(-) create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestCategory.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestRepository.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt create mode 100644 security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt create mode 100644 security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt create mode 100644 security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt diff --git a/security-c4po-angular/src/shared/services/pentest.service.ts b/security-c4po-angular/src/shared/services/pentest.service.ts index b101a7b..a85fe0a 100644 --- a/security-c4po-angular/src/shared/services/pentest.service.ts +++ b/security-c4po-angular/src/shared/services/pentest.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {environment} from '../../environments/environment'; -import {HttpClient} from '@angular/common/http'; +import {HttpClient, HttpParams} from '@angular/common/http'; import {Observable, of} from 'rxjs'; import {Category} from '@shared/models/category.model'; import {Pentest} from '@shared/models/pentest.model'; @@ -47,6 +47,7 @@ export class PentestService { * @param category the categories of which the pentests should be requested */ private getPentestByProjectIdAndCategory(projectId: string, category: Category): Observable { - return this.http.get(`${this.apiBaseURL}?projectId=${projectId}?category=${category}`); + const queryParams = new HttpParams().append('projectId', projectId).append('category', Category[category]); + return this.http.get(`${this.apiBaseURL}`, {params: queryParams}); } } diff --git a/security-c4po-api/security-c4po-api.postman_collection.json b/security-c4po-api/security-c4po-api.postman_collection.json index b51782d..c3f58c1 100644 --- a/security-c4po-api/security-c4po-api.postman_collection.json +++ b/security-c4po-api/security-c4po-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "6537da59-5c7a-478d-bf24-09a39022a690", + "_postman_id": "58adc500-c0c6-47f3-b268-5fcc16e0944d", "name": "security-c4po-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "5225213" @@ -255,6 +255,40 @@ } ] }, + { + "name": "pentests", + "item": [ + { + "name": "getPentestsByProjectIdAndCategory", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8443/pentests?projectId=8bc16303-f652-418a-b745-8a03d89356fb&category=INFORMATION_GATHERING", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8443", + "path": [ + "pentests" + ], + "query": [ + { + "key": "projectId", + "value": "8bc16303-f652-418a-b745-8a03d89356fb" + }, + { + "key": "category", + "value": "INFORMATION_GATHERING" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "getHealth", "request": { diff --git a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc index 9e1789e..75f5cae 100644 --- a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc +++ b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc @@ -57,12 +57,16 @@ include::{snippets}/saveProject/response-fields.adoc[] === Delete project -To delete a project, call the DELETE request /projects/{projectId} +To delete a project, call the DELETE request /projects/+{projectId}+ ==== Request example include::{snippets}/deleteProject/http-request.adoc[] +==== Request structure + +include::{snippets}/deleteProject/path-parameters.adoc[] + ==== Response example include::{snippets}/deleteProject/http-response.adoc[] @@ -77,7 +81,7 @@ include::{snippets}/deleteProject/response-fields.adoc[] === Update project -To update a project, call the PATCH request /projects/{projectId} +To update a project, call the PATCH request /projects/+{projectId}+ ==== Request example @@ -106,3 +110,25 @@ include::{snippets}/updateProject/response-fields.adoc[] |2021-02-12 |Initial version |=== + +== Pentest + +=== Get pentests by projectId and category + +To get pentests by projectId and category, call the GET request /pentests with the appropriate parameters. + +==== Request example + +include::{snippets}/getPentestsByProjectIdAndCategory/http-request.adoc[] + +==== Request Structure + +include::{snippets}/getPentestsByProjectIdAndCategory/request-parameters.adoc[] + +==== Response example + +include::{snippets}/getPentestsByProjectIdAndCategory/http-response.adoc[] + +==== Response structure + +include::{snippets}/getPentestsByProjectIdAndCategory/response-fields.adoc[] \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt index 0ba7402..6bc3c51 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/WebSecurityConfiguration.kt @@ -1,6 +1,5 @@ package com.securityc4po.api.configuration.security -import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration @@ -34,6 +33,7 @@ class WebSecurityConfiguration(private val userAccountDetailsService: UserAccoun .disable() .authorizeExchange() .pathMatchers(HttpMethod.GET, "/v1/projects/**").authenticated() + .pathMatchers(HttpMethod.GET, "/v1/pentests/**").authenticated() .pathMatchers("/actuator/**").permitAll() .pathMatchers("/docs/SecurityC4PO.html").permitAll() .anyExchange().authenticated() diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt new file mode 100644 index 0000000..c0306ec --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/Pentest.kt @@ -0,0 +1,30 @@ +package com.securityc4po.api.pentest + +import com.securityc4po.api.ResponseBody +import org.springframework.data.mongodb.core.index.Indexed +import java.util.UUID + +data class Pentest( + @Indexed(background = true, unique = true) + val id: String = UUID.randomUUID().toString(), + val projectId: String, + val category: PentestCategory, + val title: String, + val refNumber: String, + val status: PentestStatus, + val findingIds: String, + val commentIds: String +) + +fun Pentest.toPentestResponseBody(): ResponseBody { + return mapOf( + "id" to id, + "projectId" to projectId, + "category" to category, + "title" to title, + "refNumber" to refNumber, + "status" to status, + "findingIds" to findingIds, + "commentIds" to commentIds + ) +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestCategory.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestCategory.kt new file mode 100644 index 0000000..2c6db58 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestCategory.kt @@ -0,0 +1,15 @@ +package com.securityc4po.api.pentest + +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 +} \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt new file mode 100644 index 0000000..ee10f02 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestController.kt @@ -0,0 +1,40 @@ +package com.securityc4po.api.pentest + +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 org.springframework.http.ResponseEntity +import org.springframework.http.ResponseEntity.noContent +import org.springframework.web.bind.annotation.* +import reactor.core.publisher.Mono + +@RestController +@RequestMapping("/pentests") +@CrossOrigin( + origins = [], + allowCredentials = "false", + allowedHeaders = ["*"], + methods = [RequestMethod.GET] +) + +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION) +class PentestController(private val pentestService: PentestService) { + + var logger = getLoggerFor() + + @GetMapping + fun getPentestsByProjectIdAndCategory( + @RequestParam("projectId") projectId: String, + @RequestParam("category") category: String + ): Mono>> { + return pentestService.getPentests(projectId, PentestCategory.valueOf(category)).map { pentestList -> + pentestList.map { + it.toPentestResponseBody() + } + }.map { + if (it.isEmpty()) noContent().build() + else ResponseEntity.ok(it) + } + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt new file mode 100644 index 0000000..e1c55c5 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestEntity.kt @@ -0,0 +1,32 @@ +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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.springframework.data.mongodb.core.mapping.Document + +@Document(collection = "pentests") +open class PentestEntity( + data: Pentest +) : BaseEntity(data) + +fun PentestEntity.toPentest(): Pentest { + return Pentest( + this.data.id, + this.data.projectId, + this.data.category, + this.data.title, + this.data.refNumber, + this.data.status, + this.data.findingIds, + this.data.commentIds + ) +} + +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION) +fun List.toPentests(): List { + return this.map { + it.toPentest() + } +} diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestRepository.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestRepository.kt new file mode 100644 index 0000000..59e9e3b --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestRepository.kt @@ -0,0 +1,14 @@ +package com.securityc4po.api.pentest + +import org.springframework.data.mongodb.repository.Query +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux + +@Repository +interface PentestRepository : ReactiveMongoRepository { + + @Query("{'data.projectId': ?0, 'data.category': ?1}") + fun findPentestByProjectIdAndCategory(projectId: String, category: PentestCategory): Flux + +} \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt new file mode 100644 index 0000000..f0f05e5 --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestService.kt @@ -0,0 +1,26 @@ +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.extensions.getLoggerFor +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.springframework.stereotype.Service +import reactor.core.publisher.Mono + +@Service +@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION) +class PentestService(private val pentestRepository: PentestRepository) { + + var logger = getLoggerFor() + + /** + * Get all [Pentest]s by projectId and category + * + * @return list of [Pentest] + */ + fun getPentests(projectId: String, category: PentestCategory): Mono> { + return pentestRepository.findPentestByProjectIdAndCategory(projectId, category).collectList().map { + it.map { pentestEntity -> pentestEntity.toPentest() } + } + } +} \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt new file mode 100644 index 0000000..403934d --- /dev/null +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/pentest/PentestStatus.kt @@ -0,0 +1,11 @@ +package com.securityc4po.api.pentest + +enum class PentestStatus { + NOT_STARTED, + OPEN, + UNDER_REVIEW, + DISABLED, + CHECKED, + REPORTED, + TRIAGED +} \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt new file mode 100644 index 0000000..0b96f3a --- /dev/null +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerDocumentationTest.kt @@ -0,0 +1,167 @@ +package com.securityc4po.api.pentest + +import com.github.tomakehurst.wiremock.common.Json +import com.securityc4po.api.BaseDocumentationIntTest +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 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.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Query +import org.springframework.restdocs.operation.preprocess.Preprocessors +import org.springframework.restdocs.payload.JsonFieldType +import org.springframework.restdocs.payload.PayloadDocumentation +import org.springframework.restdocs.request.RequestDocumentation +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation + +@SuppressFBWarnings( + SIC_INNER_SHOULD_BE_STATIC, + NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, + RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE +) +class PentestControllerDocumentationTest : BaseDocumentationIntTest() { + + @Autowired + lateinit var mongoTemplate: MongoTemplate + + @BeforeEach + fun init() { + configureAdminToken() + persistBasicTestScenario() + } + + @AfterEach + fun destroy() { + cleanUp() + } + + @Nested + inner class GetPentests { + @Test + fun getPentestsByProjectIdAndCategory() { + val projectId = "d2e126ba-f608-11ec-b939-0242ac120002" + val category = "INFORMATION_GATHERING" + webTestClient.get() + .uri("/pentests?projectId={projectId}&category={category}", projectId, category) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().doesNotExist("") + .expectBody().json(Json.write(getProjectsResponse())) + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + RequestDocumentation.relaxedRequestParameters( + RequestDocumentation.parameterWithName("projectId").description("The id of the project you want to get the pentests for"), + RequestDocumentation.parameterWithName("category").description("The category you want to get the pentests for") + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING) + .description("The id of the requested pentest"), + PayloadDocumentation.fieldWithPath("[].projectId").type(JsonFieldType.STRING) + .description("The id of the project of the requested pentest"), + PayloadDocumentation.fieldWithPath("[].category").type(JsonFieldType.STRING) + .description("The category of the requested pentest"), + PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING) + .description("The title of the requested pentest"), + PayloadDocumentation.fieldWithPath("[].refNumber").type(JsonFieldType.STRING) + .description("The reference number of the requested pentest according to the current OWASP Testing Guide"), + PayloadDocumentation.fieldWithPath("[].status").type(JsonFieldType.STRING) + .description("The status of the requested pentest"), + PayloadDocumentation.fieldWithPath("[].findingIds").type(JsonFieldType.STRING) + .description("The ids of the findings in the requested pentest"), + PayloadDocumentation.fieldWithPath("[].commentIds").type(JsonFieldType.STRING) + .description("The ids of the comments of the requested pentest") + ) + ) + ) + } + + private val pentestOne = Pentest( + id = "9c8af320-f608-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Search engine discovery/reconnaissance", + refNumber = "OTG-INFO-001", + status = PentestStatus.NOT_STARTED, + findingIds = "", + commentIds = "" + ) + private val pentestTwo = Pentest( + id = "43fbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Fingerprint Web Server", + refNumber = "OTG-INFO-002", + status = PentestStatus.REPORTED, + findingIds = "", + commentIds = "" + ) + + private fun getProjectsResponse() = listOf( + pentestOne.toPentestResponseBody(), + pentestTwo.toPentestResponseBody() + ) + } + + private fun persistBasicTestScenario() { + // setup test data + val pentestOne = Pentest( + id = "9c8af320-f608-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Search engine discovery/reconnaissance", + refNumber = "OTG-INFO-001", + status = PentestStatus.NOT_STARTED, + findingIds = "", + commentIds = "" + ) + val pentestTwo = Pentest( + id = "43fbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Fingerprint Web Server", + refNumber = "OTG-INFO-002", + status = PentestStatus.REPORTED, + findingIds = "", + commentIds = "" + ) + val pentestThree = Pentest( + id = "74eae112-f62c-11ec-b939-0242ac120002", + projectId = "6fad3474-fc29-49f9-bd37-e039e9e60c18", + category = PentestCategory.AUTHENTICATION_TESTING, + title = "Testing for Credentials Transported over an Encrypted Channel", + refNumber = "OTG-AUTHN-001", + status = PentestStatus.CHECKED, + findingIds = "", + commentIds = "" + ) + // persist test data in database + mongoTemplate.save(PentestEntity(pentestOne)) + mongoTemplate.save(PentestEntity(pentestTwo)) + mongoTemplate.save(PentestEntity(pentestThree)) + } + + private fun configureAdminToken() { + tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + } + + private fun cleanUp() { + mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) + + tokenAdmin = "n/a" + } +} \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt new file mode 100644 index 0000000..5f19494 --- /dev/null +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/pentest/PentestControllerIntTest.kt @@ -0,0 +1,142 @@ +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 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 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`() { + webTestClient.get() + .uri("/pentests?projectId=d2e126ba-f608-11ec-b939-0242ac120002&category=INFORMATION_GATHERING") + .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-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Search engine discovery/reconnaissance", + refNumber = "OTG-INFO-001", + status = PentestStatus.NOT_STARTED, + findingIds = "", + commentIds = "" + ) + private val pentestTwo = Pentest( + id = "43fbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Fingerprint Web Server", + refNumber = "OTG-INFO-002", + status = PentestStatus.REPORTED, + findingIds = "", + commentIds = "" + ) + + private fun getPentests() = listOf( + pentestOne.toPentestResponseBody(), + pentestTwo.toPentestResponseBody() + ) + } + + private fun persistBasicTestScenario() { + // setup test data + val pentestOne = Pentest( + id = "9c8af320-f608-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Search engine discovery/reconnaissance", + refNumber = "OTG-INFO-001", + status = PentestStatus.NOT_STARTED, + findingIds = "", + commentIds = "" + ) + val pentestTwo = Pentest( + id = "43fbc63c-f624-11ec-b939-0242ac120002", + projectId = "d2e126ba-f608-11ec-b939-0242ac120002", + category = PentestCategory.INFORMATION_GATHERING, + title = "Fingerprint Web Server", + refNumber = "OTG-INFO-002", + status = PentestStatus.REPORTED, + findingIds = "", + commentIds = "" + ) + val pentestThree = Pentest( + id = "74eae112-f62c-11ec-b939-0242ac120002", + projectId = "6fad3474-fc29-49f9-bd37-e039e9e60c18", + category = PentestCategory.AUTHENTICATION_TESTING, + title = "Testing for Credentials Transported over an Encrypted Channel", + refNumber = "OTG-AUTHN-001", + status = PentestStatus.CHECKED, + findingIds = "", + commentIds = "" + ) + // persist test data in database + mongoTemplate.save(PentestEntity(pentestOne)) + mongoTemplate.save(PentestEntity(pentestTwo)) + mongoTemplate.save(PentestEntity(pentestThree)) + } + + private fun configureAdminToken() { + tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + } + + private fun cleanUp() { + mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java) + + tokenAdmin = "n/a" + } +} \ No newline at end of file diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt index 7f96346..c17989c 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerDocumentationTest.kt @@ -154,7 +154,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { inner class DeleteProject { @Test fun deleteProject() { - webTestClient.delete().uri("/projects/${project.id}") + val id = project.id + webTestClient.delete().uri("/projects/{id}", id) .header("Authorization", "Bearer $tokenAdmin") .exchange() .expectStatus().isOk @@ -172,6 +173,9 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { Preprocessors.preprocessResponse( Preprocessors.prettyPrint() ), + RequestDocumentation.relaxedPathParameters( + RequestDocumentation.parameterWithName("id").description("The id of the project you want to delete") + ), PayloadDocumentation.relaxedResponseFields( PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) .description("The id of the deleted project")