diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt index b292d4e..aa46d5a 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/Appuser.kt @@ -5,7 +5,10 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.userdetails.UserDetails -class Appuser internal constructor() : UserDetails { +class Appuser internal constructor(sub: String, username: String, val token: String) : UserDetails { + + var userSub = sub + var userName = username override fun getAuthorities(): Collection { return listOf("user").stream().map { @@ -17,12 +20,17 @@ class Appuser internal constructor() : UserDetails { }.collect(Collectors.toList()) } + + fun getSub(): String { + return userSub + } + override fun getPassword(): String { return "n/a" } override fun getUsername(): String { - return "n/a" + return userName } override fun isAccountNonExpired(): Boolean { diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt index e313477..2467c8e 100644 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt +++ b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/AppuserJwtAuthConverter.kt @@ -9,20 +9,17 @@ 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> { override fun convert(jwt: Jwt): Mono { val authorities = extractAuthorities(jwt) - // val sub = extractSub(jwt) + val sub = extractSub(jwt) val username = extractUserName(jwt) - return appuserDetailsService - .findByUsername(username) - .map { user -> - UsernamePasswordAuthenticationToken(user, "n/a", authorities); - } + return UsernamePasswordAuthenticationToken(Appuser(sub, username, jwt.tokenValue!!), "n/a", authorities).toMono() } private fun extractSub(jwt: Jwt): String { diff --git a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt b/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt deleted file mode 100644 index 48c00c8..0000000 --- a/security-c4po-api/src/main/kotlin/com/securityc4po/api/configuration/security/UserAccountDetailsService.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.securityc4po.api.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 { - return Appuser().toMono() - } -} 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 6c49e2f..ebcdd67 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 @@ -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 @@ -60,7 +60,7 @@ class WebSecurityConfiguration(private val userAccountDetailsService: UserAccoun @Bean fun appuserJwtAuthenticationConverter(): AppuserJwtAuthConverter { - return AppuserJwtAuthConverter(userAccountDetailsService) + return AppuserJwtAuthConverter() } @Bean 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 54df272..cc464f7 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 @@ -24,7 +24,8 @@ data class Project( val state: PentestState, val version: String, var projectPentests: List = emptyList(), - val createdBy: String + @Indexed(background = true, unique = false) + var createdBy: String ) fun buildProject(body: ProjectRequestBody, projectEntity: ProjectEntity): Project { @@ -159,6 +160,21 @@ fun ProjectRequestBody.toProject(): Project { // ToDo: Update version in backend automatically version = "1.0", // ToDo: Should be changed to SUB from Token after adding AUTH Header - createdBy = UUID.randomUUID().toString() + createdBy = "" + ) +} + +fun ProjectRequestBody.toNewProject(userId: String): Project { + return Project( + id = UUID.randomUUID().toString(), + client = this.client, + title = this.title, + createdAt = Instant.now().toString(), + tester = this.tester, + summary = this.summary, + state = this.state, + // ToDo: Update version in backend automatically + version = "1.0", + createdBy = userId ) } 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 a84177f..5bee23e 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 @@ -4,11 +4,13 @@ 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.configuration.security.Appuser import com.securityc4po.api.pentest.PentestDeletionService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.switchIfEmpty +import org.springframework.security.core.annotation.AuthenticationPrincipal @RestController @RequestMapping("/projects") @@ -24,8 +26,12 @@ class ProjectController(private val projectService: ProjectService, private val var logger = getLoggerFor() @GetMapping - fun getProjects(): Mono>> { - return projectService.getProjects().map { projectList -> + fun getProjects( + @AuthenticationPrincipal user: Appuser + ): Mono>> { + + println("controller " + user.getSub()) + return projectService.getProjects(user.getSub()).map { projectList -> projectList.map { it.toProjectResponseBody() } @@ -37,9 +43,10 @@ class ProjectController(private val projectService: ProjectService, private val @GetMapping("/{projectId}") fun getCompletedProjectById( - @PathVariable(value = "projectId") projectId: String + @PathVariable(value = "projectId") projectId: String, + @AuthenticationPrincipal user: Appuser ): Mono> { - return projectService.getProjectById(projectId).map { + return projectService.getProjectById(projectId, user.getSub()).map { it.toProjectCompletedPentestResponseBody() }.map { if (it.isEmpty()) ResponseEntity.noContent().build() @@ -49,9 +56,10 @@ class ProjectController(private val projectService: ProjectService, private val @GetMapping("/evaluation/{projectId}") fun getProjectById( - @PathVariable(value = "projectId") projectId: String + @PathVariable(value = "projectId") projectId: String, + @AuthenticationPrincipal user: Appuser ): Mono> { - return projectService.getProjectById(projectId).map { + return projectService.getProjectById(projectId, user.getSub()).map { it.toProjectEvaluatedPentestResponseBody() }.map { if (it.isEmpty()) ResponseEntity.noContent().build() @@ -61,9 +69,10 @@ class ProjectController(private val projectService: ProjectService, private val @PostMapping fun saveProject( - @RequestBody body: ProjectRequestBody + @RequestBody body: ProjectRequestBody, + @AuthenticationPrincipal user: Appuser ): Mono> { - return this.projectService.saveProject(body).map { + return this.projectService.saveProject(body, user.getSub()).map { ResponseEntity.accepted().body(it.toProjectResponseBody()) } } @@ -88,9 +97,10 @@ class ProjectController(private val projectService: ProjectService, private val @PatchMapping("/{id}") fun updateProject( @PathVariable(value = "id") id: String, + @AuthenticationPrincipal user: Appuser, @RequestBody body: ProjectRequestBody ): Mono> { - return this.projectService.updateProject(id, body).map { + return this.projectService.updateProject(id, body, user.getSub()).map { ResponseEntity.accepted().body(it.toProjectResponseBody()) } } 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 29ae1ba..e9e4615 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 @@ -24,8 +24,8 @@ class ProjectService(private val projectRepository: ProjectRepository) { * @throws [EntityNotFoundException] if there are no [Project]s in collection * @return list of [Project] */ - fun getProjects(): Mono> { - return projectRepository.findAll().collectList().map { + fun getProjects(userSub: String): Mono> { + return projectRepository.findAll().filter { project -> project.data.createdBy == userSub }.collectList().map { it.map { projectEntity -> projectEntity.toProject() } }.switchIfEmpty { val msg = "Projects not found." @@ -41,8 +41,8 @@ class ProjectService(private val projectRepository: ProjectRepository) { * @throws [EntityNotFoundException] if there is no [Project] in collection * @return [Project] */ - fun getProjectById(projectId: String): Mono { - return projectRepository.findProjectById(projectId).map { + fun getProjectById(projectId: String, userSub: String): Mono { + return projectRepository.findProjectById(projectId).filter { project -> project.data.createdBy == userSub }.map { it.toProject() }.switchIfEmpty { val msg = "Project not found." @@ -59,7 +59,7 @@ class ProjectService(private val projectRepository: ProjectRepository) { * @throws [TransactionInterruptedException] if the [Project] could not be stored * @return saved [Project] */ - fun saveProject(body: ProjectRequestBody): Mono { + fun saveProject(body: ProjectRequestBody, userSub: String): Mono { validate( require = body.isValid(), logging = { logger.warn("Project not valid.") }, @@ -68,6 +68,7 @@ class ProjectService(private val projectRepository: ProjectRepository) { ) ) val project = body.toProject() + project.createdBy = userSub val projectEntity = ProjectEntity(project) return projectRepository.insert(projectEntity).map { it.toProject() @@ -110,7 +111,7 @@ class ProjectService(private val projectRepository: ProjectRepository) { * @throws [TransactionInterruptedException] if the [Project] could not be updated * @return updated [Project] */ - fun updateProject(id: String, body: ProjectRequestBody): Mono { + fun updateProject(id: String, body: ProjectRequestBody, userSub: String): Mono { validate( require = body.isValid(), logging = { logger.warn("Project not valid.") }, 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 7a148fd..bed0022 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 @@ -74,6 +74,7 @@ abstract class BaseContainerizedTest { var token = "n/a" var tokenAdmin = "n/a" + var adminSub = "n/a" var tokenUser = "n/a" fun getAccessToken(username: String, password: String, clientId: String, realm: String): String { 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 aab37a1..d149632 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 @@ -98,7 +98,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { projectPentests = emptyList(), state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) val projectTwo = Project( id = "61360a47-796b-4b3f-abf9-c46c668596c5", @@ -110,7 +110,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { projectPentests = emptyList(), state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) private fun getProjectsResponse() = listOf( @@ -245,7 +245,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { projectPentests = emptyList(), state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) } @@ -315,7 +315,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { state = PentestState.NEW, version = "1.0", projectPentests = emptyList(), - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) } @@ -331,7 +331,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { state = PentestState.NEW, version = "1.0", projectPentests = emptyList(), - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) val projectTwo = Project( id = "61360a47-796b-4b3f-abf9-c46c668596c5", @@ -343,7 +343,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { state = PentestState.NEW, version = "1.0", projectPentests = emptyList(), - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) @@ -352,11 +352,13 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() { private fun configureAdminToken() { tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + adminSub = getSubClaim(tokenAdmin) } private fun cleanUp() { mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) tokenAdmin = "n/a" + adminSub = "n/a" } } \ No newline at end of file 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 d963430..9c73799 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 @@ -57,6 +57,8 @@ class ProjectControllerIntTest : BaseIntTest() { inner class GetProjects { @Test fun `requesting projects successfully`() { + println("test " + adminSub) + webTestClient.get().uri("/projects") .header("Authorization", "Bearer $tokenAdmin") .exchange() @@ -74,7 +76,7 @@ class ProjectControllerIntTest : BaseIntTest() { summary = "Lorem Ipsum", state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) val projectTwo = Project( id = "61360a47-796b-4b3f-abf9-c46c668596c5", @@ -85,7 +87,7 @@ class ProjectControllerIntTest : BaseIntTest() { summary = "Lorem Ipsum", state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) private fun getProjects() = listOf( @@ -109,8 +111,7 @@ class ProjectControllerIntTest : BaseIntTest() { .jsonPath("$.client").isEqualTo("Novatec") .jsonPath("$.title").isEqualTo("log4j Pentest") .jsonPath("$.tester").isEqualTo("Stipe") - // ToDo: Should be changed to SUB from Token after adding AUTH Header - /*.jsonPath("$.createdBy").isEqualTo("f8aab31f-4925-4242-a6fa-f98135b4b032")*/ + .jsonPath("$.createdBy").isEqualTo("8f725a10-bdf5-4530-a185-4627fb092d78") } val project = Project( @@ -122,7 +123,7 @@ class ProjectControllerIntTest : BaseIntTest() { summary = "", state = PentestState.NEW, version = "1.0", - createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) } @@ -157,7 +158,7 @@ class ProjectControllerIntTest : BaseIntTest() { tester = "Elliot", state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) } @@ -185,7 +186,7 @@ class ProjectControllerIntTest : BaseIntTest() { tester = "Stipe_updated", state = PentestState.NEW, version = "1.0", - createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) } @@ -200,7 +201,7 @@ class ProjectControllerIntTest : BaseIntTest() { summary = "Lorem Ipsum", state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) val projectTwo = Project( id = "61360a47-796b-4b3f-abf9-c46c668596c5", @@ -211,7 +212,7 @@ class ProjectControllerIntTest : BaseIntTest() { summary = "Lorem Ipsum", state = PentestState.NEW, version = "1.0", - createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032" + createdBy = "8f725a10-bdf5-4530-a185-4627fb092d78" ) // persist test data in database mongoTemplate.save(ProjectEntity(projectOne)) @@ -220,11 +221,13 @@ class ProjectControllerIntTest : BaseIntTest() { private fun configureAdminToken() { tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local") + adminSub = getSubClaim(tokenAdmin) } private fun cleanUp() { mongoTemplate.findAllAndRemove(Query(), ProjectEntity::class.java) tokenAdmin = "n/a" + adminSub = "n/a" } } \ No newline at end of file diff --git a/security-c4po-api/src/test/resources/collections/projects.json b/security-c4po-api/src/test/resources/collections/projects.json index 8f89aa9..e0d402e 100644 --- a/security-c4po-api/src/test/resources/collections/projects.json +++ b/security-c4po-api/src/test/resources/collections/projects.json @@ -72,7 +72,7 @@ "status": "PAUSED" } ], - "createdBy": "2b4615ec-2f58-4d6a-8543-0c764d64455a" + "createdBy": "16a52c3d-998b-4f2d-badb-1f369d95a690" }, "_class": "com.securityc4po.api.project.ProjectEntity" },{ @@ -91,7 +91,7 @@ "state": "NEW", "version": "1.0", "projectPentests": [], - "createdBy": "5e741fe5-591f-48d1-afef-4e59ff5d8f78" + "createdBy": "16a52c3d-998b-4f2d-badb-1f369d95a690" }, "_class": "com.securityc4po.api.project.ProjectEntity" },{ @@ -467,7 +467,7 @@ "status": "PAUSED" } ], - "createdBy": "20c3059c-0b3c-4d74-9449-472bd87f3544" + "createdBy": "16a52c3d-998b-4f2d-badb-1f369d95a690" }, "_class": "com.securityc4po.api.project.ProjectEntity" }] \ No newline at end of file diff --git a/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/report/ReportController.kt b/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/report/ReportController.kt index 07802fb..4b1b6cb 100644 --- a/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/report/ReportController.kt +++ b/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/report/ReportController.kt @@ -27,7 +27,11 @@ class ReportController(private val apiService: APIService, private val reportSer "/{projectId}/pdf/{reportLanguage}", produces = [MediaType.APPLICATION_PDF_VALUE] ) - fun downloadPentestReportPDF(@PathVariable(value = "projectId") projectId: String, @PathVariable(value = "reportLanguage") reportLanguage: String, @AuthenticationPrincipal user: Appuser): Mono> { + fun downloadPentestReportPDF( + @PathVariable(value = "projectId") projectId: String, + @PathVariable(value = "reportLanguage") reportLanguage: String, + @AuthenticationPrincipal user: Appuser + ): Mono> { return this.apiService.requestProjectReportDataById(projectId, user.token).flatMap {projectReport -> this.reportService.createReport(projectReport, "pdf", reportLanguage).map { reportClassLoaderFilePath -> ResponseEntity.ok().body(reportClassLoaderFilePath)