Skip to content

Commit

Permalink
Delete disallowed personal mobile devices on ACL changes
Browse files Browse the repository at this point in the history
  • Loading branch information
akheron committed Dec 16, 2024
1 parent 6da58d4 commit 5c7b891
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@ import com.github.kittinunf.fuel.core.isSuccessful
import com.github.kittinunf.fuel.jackson.responseObject
import fi.espoo.evaka.FullApplicationTest
import fi.espoo.evaka.attendance.getOccupancyCoefficientsByUnit
import fi.espoo.evaka.pairing.listPersonalDevices
import fi.espoo.evaka.pis.TemporaryEmployee
import fi.espoo.evaka.pis.controllers.PinCode
import fi.espoo.evaka.pis.deactivateInactiveEmployees
import fi.espoo.evaka.shared.DaycareId
import fi.espoo.evaka.shared.EmployeeId
import fi.espoo.evaka.shared.async.AsyncJob
import fi.espoo.evaka.shared.async.AsyncJobRunner
import fi.espoo.evaka.shared.auth.AuthenticatedUser
import fi.espoo.evaka.shared.auth.DaycareAclRow
import fi.espoo.evaka.shared.auth.DaycareAclRowEmployee
import fi.espoo.evaka.shared.auth.UserRole
import fi.espoo.evaka.shared.auth.asUser
import fi.espoo.evaka.shared.dev.DevDaycare
import fi.espoo.evaka.shared.dev.DevEmployee
import fi.espoo.evaka.shared.dev.DevPersonalMobileDevice
import fi.espoo.evaka.shared.dev.insert
import fi.espoo.evaka.shared.domain.EvakaClock
import fi.espoo.evaka.shared.domain.HelsinkiDateTime
Expand All @@ -45,6 +49,7 @@ import org.springframework.beans.factory.annotation.Autowired

class UnitAclControllerIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) {
@Autowired private lateinit var unitAclController: UnitAclController
@Autowired private lateinit var asyncJobRunner: AsyncJobRunner<AsyncJob>
private val employee =
DaycareAclRowEmployee(
id = EmployeeId(UUID.randomUUID()),
Expand Down Expand Up @@ -273,6 +278,52 @@ class UnitAclControllerIntegrationTest : FullApplicationTest(resetDbBeforeEach =
assertEquals(MessageAccountState.INACTIVE_ACCOUNT, employeeMessageAccountState())
}

@Test
fun `personal mobile device is deleted in async job after supervisor role is removed`() {
val supervisor1 = DevEmployee()
val device1 = DevPersonalMobileDevice(employeeId = supervisor1.id)
val supervisor2 = DevEmployee()
val device2 = DevPersonalMobileDevice(employeeId = supervisor2.id)
db.transaction { tx ->
tx.insert(supervisor1, unitRoles = mapOf(testDaycare.id to UserRole.UNIT_SUPERVISOR))
tx.insert(device1)
tx.insert(
supervisor2,
unitRoles =
mapOf(
testDaycare.id to UserRole.UNIT_SUPERVISOR,
testDaycare2.id to UserRole.UNIT_SUPERVISOR,
),
)
tx.insert(device2)
}

val now = HelsinkiDateTime.of(LocalDate.of(2024, 12, 13), LocalTime.of(12, 0))
unitAclController.deleteUnitSupervisor(
dbInstance(),
admin,
MockEvakaClock(now),
testDaycare.id,
supervisor1.id,
)
unitAclController.deleteUnitSupervisor(
dbInstance(),
admin,
MockEvakaClock(now),
testDaycare.id,
supervisor2.id,
)
asyncJobRunner.runPendingJobsSync(MockEvakaClock(now.plusHours(1)))

db.read { tx ->
assertEquals(emptyList(), tx.listPersonalDevices(employeeId = supervisor1.id))
assertEquals(
listOf(device2.id),
tx.listPersonalDevices(employeeId = supervisor2.id).map { it.id },
)
}
}

@Test
fun temporaryEmployeeCrud() {
val dateTime = HelsinkiDateTime.of(LocalDate.of(2023, 3, 29), LocalTime.of(8, 37))
Expand Down
15 changes: 0 additions & 15 deletions service/src/main/kotlin/fi/espoo/evaka/daycare/UnitAcl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,10 @@
package fi.espoo.evaka.daycare

import fi.espoo.evaka.messaging.deactivateEmployeeMessageAccount
import fi.espoo.evaka.shared.DaycareId
import fi.espoo.evaka.shared.EmployeeId
import fi.espoo.evaka.shared.auth.UserRole
import fi.espoo.evaka.shared.auth.clearDaycareGroupAcl
import fi.espoo.evaka.shared.auth.deleteDaycareAclRow
import fi.espoo.evaka.shared.auth.hasAnyDaycareAclRow
import fi.espoo.evaka.shared.db.Database

fun removeDaycareAclForRole(
tx: Database.Transaction,
daycareId: DaycareId,
employeeId: EmployeeId,
role: UserRole,
) {
tx.clearDaycareGroupAcl(daycareId, employeeId)
tx.deleteDaycareAclRow(daycareId, employeeId, role)
deactivatePersonalMessageAccountIfNeeded(tx, employeeId)
}

fun deactivatePersonalMessageAccountIfNeeded(tx: Database.Transaction, employeeId: EmployeeId) {
if (!tx.hasAnyDaycareAclRow(employeeId)) {
// Deactivate the message account when the employee is not in any unit anymore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import fi.espoo.evaka.absence.getDaycareIdByGroup
import fi.espoo.evaka.attendance.OccupancyCoefficientUpsert
import fi.espoo.evaka.attendance.getOccupancyCoefficientForEmployeeInUnit
import fi.espoo.evaka.attendance.upsertOccupancyCoefficient
import fi.espoo.evaka.daycare.removeDaycareAclForRole
import fi.espoo.evaka.daycare.deactivatePersonalMessageAccountIfNeeded
import fi.espoo.evaka.daycare.domain.ProviderType
import fi.espoo.evaka.messaging.deactivateEmployeeMessageAccount
import fi.espoo.evaka.messaging.upsertEmployeeMessageAccount
import fi.espoo.evaka.pairing.deletePersonalDevices
import fi.espoo.evaka.pis.Employee
import fi.espoo.evaka.pis.NewEmployee
import fi.espoo.evaka.pis.TemporaryEmployee
Expand All @@ -26,13 +28,16 @@ import fi.espoo.evaka.pis.upsertPinCode
import fi.espoo.evaka.shared.DaycareId
import fi.espoo.evaka.shared.EmployeeId
import fi.espoo.evaka.shared.GroupId
import fi.espoo.evaka.shared.async.AsyncJob
import fi.espoo.evaka.shared.async.AsyncJobRunner
import fi.espoo.evaka.shared.auth.AuthenticatedUser
import fi.espoo.evaka.shared.auth.DaycareAclRow
import fi.espoo.evaka.shared.auth.UserRole
import fi.espoo.evaka.shared.auth.clearDaycareGroupAcl
import fi.espoo.evaka.shared.auth.deleteDaycareAclRow
import fi.espoo.evaka.shared.auth.getDaycareAclRows
import fi.espoo.evaka.shared.auth.hasAnyDaycareAclRow
import fi.espoo.evaka.shared.auth.hasRoleInAnyUnitWithProviderType
import fi.espoo.evaka.shared.auth.insertDaycareAclRow
import fi.espoo.evaka.shared.auth.insertDaycareGroupAcl
import fi.espoo.evaka.shared.db.Database
Expand All @@ -49,8 +54,10 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class UnitAclController(private val accessControl: AccessControl) {

class UnitAclController(
private val accessControl: AccessControl,
private val asyncJobRunner: AsyncJobRunner<AsyncJob>,
) {
val coefficientPositiveValue = BigDecimal("7.00")
val coefficientNegativeValue = BigDecimal("0.00")

Expand Down Expand Up @@ -118,7 +125,13 @@ class UnitAclController(private val accessControl: AccessControl) {
daycareId,
)
validateIsPermanentEmployee(it, employeeId)
removeDaycareAclForRole(it, daycareId, employeeId, UserRole.UNIT_SUPERVISOR)
removeDaycareAclForRole(
it,
clock.now(),
daycareId,
employeeId,
UserRole.UNIT_SUPERVISOR,
)
}
}
Audit.UnitAclDelete.log(targetId = AuditId(daycareId), objectId = AuditId(employeeId))
Expand All @@ -144,6 +157,7 @@ class UnitAclController(private val accessControl: AccessControl) {
validateIsPermanentEmployee(it, employeeId)
removeDaycareAclForRole(
it,
clock.now(),
daycareId,
employeeId,
UserRole.SPECIAL_EDUCATION_TEACHER,
Expand Down Expand Up @@ -173,6 +187,7 @@ class UnitAclController(private val accessControl: AccessControl) {
validateIsPermanentEmployee(it, employeeId)
removeDaycareAclForRole(
it,
clock.now(),
daycareId,
employeeId,
UserRole.EARLY_CHILDHOOD_EDUCATION_SECRETARY,
Expand Down Expand Up @@ -200,7 +215,7 @@ class UnitAclController(private val accessControl: AccessControl) {
daycareId,
)
validateIsPermanentEmployee(it, employeeId)
removeDaycareAclForRole(it, daycareId, employeeId, UserRole.STAFF)
removeDaycareAclForRole(it, clock.now(), daycareId, employeeId, UserRole.STAFF)
}
}
Audit.UnitAclDelete.log(targetId = AuditId(daycareId), objectId = AuditId(employeeId))
Expand Down Expand Up @@ -605,4 +620,46 @@ class UnitAclController(private val accessControl: AccessControl) {

fun parseCoefficientValue(bool: Boolean) =
if (bool) coefficientPositiveValue else coefficientNegativeValue

init {
asyncJobRunner.registerHandler(::deletePersonalMobileDevicesIfNeeded)
}

private fun deletePersonalMobileDevicesIfNeeded(
db: Database.Connection,
clock: EvakaClock,
job: AsyncJob.DeletePersonalDevicesIfNeeded,
) {
db.transaction { tx ->
if (
!tx.hasRoleInAnyUnitWithProviderType(
job.employeeId,
UserRole.UNIT_SUPERVISOR,
ProviderType.MUNICIPAL,
)
) {
tx.deletePersonalDevices(job.employeeId)
}
}
}

fun removeDaycareAclForRole(
tx: Database.Transaction,
now: HelsinkiDateTime,
daycareId: DaycareId,
employeeId: EmployeeId,
role: UserRole,
) {
tx.clearDaycareGroupAcl(daycareId, employeeId)
tx.deleteDaycareAclRow(daycareId, employeeId, role)
deactivatePersonalMessageAccountIfNeeded(tx, employeeId)

// Delete personal mobile devices after a while, in case the employee is added back to this
// or some other unit
asyncJobRunner.plan(
tx,
listOf(AsyncJob.DeletePersonalDevicesIfNeeded(employeeId)),
runAt = now.plusHours(1),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ fun Database.Transaction.renameDevice(id: MobileDeviceId, name: String) {

fun Database.Transaction.deleteDevice(id: MobileDeviceId) =
createUpdate { sql("DELETE FROM mobile_device WHERE id = ${bind(id)}") }.execute()

fun Database.Transaction.deletePersonalDevices(employee: EmployeeId) = execute {
sql("DELETE FROM mobile_device WHERE employee_id = ${bind(employee)}")
}
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ sealed interface AsyncJob : AsyncJobPayload {
override val user: AuthenticatedUser? = null
}

data class DeletePersonalDevicesIfNeeded(val employeeId: EmployeeId) : AsyncJob {
override val user: AuthenticatedUser? = null
}

companion object {
val main =
AsyncJobRunner.Pool(
Expand Down Expand Up @@ -458,6 +462,7 @@ sealed interface AsyncJob : AsyncJobPayload {
PlacementTool::class,
PlacementToolFromSSN::class,
InvoiceCorrectionMigration::class,
DeletePersonalDevicesIfNeeded::class,
),
)
val email =
Expand Down
22 changes: 22 additions & 0 deletions service/src/main/kotlin/fi/espoo/evaka/shared/auth/AclQueries.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package fi.espoo.evaka.shared.auth

import fi.espoo.evaka.daycare.domain.ProviderType
import fi.espoo.evaka.shared.DaycareId
import fi.espoo.evaka.shared.EmployeeId
import fi.espoo.evaka.shared.GroupId
Expand Down Expand Up @@ -73,6 +74,27 @@ fun Database.Read.hasAnyDaycareAclRow(employeeId: EmployeeId): Boolean =
}
.exactlyOne<Boolean>()

fun Database.Read.hasRoleInAnyUnitWithProviderType(
employeeId: EmployeeId,
role: UserRole,
providerType: ProviderType,
): Boolean =
createQuery {
sql(
"""
SELECT EXISTS (
SELECT FROM daycare_acl acl
JOIN daycare d ON d.id = acl.daycare_id
WHERE
acl.employee_id = ${bind(employeeId)} AND
acl.role = ${bind(role)} AND
d.provider_type = ${bind(providerType)}
)
"""
)
}
.exactlyOne<Boolean>()

fun Database.Transaction.insertDaycareAclRow(
daycareId: DaycareId,
employeeId: EmployeeId,
Expand Down

0 comments on commit 5c7b891

Please sign in to comment.