Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
lock implementation
  • Loading branch information
mrFlick72 committed Nov 22, 2024
1 parent c64db17 commit 74a9ba2
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 12 deletions.
5 changes: 5 additions & 0 deletions local-environment/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ endSessionWithoutDiscovery: true
oidcEndSessionUrl: ${vauthenticator.host}/oidc/logout
auth.oidcIss: ${vauthenticator.host}

scheduled:
database-cleanup:
lock-ttl: 30000
cron: "0 * * * * *"

event:
consumer:
enable:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
package com.vauthenticator.server.job

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.queryForList
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.Clock


@Transactional
class DatabaseTtlEntryCleanJob(
private val jdbcTemplate: JdbcTemplate,
private val lockTtl: Long,
private val lockService: LockService,
private val clock: Clock
) {

private val logger = LoggerFactory.getLogger(DatabaseTtlEntryCleanJob::class.java)

@Scheduled(cron = "\${scheduled.database-cleanup.cron}")
fun execute() {
val now = clock.instant().epochSecond
try {
logger.info("Try to Schedule")

lockService.lock(lockTtl)
logger.info("Job Running")
val now = clock.instant().epochSecond

deleteOldTicket(now)
deleteOldKeys(now)
logger.info("Job Completed")
} finally {
lockService.unlock()
}

deleteOldTicket(now)
deleteOldKeys(now)
}

private fun deleteOldKeys(now: Long) {
val keysToBeDeleted =
jdbcTemplate.queryForList("SELECT key_id,key_purpose FROM KEYS WHERE key_expiration_date_timestamp < ?", now)
jdbcTemplate.queryForList(
"SELECT key_id,key_purpose FROM KEYS WHERE key_expiration_date_timestamp < ?",
now
)
keysToBeDeleted.forEach {
jdbcTemplate.update(
"DELETE FROM KEYS WHERE key_id = ? AND key_purpose = ?;", it["key_id"], it["key_purpose"]
Expand All @@ -42,4 +66,21 @@ class DatabaseTtlEntryCleanJob(
}
}

}


@Profile("!kms")
@EnableScheduling
@Configuration(proxyBeanMethods = false)
class DatabaseTtlEntryCleanJobConfig {

@Bean
fun databaseTtlEntryCleanJob(
@Value("\${scheduled.database-cleanup.lock-ttl}") lockTtl: Long,
lockService: LockService,
jdbcTemplate: JdbcTemplate
) = DatabaseTtlEntryCleanJob(jdbcTemplate, lockTtl, lockService, Clock.systemUTC())

@Bean
fun lockService(redisTemplate: RedisTemplate<String, String>) = RedisLockService(redisTemplate)
}
42 changes: 42 additions & 0 deletions src/main/kotlin/com/vauthenticator/server/job/LockService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.vauthenticator.server.job

import org.slf4j.LoggerFactory
import org.springframework.data.redis.core.RedisTemplate
import java.util.concurrent.TimeUnit


interface LockService {

fun lock(timeout: Long)

fun unlock()

}

class RedisLockService(
private val redisTemplate: RedisTemplate<String, String>
) : LockService {

private val logger = LoggerFactory.getLogger(RedisLockService::class.java)

override fun lock(timeout: Long) {
if (!acquireLockWith(timeout)) {
logger.info("lock already acquired")
Thread.sleep(timeout)
} else {
logger.info("lock acquired")
}
}

private fun acquireLockWith(timeout: Long): Boolean {
logger.info("try to acquire Lock")
return redisTemplate.opsForValue()
.setIfAbsent("lockKey", "locked", timeout, TimeUnit.MILLISECONDS)!!
}

override fun unlock() {
logger.info("lock released")
redisTemplate.opsForValue().getAndDelete("lockKey")
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.vauthenticator.server.keys.adapter.java

import com.vauthenticator.server.keys.domain.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
Expand All @@ -15,6 +16,8 @@ class KeyInitJob(
private val keyRepository: KeyRepository
) : ApplicationRunner {

val logger = LoggerFactory.getLogger(KeyInitJob::class.java)

override fun run(args: ApplicationArguments) {

if (keyStorage.signatureKeys().keys.isEmpty()) {
Expand All @@ -23,7 +26,7 @@ class KeyInitJob(
keyPurpose = KeyPurpose.SIGNATURE,
keyType = KeyType.ASYMMETRIC,
)
println(kid)
logger.info("Token Signature Key init job has been executed. Key ID generated: $kid")
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,39 @@ package com.vauthenticator.server.job

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vauthenticator.server.keys.adapter.jdbc.JdbcKeyStorage
import com.vauthenticator.server.keys.domain.*
import com.vauthenticator.server.keys.domain.DataKey
import com.vauthenticator.server.keys.domain.KeyPurpose.SIGNATURE
import com.vauthenticator.server.keys.domain.KeyType
import com.vauthenticator.server.keys.domain.Kid
import com.vauthenticator.server.keys.domain.MasterKid
import com.vauthenticator.server.support.AccountTestFixture
import com.vauthenticator.server.support.ClientAppFixture
import com.vauthenticator.server.support.JdbcUtils.jdbcTemplate
import com.vauthenticator.server.support.JdbcUtils.resetDb
import com.vauthenticator.server.support.TicketFixture
import com.vauthenticator.server.ticket.adapter.jdbc.JdbcTicketRepository
import com.vauthenticator.server.ticket.domain.TicketId
import org.junit.jupiter.api.Assertions
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.just
import io.mockk.runs
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.time.Clock
import java.time.Duration
import java.util.*


@ExtendWith(MockKExtension::class)
class DatabaseTtlEntryCleanJobTest {

@MockK
lateinit var lockService: LockService

@BeforeEach
fun setUp() {
resetDb()
Expand All @@ -31,7 +45,7 @@ class DatabaseTtlEntryCleanJobTest {
val ticketRepository = JdbcTicketRepository(jdbcTemplate, jacksonObjectMapper())
val keyStorage = JdbcKeyStorage(jdbcTemplate, Clock.systemDefaultZone())

val uut = DatabaseTtlEntryCleanJob(jdbcTemplate, Clock.systemUTC())
val uut = DatabaseTtlEntryCleanJob(jdbcTemplate, 100, lockService, Clock.systemUTC())

val kid = Kid("")
val anAccount = AccountTestFixture.anAccount()
Expand All @@ -47,11 +61,21 @@ class DatabaseTtlEntryCleanJobTest {
)
keyStorage.keyDeleteJodPlannedFor(kid, Duration.ofSeconds(-200), SIGNATURE)

every {
lockService.lock(100)
lockService.unlock()
} just runs

uut.execute()

verify {
lockService.lock(100)
lockService.unlock()
}

val actualTicket = ticketRepository.loadFor(TicketId("A_TICKET"))
Assertions.assertTrue(actualTicket.isEmpty)
Assertions.assertThrows(NoSuchElementException::class.java) {
assertTrue(actualTicket.isEmpty)
assertThrows(NoSuchElementException::class.java) {
keyStorage.findOne(kid, SIGNATURE)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.vauthenticator.server.job

import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.core.ValueOperations
import java.util.concurrent.TimeUnit

@ExtendWith(MockKExtension::class)
class RedisLockServiceTest {

@MockK
lateinit var redisTemplate: RedisTemplate<String, String>

@Test
fun `when lock and unlock`() {
val uut = RedisLockService(redisTemplate)

every { redisTemplate.opsForValue() } returns mockk<ValueOperations<String, String>> {
every { setIfAbsent("lockKey", "locked", 100, TimeUnit.MILLISECONDS) } returns true
every { getAndDelete("lockKey") } returns "lockKey"
}

uut.lock(100)
uut.unlock()
}
}

0 comments on commit 74a9ba2

Please sign in to comment.