Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up entry with ttl job #260

Merged
merged 9 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Right now it is based, as said before to the latest version on spring oauth2/ope
- Post login flow
- force to reset password
- back/front channel logout
- management api: custom actuator endpoint for more details [look here](docs/management.md)

**Storage:**

Expand Down
13 changes: 13 additions & 0 deletions docs/management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Management

Custom Actuator Endpoint

## Clean Database Entry with TTL

Actuator Clean Database enabled for database profile activated

*URI:* ```Post /actuator/database-clean-up```

*Request:* Empty request body

*Response Status:* ```204 NoContent```
3 changes: 2 additions & 1 deletion local-environment/http-client.env.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"LOCAL": {
"host": "http://local.management.vauthenticator.com:9090"
"host": "http://local.management.vauthenticator.com:9090",
"actuatorHost": "http://local.management.vauthenticator.com:9091"
}
}
8 changes: 8 additions & 0 deletions local-environment/request.http
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### actuator root
GET {{actuatorHost}}/actuator


### acgtuator database-clean-up job
POST {{actuatorHost}}/actuator/database-clean-up


### login page
GET {{host}}/login

Expand Down
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
@@ -0,0 +1,69 @@
package com.vauthenticator.server.management

import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.queryForList
import org.springframework.transaction.annotation.Transactional
import java.time.Clock

@Transactional
class DatabaseTtlEntryCleanJob(
private val jdbcTemplate: JdbcTemplate,
private val clock: Clock
) {

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

fun execute() {
logger.info("Job Running")
val now = clock.instant().epochSecond

deleteOldTicket(now)
deleteOldKeys(now)
logger.info("Job Completed")

}

private fun deleteOldKeys(now: Long) {
val keysToBeDeleted =
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"]
)
}
}

private fun deleteOldTicket(now: Long) {
val ticketToBeDeleted =
jdbcTemplate.queryForList<String>(
"SELECT ticket FROM TICKET WHERE ttl < ?",
arrayOf(now)
)
ticketToBeDeleted.forEach {
jdbcTemplate.update("DELETE FROM TICKET WHERE ticket = ?", it)
}
}

}


@Profile("database")
@Configuration(proxyBeanMethods = false)
class DatabaseTtlEntryCleanJobConfig() {

@Bean
fun databaseTtlEntryCleanJob(
jdbcTemplate: JdbcTemplate
) = DatabaseTtlEntryCleanJob(jdbcTemplate, Clock.systemUTC())

@Bean
fun databaseTtlEntryCleanJobEndPoint(databaseTtlEntryCleanJob: DatabaseTtlEntryCleanJob) =
DatabaseTtlEntryCleanJobEndPoint(databaseTtlEntryCleanJob)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.vauthenticator.server.management

import org.springframework.boot.actuate.endpoint.annotation.Endpoint
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation
import org.springframework.http.ResponseEntity

@Endpoint(id = "database-clean-up")
class DatabaseTtlEntryCleanJobEndPoint(
private val databaseTtlEntryCleanJob: DatabaseTtlEntryCleanJob
) {

@WriteOperation
fun cleanUp(): ResponseEntity<Unit> {
databaseTtlEntryCleanJob.execute()
return ResponseEntity.noContent().build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ object JdbcClientApplicationConverter {
webServerRedirectUri = CallbackUri(rs.getString("web_server_redirect_uri")),
accessTokenValidity = TokenTimeToLive(rs.getLong("access_token_validity")),
refreshTokenValidity = TokenTimeToLive(rs.getLong("refresh_token_validity")),
additionalInformation = objectMapper.readValue(
additionalInformation = Optional.ofNullable(objectMapper.readValue(
rs.getString("additional_information"),
Map::class.java
) as Map<String, String>,
) as Map<String, String>).orElse(emptyMap()),
autoApprove = AutoApprove(rs.getBoolean("auto_approve")),
postLogoutRedirectUri = PostLogoutRedirectUri(rs.getString("post_logout_redirect_uri")),
logoutUri = LogoutUri(rs.getString("logout_uri"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.vauthenticator.server.management

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vauthenticator.server.keys.adapter.jdbc.JdbcKeyStorage
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.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Clock
import java.time.Duration
import java.util.*

class DatabaseTtlEntryCleanJobTest {

@BeforeEach
fun setUp() {
resetDb()
}

@Test
fun `when the old entries are deleted`() {
val ticketRepository = JdbcTicketRepository(jdbcTemplate, jacksonObjectMapper())
val keyStorage = JdbcKeyStorage(jdbcTemplate, Clock.systemDefaultZone())

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

val kid = Kid("")
val anAccount = AccountTestFixture.anAccount()
val aClientAppId = ClientAppFixture.aClientAppId()

ticketRepository.store(TicketFixture.ticketFor("A_TICKET", anAccount.email, aClientAppId.content))
keyStorage.store(
MasterKid(""),
kid,
DataKey(ByteArray(0), Optional.empty()),
KeyType.ASYMMETRIC,
SIGNATURE
)
keyStorage.keyDeleteJodPlannedFor(kid, Duration.ofSeconds(-200), SIGNATURE)


uut.execute()


val actualTicket = ticketRepository.loadFor(TicketId("A_TICKET"))
assertTrue(actualTicket.isEmpty)
assertThrows(NoSuchElementException::class.java) {
keyStorage.findOne(kid, SIGNATURE)
}
}
}
Loading