diff --git a/security-c4po-api/security-c4po-api.postman_collection.json b/security-c4po-api/security-c4po-api.postman_collection.json index 165b433..76ee6a9 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": "a20516f0-5f7f-4d15-9f26-ada358993ff8", + "_postman_id": "1823096a-1cb2-438d-9872-73ec0ca94cfa", "name": "security-c4po-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -10,9 +10,6 @@ "item": [ { "name": "getProjects", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, "request": { "auth": { "type": "oauth2", @@ -41,9 +38,38 @@ }, "method": "GET", "header": [], + "url": { + "raw": "http://localhost:8443/projects", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8443", + "path": [ + "projects" + ] + } + }, + "response": [] + }, + { + "name": "saveProject", + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], "body": { "mode": "raw", - "raw": "", + "raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j pentest\",\n \"tester\" : \"Stipe\",\n \"createdBy\" : \"10e06d7a-8dd0-4ecd-8963-056b45079c4f\"\n}", "options": { "raw": { "language": "json" @@ -65,30 +91,11 @@ "response": [] }, { - "name": "saveProject", + "name": "deleteProject", "request": { - "method": "POST", + "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j Pentest\",\n \"tester\": \"Stipe\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8443/projects", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8443", - "path": [ - "projects" - ] - } + "url": null }, "response": [] } diff --git a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc index dd6b4a9..eda7418 100644 --- a/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc +++ b/security-c4po-api/src/main/asciidoc/SecurityC4PO.adoc @@ -55,10 +55,28 @@ include::{snippets}/saveProject/http-response.adoc[] include::{snippets}/saveProject/response-fields.adoc[] +=== Delete project + +To delete a project, call the DELETE request /projects/{projectId} + +==== Request example + +include::{snippets}/deleteProject/http-request.adoc[] + +==== Response example + +include::{snippets}/deleteProject/http-response.adoc[] + +==== Response structure + +include::{snippets}/deleteProject/response-fields.adoc[] + == Change History |=== |Date |Change +|2022-02-01 +|Added DELETE endpoint to save Projects |2021-12-22 |Added POST endpoint to save Projects |2021-02-14 diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt index b5686ad..960288b 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/Project.kt @@ -28,6 +28,12 @@ fun Project.toProjectResponseBody(): ResponseBody { ) } +fun Project.toProjectDeleteResponseBody(): ResponseBody { + return mapOf( + "id" to id + ) +} + data class ProjectOverview( val projects: List ) @@ -44,6 +50,10 @@ data class ProjectRequestBody( val tester: String? = null ) +data class ProjectDeleteRequestBody( + val id: String +) + fun ProjectRequestBody.toProject(): Project { return Project( id = UUID.randomUUID().toString(), @@ -53,5 +63,7 @@ fun ProjectRequestBody.toProject(): Project { tester = this.tester, // ToDo: Should be changed to SUB from Token after adding AUTH Header createdBy = UUID.randomUUID().toString() - ) + +) + } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt index 9ae7a50..ae01d0d 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectController.kt @@ -15,8 +15,9 @@ import java.util.* origins = [], allowCredentials = "false", allowedHeaders = ["*"], - methods = [RequestMethod.GET, RequestMethod.POST] + methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST] ) + @SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION) class ProjectController(private val projectService: ProjectService) { @@ -41,4 +42,11 @@ class ProjectController(private val projectService: ProjectService) { ResponseEntity.accepted().body(it.toProjectResponseBody()) } } + + @DeleteMapping("/{id}") + fun deleteProject(@PathVariable(value = "id") id: String): Mono> { + return this.projectService.deleteProject(id).map{ + ResponseEntity.ok().body(it.toProjectDeleteResponseBody()) + } + } } diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectRepository.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectRepository.kt index 03e1561..b896237 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectRepository.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectRepository.kt @@ -1,5 +1,6 @@ package com.securityc4po.api.project +import org.springframework.data.mongodb.repository.DeleteQuery import org.springframework.data.mongodb.repository.Query import org.springframework.data.mongodb.repository.ReactiveMongoRepository import org.springframework.stereotype.Repository @@ -10,4 +11,7 @@ interface ProjectRepository: ReactiveMongoRepository { @Query("{'data._id' : ?0}") fun findProjectById(id: String): Mono + + @DeleteQuery("{'data._id' : ?0}") + fun deleteProjectById(id: String): Mono } \ No newline at end of file diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt index f1c8f1e..e963b92 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/project/ProjectService.kt @@ -6,6 +6,8 @@ import com.securityc4po.api.extensions.getLoggerFor import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import org.springframework.stereotype.Service import reactor.core.publisher.Mono +import reactor.kotlin.core.publisher.switchIfEmpty + @Service @SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION) @@ -33,4 +35,14 @@ class ProjectService(private val projectRepository: ProjectRepository) { logger.warn("Project could not be stored in Database. Thrown exception: ", it) } } + + fun deleteProject(id: String): Mono { + return projectRepository.findProjectById(id).switchIfEmpty{ + logger.info("Project with id $id not found. Deletion not possible.") + Mono.empty() + }.flatMap{ projectEntity: ProjectEntity -> + val project = projectEntity.toProject() + projectRepository.deleteProjectById(id).map{project} + } + } } diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt index f1b69c2..281df22 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/BaseContainerizedTest.kt @@ -39,7 +39,7 @@ abstract class BaseContainerizedTest { companion object { val mongoDbContainer = KGenericContainer(ImageFromDockerfile("c4poapibasecontainerizedtest").withDockerfileFromBuilder { - it.from("mongo") + it.from("mongo:4.4.6") it.env("MONGO_INITDB_ROOT_USERNAME", "root") it.env("MONGO_INITDB_ROOT_PASSWORD", "cjwkbencowepoc324pon2mop3mp4") it.env("MONGO_INITDB_DATABASE", "admin") 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 79dd7d0..c5b87bd 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 @@ -149,6 +149,46 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { ) } + @Nested + inner class DeleteProject { + @Test + fun deleteProject() { + webTestClient.delete().uri("/projects/${project.id}") + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody() + .json(Json.write(project.toProjectDeleteResponseBody())) + .consumeWith( + WebTestClientRestDocumentation.document( + "{methodName}", + Preprocessors.preprocessRequest( + Preprocessors.prettyPrint(), + Preprocessors.modifyUris().removePort(), + Preprocessors.removeHeaders("Host", "Content-Length") + ), + Preprocessors.preprocessResponse( + Preprocessors.prettyPrint() + ), + PayloadDocumentation.relaxedResponseFields( + PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING) + .description("The id of the deleted project") + ) + ) + ) + } + + val project = Project( + id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9", + client = "E Corp", + title = "Some Mock API (v1.0) Scanning", + createdAt = "2021-01-10T18:05:00Z", + tester = "Novatester", + createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + ) + } + private fun persistBasicTestScenario() { // setup test data val projectOne = Project( diff --git a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt index aae009e..9b89b47 100644 --- a/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt +++ b/security-c4po-api/src/test/kotlin/com/securityc4po/api/project/ProjectControllerIntTest.kt @@ -116,6 +116,36 @@ class ProjectControllerIntTest : BaseIntTest() { ) } + @Nested + inner class DeleteProject { + @Test + fun `deleted project successfully`() { + webTestClient.delete().uri("/projects/{id}", projectTwo.id) + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody().json(Json.write(projectTwo.toProjectDeleteResponseBody())) + } + /*@Test + fun `delete project by non-existing id`() { + webTestClient.delete().uri("/projects/{id}", "98754a47-796b-4b3f-abf9-c46c668596c5") + .header("Authorization", "Bearer $tokenAdmin") + .exchange() + .expectStatus().isNoContent + .expectHeader().valueEquals("Application-Name", "SecurityC4PO") + .expectBody().isEmpty + }*/ + val projectTwo = Project( + id = "61360a47-796b-4b3f-abf9-c46c668596c5", + client = "Allsafe", + title = "CashMyData (iOS)", + createdAt = "2021-01-10T18:05:00Z", + tester = "Elliot", + createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + ) + } + private fun persistBasicTestScenario() { // setup test data val projectOne = Project( diff --git a/security-c4po-cfg/mongodb/docker-compose.mongodb.yml b/security-c4po-cfg/mongodb/docker-compose.mongodb.yml index c9f3a7d..d6c959d 100644 --- a/security-c4po-cfg/mongodb/docker-compose.mongodb.yml +++ b/security-c4po-cfg/mongodb/docker-compose.mongodb.yml @@ -2,7 +2,7 @@ version: '3.1' services: c4po-db: - image: mongo:latest + image: mongo:4.4.6 container_name: c4po-db volumes: - ../volumes/mongodb/data:/data/db