Skip to content

Commit

Permalink
HAI-1527 Audit logging for permission updates
Browse files Browse the repository at this point in the history
  • Loading branch information
corvidian committed Aug 31, 2023
1 parent 390d7be commit 9fc4e10
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.containsExactlyInAnyOrder
import assertk.assertions.each
import assertk.assertions.first
import assertk.assertions.hasClass
import assertk.assertions.hasSize
import assertk.assertions.isEmpty
Expand All @@ -23,7 +24,12 @@ import fi.hel.haitaton.hanke.factory.HankeFactory
import fi.hel.haitaton.hanke.factory.HankeFactory.Companion.withGeneratedOmistaja
import fi.hel.haitaton.hanke.factory.HankeFactory.Companion.withYhteystiedot
import fi.hel.haitaton.hanke.factory.HankeYhteystietoFactory
import fi.hel.haitaton.hanke.logging.AuditLogRepository
import fi.hel.haitaton.hanke.logging.ObjectType
import fi.hel.haitaton.hanke.logging.Operation
import fi.hel.haitaton.hanke.logging.UserRole
import fi.hel.haitaton.hanke.test.Asserts.isRecent
import fi.hel.haitaton.hanke.toChangeLogJsonString
import java.time.OffsetDateTime
import java.util.UUID
import org.junit.jupiter.api.Nested
Expand Down Expand Up @@ -51,6 +57,7 @@ class HankeKayttajaServiceITest : DatabaseTest() {
@Autowired private lateinit var hankeKayttajaRepository: HankeKayttajaRepository
@Autowired private lateinit var permissionRepository: PermissionRepository
@Autowired private lateinit var roleRepository: RoleRepository
@Autowired private lateinit var auditLogRepository: AuditLogRepository

@Autowired private lateinit var permissionService: PermissionService

Expand Down Expand Up @@ -391,6 +398,36 @@ class HankeKayttajaServiceITest : DatabaseTest() {
}
}

@Test
fun `Writes permission update to audit log`() {
val hanke = hankeFactory.save()
val kayttaja = saveUserAndPermission(hanke.id!!)
val updates = mapOf(kayttaja.id to Role.HANKEMUOKKAUS)
auditLogRepository.deleteAll()

hankeKayttajaService.updatePermissions(hanke, updates, false, USERNAME)

val logs = auditLogRepository.findAll()
assertThat(logs).hasSize(1)
assertThat(logs)
.first()
.transform { it.message.auditEvent }
.all {
transform { it.operation }.isEqualTo(Operation.UPDATE)
transform { it.actor.role }.isEqualTo(UserRole.USER)
transform { it.actor.userId }.isEqualTo(USERNAME)
transform { it.target.id }.isEqualTo(kayttaja.permission?.id.toString())
transform { it.target.type }.isEqualTo(ObjectType.PERMISSION)
val permission = kayttaja.permission!!.toDomain()
transform { it.target.objectBefore }
.isEqualTo(permission.toChangeLogJsonString())
transform { it.target.objectAfter }
.isEqualTo(
permission.copy(role = Role.HANKEMUOKKAUS).toChangeLogJsonString()
)
}
}

@Test
fun `Updates role to tunniste if permission doesn't exist`() {
val hanke = hankeFactory.save()
Expand All @@ -406,6 +443,34 @@ class HankeKayttajaServiceITest : DatabaseTest() {
}
}

@Test
fun `Writes tunniste update to audit log`() {
val hanke = hankeFactory.save()
val kayttaja = saveUserAndToken(hanke.id!!, "Toinen Tohelo", "[email protected]")
val updates = mapOf(kayttaja.id to Role.HANKEMUOKKAUS)
auditLogRepository.deleteAll()

hankeKayttajaService.updatePermissions(hanke, updates, false, USERNAME)

val logs = auditLogRepository.findAll()
assertThat(logs).hasSize(1)
assertThat(logs)
.first()
.transform { it.message.auditEvent }
.all {
transform { it.operation }.isEqualTo(Operation.UPDATE)
transform { it.actor.role }.isEqualTo(UserRole.USER)
transform { it.actor.userId }.isEqualTo(USERNAME)
transform { it.target.id }.isEqualTo(kayttaja.kayttajaTunniste?.id.toString())
transform { it.target.type }.isEqualTo(ObjectType.KAYTTAJA_TUNNISTE)
val tunniste =
kayttaja.kayttajaTunniste!!.toDomain().copy(hankeKayttajaId = kayttaja.id)
transform { it.target.objectBefore }.isEqualTo(tunniste.toChangeLogJsonString())
transform { it.target.objectAfter }
.isEqualTo(tunniste.copy(role = Role.HANKEMUOKKAUS).toChangeLogJsonString())
}
}

@Test
fun `Updates role to only permission if both permission and tunniste exist`() {
val hanke = hankeFactory.save()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ enum class Status {
}

enum class ObjectType {
YHTEYSTIETO,
ALLU_CUSTOMER,
APPLICATION,
ALLU_CONTACT,
ALLU_CUSTOMER,
GDPR_RESPONSE,
HANKE,
HANKE_KAYTTAJA,
APPLICATION,
KAYTTAJA_TUNNISTE,
PERMISSION,
YHTEYSTIETO,
}

enum class UserRole {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package fi.hel.haitaton.hanke.logging

import fi.hel.haitaton.hanke.application.Application
import fi.hel.haitaton.hanke.permissions.KayttajaTunniste
import fi.hel.haitaton.hanke.permissions.PermissionEntity
import fi.hel.haitaton.hanke.permissions.Role
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@Service
class HankeKayttajaLoggingService(private val auditLogService: AuditLogService) {

@Transactional(propagation = Propagation.MANDATORY)
fun logCreate(savedApplication: Application, userId: String) {
auditLogService.create(
AuditLogService.createEntry(userId, ObjectType.APPLICATION, savedApplication)
)
}

@Transactional(propagation = Propagation.MANDATORY)
fun logUpdate(roleBefore: Role, permissionEntityAfter: PermissionEntity, userId: String) {
val permissionAfter = permissionEntityAfter.toDomain()
val permissionBefore = permissionAfter.copy(role = roleBefore)

AuditLogService.updateEntry(
userId,
ObjectType.PERMISSION,
permissionBefore,
permissionAfter,
)
?.let { auditLogService.create(it) }
}

@Transactional(propagation = Propagation.MANDATORY)
fun logUpdate(
kayttajaTunnisteBefore: KayttajaTunniste,
kayttajaTunnisteAfter: KayttajaTunniste,
userId: String
) {
AuditLogService.updateEntry(
userId,
ObjectType.KAYTTAJA_TUNNISTE,
kayttajaTunnisteBefore,
kayttajaTunnisteAfter,
)
?.let { auditLogService.create(it) }
}

@Transactional(propagation = Propagation.MANDATORY)
fun logDelete(applicationBefore: Application, userId: String) {
auditLogService.create(
AuditLogService.deleteEntry(userId, ObjectType.APPLICATION, applicationBefore)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fi.hel.haitaton.hanke.configuration.Feature
import fi.hel.haitaton.hanke.configuration.FeatureFlags
import fi.hel.haitaton.hanke.domain.Hanke
import fi.hel.haitaton.hanke.domain.Perustaja
import fi.hel.haitaton.hanke.logging.HankeKayttajaLoggingService
import java.util.UUID
import mu.KotlinLogging
import org.springframework.stereotype.Service
Expand All @@ -20,6 +21,7 @@ class HankeKayttajaService(
private val roleRepository: RoleRepository,
private val permissionService: PermissionService,
private val featureFlags: FeatureFlags,
private val logService: HankeKayttajaLoggingService,
) {

@Transactional(readOnly = true)
Expand Down Expand Up @@ -97,9 +99,14 @@ class HankeKayttajaService(

kayttajat.forEach { kayttaja ->
if (kayttaja.permission != null) {
val roleBefore = kayttaja.permission.role.role
kayttaja.permission.role = roleRepository.findOneByRole(updates[kayttaja.id]!!)
logService.logUpdate(roleBefore, kayttaja.permission, userId)
} else {
kayttaja.kayttajaTunniste!!.role = updates[kayttaja.id]!!
val kayttajaTunnisteBefore = kayttaja.kayttajaTunniste!!.toDomain()
kayttaja.kayttajaTunniste.role = updates[kayttaja.id]!!
val kayttajaTunnisteAfter = kayttaja.kayttajaTunniste.toDomain()
logService.logUpdate(kayttajaTunnisteBefore, kayttajaTunnisteAfter, userId)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package fi.hel.haitaton.hanke.permissions

import com.fasterxml.jackson.annotation.JsonView
import fi.hel.haitaton.hanke.ChangeLogView
import fi.hel.haitaton.hanke.domain.HasId
import fi.hel.haitaton.hanke.getCurrentTimeUTC
import jakarta.persistence.Column
import jakarta.persistence.Entity
Expand All @@ -15,6 +18,16 @@ import kotlin.streams.asSequence
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@JsonView(ChangeLogView::class)
data class KayttajaTunniste(
override val id: UUID,
val tunniste: String,
val createdAt: OffsetDateTime,
val sentAt: OffsetDateTime?,
var role: Role,
val hankeKayttajaId: UUID?
) : HasId<UUID>

@Entity
@Table(name = "kayttaja_tunniste")
class KayttajaTunnisteEntity(
Expand All @@ -26,6 +39,8 @@ class KayttajaTunnisteEntity(
@OneToOne(mappedBy = "kayttajaTunniste") val hankeKayttaja: HankeKayttajaEntity?
) {

fun toDomain() = KayttajaTunniste(id, tunniste, createdAt, sentAt, role, hankeKayttaja?.id)

companion object {
private const val tokenLength: Int = 24
private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package fi.hel.haitaton.hanke.permissions

import com.fasterxml.jackson.annotation.JsonView
import fi.hel.haitaton.hanke.ChangeLogView
import fi.hel.haitaton.hanke.domain.HasId
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
Expand Down Expand Up @@ -52,4 +55,14 @@ class PermissionEntity(
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "roleid")
var role: RoleEntity,
)
) {
fun toDomain() = Permission(id, userId, hankeId, role.role)
}

@JsonView(ChangeLogView::class)
data class Permission(
override val id: Int,
val userId: String,
val hankeId: Int,
var role: Role,
) : HasId<Int>

0 comments on commit 9fc4e10

Please sign in to comment.