feat: As an user I want to see only the projects I created

This commit is contained in:
Marcel Haag 2023-06-05 09:13:09 +02:00
parent b6ec78ef49
commit a883e052e8
12 changed files with 89 additions and 61 deletions

View File

@ -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<GrantedAuthority> {
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 {

View File

@ -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<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken> {
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 {

View File

@ -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<UserDetails> {
return Appuser().toMono()
}
}

View File

@ -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

View File

@ -24,7 +24,8 @@ data class Project(
val state: PentestState,
val version: String,
var projectPentests: List<ProjectPentest> = 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
)
}

View File

@ -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<ProjectController>()
@GetMapping
fun getProjects(): Mono<ResponseEntity<List<ResponseBody>>> {
return projectService.getProjects().map { projectList ->
fun getProjects(
@AuthenticationPrincipal user: Appuser
): Mono<ResponseEntity<List<ResponseBody>>> {
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<ResponseEntity<ResponseBody>> {
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<ResponseEntity<ResponseBody>> {
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<ResponseEntity<ResponseBody>> {
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<ResponseEntity<ResponseBody>> {
return this.projectService.updateProject(id, body).map {
return this.projectService.updateProject(id, body, user.getSub()).map {
ResponseEntity.accepted().body(it.toProjectResponseBody())
}
}

View File

@ -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<List<Project>> {
return projectRepository.findAll().collectList().map {
fun getProjects(userSub: String): Mono<List<Project>> {
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<Project> {
return projectRepository.findProjectById(projectId).map {
fun getProjectById(projectId: String, userSub: String): Mono<Project> {
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<Project> {
fun saveProject(body: ProjectRequestBody, userSub: String): Mono<Project> {
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<Project> {
fun updateProject(id: String, body: ProjectRequestBody, userSub: String): Mono<Project> {
validate(
require = body.isValid(),
logging = { logger.warn("Project not valid.") },

View File

@ -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 {

View File

@ -98,7 +98,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
projectPentests = emptyList<ProjectPentest>(),
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<ProjectPentest>(),
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<ProjectPentest>(),
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<ProjectPentest>(),
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<ProjectPentest>(),
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<ProjectPentest>(),
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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}]

View File

@ -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<ResponseEntity<ByteArray>> {
fun downloadPentestReportPDF(
@PathVariable(value = "projectId") projectId: String,
@PathVariable(value = "reportLanguage") reportLanguage: String,
@AuthenticationPrincipal user: Appuser
): Mono<ResponseEntity<ByteArray>> {
return this.apiService.requestProjectReportDataById(projectId, user.token).flatMap {projectReport ->
this.reportService.createReport(projectReport, "pdf", reportLanguage).map { reportClassLoaderFilePath ->
ResponseEntity.ok().body(reportClassLoaderFilePath)