Skip to content

Commit

Permalink
HAI-1527 API for changing permissions
Browse files Browse the repository at this point in the history
Add API for changing the permissions of hanke kayttajat.

Audit logging will be added in a separate PR.

Add a security scheme to the OpenAPI configuration. This enables the use
of the Swagger UI by copying over your bearer token. Reformat some
OpenAPI descriptions, since they were shown as code blocks.
  • Loading branch information
corvidian committed Aug 22, 2023
1 parent 49c3d8d commit 7b272c5
Show file tree
Hide file tree
Showing 14 changed files with 1,121 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import assertk.assertions.hasSize
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import fi.hel.haitaton.hanke.ControllerTest
import fi.hel.haitaton.hanke.HankeError
import fi.hel.haitaton.hanke.HankeNotFoundException
import fi.hel.haitaton.hanke.HankeService
import fi.hel.haitaton.hanke.IntegrationTestConfiguration
import fi.hel.haitaton.hanke.andReturnBody
import fi.hel.haitaton.hanke.domain.Hanke
import fi.hel.haitaton.hanke.factory.HankeFactory
import fi.hel.haitaton.hanke.factory.HankeKayttajaFactory
import fi.hel.haitaton.hanke.hankeError
import fi.hel.haitaton.hanke.hasSameElementsAs
import fi.hel.haitaton.hanke.logging.DisclosureLogService
import fi.hel.haitaton.hanke.permissions.PermissionCode.VIEW
Expand All @@ -22,8 +25,11 @@ import io.mockk.every
import io.mockk.justRun
import io.mockk.verify
import io.mockk.verifyOrder
import io.mockk.verifySequence
import java.util.UUID
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.test.autoconfigure.web.servlet.WebMvcTest
Expand All @@ -33,6 +39,7 @@ import org.springframework.security.test.context.support.WithMockUser
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

private const val USERNAME = "testUser"
Expand Down Expand Up @@ -60,54 +67,268 @@ class HankeKayttajaControllerITest(@Autowired override val mockMvc: MockMvc) : C
confirmVerified(hankeKayttajaService, hankeService, permissionService)
}

@Test
fun `getHankeKayttajat when valid request returns users of given hanke and logs audit`() {
val hanke = HankeFactory.create()
val testData = HankeKayttajaFactory.generateHankeKayttajat()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
justRun { permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW) }
every { hankeKayttajaService.getKayttajatByHankeId(hanke.id!!) } returns testData

val response: HankeKayttajaResponse =
getHankeKayttajat().andExpect(status().isOk).andReturnBody()

assertThat(response.kayttajat).hasSize(3)
with(response.kayttajat.first()) {
assertThat(id).isNotNull()
assertThat(nimi).isEqualTo("test name1")
assertThat(sahkoposti).isEqualTo("email.1.address.com")
assertThat(tunnistautunut).isEqualTo(false)
@Nested
inner class GetHankeKayttajat {

@Test
fun `With valid request returns users of given hanke and logs audit`() {
val hanke = HankeFactory.create()
val testData = HankeKayttajaFactory.generateHankeKayttajat()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
justRun { permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW) }
every { hankeKayttajaService.getKayttajatByHankeId(hanke.id!!) } returns testData

val response: HankeKayttajaResponse =
getHankeKayttajat().andExpect(status().isOk).andReturnBody()

assertThat(response.kayttajat).hasSize(3)
with(response.kayttajat.first()) {
assertThat(id).isNotNull()
assertThat(nimi).isEqualTo("test name1")
assertThat(sahkoposti).isEqualTo("email.1.address.com")
assertThat(tunnistautunut).isEqualTo(false)
}
assertThat(response.kayttajat).hasSameElementsAs(testData)
verifyOrder {
hankeService.findHankeOrThrow(HANKE_TUNNUS)
permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW)
hankeKayttajaService.getKayttajatByHankeId(hanke.id!!)
disclosureLogService.saveDisclosureLogsForHankeKayttajat(
response.kayttajat,
USERNAME
)
}
}

@Test
fun `getHankeKayttajat when no permission for hanke returns not found`() {
val hanke = HankeFactory.create()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
every { permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW) } throws
HankeNotFoundException(HANKE_TUNNUS)

getHankeKayttajat().andExpect(status().isNotFound)

verifyOrder {
hankeService.findHankeOrThrow(HANKE_TUNNUS)
permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW)
}
verify { hankeKayttajaService wasNot Called }
}
assertThat(response.kayttajat).hasSameElementsAs(testData)
verifyOrder {
hankeService.findHankeOrThrow(HANKE_TUNNUS)
permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW)
hankeKayttajaService.getKayttajatByHankeId(hanke.id!!)
disclosureLogService.saveDisclosureLogsForHankeKayttajat(response.kayttajat, USERNAME)

@Test
@WithAnonymousUser
fun `When unauthorized token returns 401 `() {
getHankeKayttajat().andExpect(status().isUnauthorized)
}

private fun getHankeKayttajat(): ResultActions = get("/hankkeet/$HANKE_TUNNUS/kayttajat")
}

@Test
fun `getHankeKayttajat when no permission for hanke returns not found`() {
val hanke = HankeFactory.create()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
every { permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW) } throws
HankeNotFoundException(HANKE_TUNNUS)
@Nested
inner class UpdatePermissions {
private val url = "/hankkeet/$HANKE_TUNNUS/kayttajat"
private val hankeKayttajaId = UUID.fromString("5d67712f-ea0b-490c-957f-9b30bddb848c")

@Test
@WithAnonymousUser
fun `Returns 401 when unauthorized token`() {
put(url).andExpect(status().isUnauthorized)
}

@Test
fun `Returns not found when no permission for hanke`() {
val hanke = HankeFactory.create()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
every {
permissionService.verifyHankeUserAuthorization(
USERNAME,
hanke,
PermissionCode.MODIFY_EDIT_PERMISSIONS
)
} throws HankeNotFoundException(HANKE_TUNNUS)

getHankeKayttajat().andExpect(status().isNotFound)
put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isNotFound)

verifyOrder {
hankeService.findHankeOrThrow(HANKE_TUNNUS)
permissionService.verifyHankeUserAuthorization(USERNAME, hanke, VIEW)
verifySequence {
hankeService.findHankeOrThrow(HANKE_TUNNUS)
permissionService.verifyHankeUserAuthorization(
USERNAME,
hanke,
PermissionCode.MODIFY_EDIT_PERMISSIONS
)
}
verify { hankeKayttajaService wasNot Called }
}
verify { hankeKayttajaService wasNot Called }
}

@Test
@WithAnonymousUser
fun `getHankeKayttajat when unauthorized token returns 401 `() {
getHankeKayttajat().andExpect(status().isUnauthorized)
}
@Test
fun `Returns 204 on success`() {
val hanke = HankeFactory.create()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
justRun {
permissionService.verifyHankeUserAuthorization(
USERNAME,
hanke,
PermissionCode.MODIFY_EDIT_PERMISSIONS
)
}
every {
permissionService.hasPermission(
hanke.id!!,
USERNAME,
PermissionCode.MODIFY_DELETE_PERMISSIONS
)
} returns false
val updates = mapOf(hankeKayttajaId to Role.HANKEMUOKKAUS)
justRun { hankeKayttajaService.updatePermissions(hanke, updates, false, USERNAME) }

put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isNoContent)
.andExpect(content().string(""))

verifyCalls(hanke, updates)
}

@Test
fun `Calls service with admin permission when user has them`() {
val hanke = HankeFactory.create()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
justRun {
permissionService.verifyHankeUserAuthorization(
USERNAME,
hanke,
PermissionCode.MODIFY_EDIT_PERMISSIONS
)
}
every {
permissionService.hasPermission(
hanke.id!!,
USERNAME,
PermissionCode.MODIFY_DELETE_PERMISSIONS
)
} returns true
val updates = mapOf(hankeKayttajaId to Role.HANKEMUOKKAUS)
justRun { hankeKayttajaService.updatePermissions(hanke, updates, true, USERNAME) }

private fun getHankeKayttajat(): ResultActions = get("/hankkeet/$HANKE_TUNNUS/kayttajat")
put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isNoContent)

verifyCalls(hanke, updates, deleteAdminPermission = true)
}

@Test
fun `Returns forbidden when missing admin permissions`() {
val (hanke, updates) = setupForException(MissingAdminPermissionException(USERNAME))

put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isForbidden)
.andExpect(hankeError(HankeError.HAI0005))

verifyCalls(hanke, updates)
}

@Test
fun `Returns forbidden when changing own permissions`() {
val (hanke, updates) = setupForException(ChangingOwnPermissionException(USERNAME))

put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isForbidden)
.andExpect(hankeError(HankeError.HAI4002))

verifyCalls(hanke, updates)
}

@Test
fun `Returns internal server error if there are users without either permission or tunniste`() {
val (hanke, updates) =
setupForException(UsersWithoutRolesException(missingIds = listOf(hankeKayttajaId)))

put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isInternalServerError)
.andExpect(hankeError(HankeError.HAI4003))

verifyCalls(hanke, updates)
}

@Test
fun `Returns conflict if there would be no admins remaining`() {
val (hanke, updates) = setupForException { hanke -> NoAdminRemainingException(hanke) }

put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isConflict)
.andExpect(hankeError(HankeError.HAI4003))

verifyCalls(hanke, updates)
}

@Test
fun `Returns bad request if there would be no admins remaining`() {
val (hanke, updates) =
setupForException { hanke ->
HankeKayttajatNotFoundException(listOf(hankeKayttajaId), hanke)
}

put(url, PermissionUpdate(listOf(PermissionDto(hankeKayttajaId, Role.HANKEMUOKKAUS))))
.andExpect(status().isBadRequest)
.andExpect(hankeError(HankeError.HAI4001))

verifyCalls(hanke, updates)
}

private fun verifyCalls(
hanke: Hanke,
updates: Map<UUID, Role>,
deleteAdminPermission: Boolean = false,
) {
verifySequence {
hankeService.findHankeOrThrow(HANKE_TUNNUS)
permissionService.verifyHankeUserAuthorization(
USERNAME,
hanke,
PermissionCode.MODIFY_EDIT_PERMISSIONS
)
permissionService.hasPermission(
hanke.id!!,
USERNAME,
PermissionCode.MODIFY_DELETE_PERMISSIONS
)
hankeKayttajaService.updatePermissions(
hanke,
updates,
deleteAdminPermission,
USERNAME
)
}
}

private fun setupForException(ex: Throwable): Pair<Hanke, Map<UUID, Role>> =
setupForException {
ex
}

private fun setupForException(ex: (Hanke) -> Throwable): Pair<Hanke, Map<UUID, Role>> {
val hanke = HankeFactory.create()
every { hankeService.findHankeOrThrow(HANKE_TUNNUS) } returns hanke
justRun {
permissionService.verifyHankeUserAuthorization(
USERNAME,
hanke,
PermissionCode.MODIFY_EDIT_PERMISSIONS
)
}
every {
permissionService.hasPermission(
hanke.id!!,
USERNAME,
PermissionCode.MODIFY_DELETE_PERMISSIONS
)
} returns false
val updates = mapOf(hankeKayttajaId to Role.HANKEMUOKKAUS)
every { hankeKayttajaService.updatePermissions(hanke, updates, false, USERNAME) } throws
ex(hanke)

return Pair(hanke, updates)
}
}
}
Loading

0 comments on commit 7b272c5

Please sign in to comment.