From ea6ff16e6fbba8a76eb1a515fefc250279448959 Mon Sep 17 00:00:00 2001 From: mrflick72 Date: Fri, 25 Oct 2024 23:18:47 +0200 Subject: [PATCH] add jdbc implementation for the password history feature --- .../jdbc/JdbcPasswordHistoryRepository.kt | 59 ++++++++----------- src/main/resources/data/schema.sql | 10 ++++ .../AbstractPasswordHistoryRepositoryTest.kt | 2 - .../jdbc/JdbcPasswordHistoryRepositoryTest.kt | 29 ++++++++- .../server/support/JdbcUtils.kt | 1 + 5 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/main/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepository.kt b/src/main/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepository.kt index e3e21e98..050ea7cc 100644 --- a/src/main/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepository.kt +++ b/src/main/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepository.kt @@ -3,7 +3,6 @@ package com.vauthenticator.server.password.adapter.jdbc import com.vauthenticator.server.password.domain.Password import com.vauthenticator.server.password.domain.PasswordHistoryRepository import org.springframework.jdbc.core.JdbcTemplate -import software.amazon.awssdk.services.dynamodb.model.AttributeValue import java.time.Clock import java.time.LocalDateTime import java.time.ZoneOffset @@ -12,22 +11,13 @@ class JdbcPasswordHistoryRepository( private val historyEvaluationLimit: Int, private val maxHistoryAllowedSize: Int, private val clock: Clock, - private val dynamoPasswordHistoryTableName: String, private val jdbcTemplate: JdbcTemplate ) : PasswordHistoryRepository { override fun store(userName: String, password: Password) { -// dynamoDbClient.putItem( -// PutItemRequest.builder() -// .tableName(dynamoPasswordHistoryTableName) -// .item( -// mapOf( -// "user_name" to userName.asDynamoAttribute(), -// "created_at" to createdAt().asDynamoAttribute(), -// "password" to password.content.asDynamoAttribute() -// ) -// ) -// .build() -// ) + jdbcTemplate.update( + "INSERT INTO PASSWORD_HISTORY (user_name,created_at,password) VALUES (?,?,?)", + userName, createdAt(), password.content + ) } private fun createdAt() = @@ -36,33 +26,34 @@ class JdbcPasswordHistoryRepository( .toEpochMilli() override fun load(userName: String): List { - /* val items = dynamoDbClient.query( - QueryRequest.builder() - .tableName(dynamoPasswordHistoryTableName) - .scanIndexForward(false) - .keyConditionExpression("user_name=:email") - .expressionAttributeValues(mapOf(":email" to userName.asDynamoAttribute())).build() - ).items() - + val items = jdbcTemplate.query( + "SELECT * FROM PASSWORD_HISTORY WHERE user_name=? ORDER BY created_at DESC", + { rs, _ -> + mapOf( + "user_name" to rs.getString("user_name"), + "created_at" to rs.getLong("created_at"), + "password" to rs.getString("password") + ) + }, + userName + ) val allowedPassword = items.take(historyEvaluationLimit) deleteUselessPasswordHistory(items) - return allowedPassword.map { Password(it.valueAsStringFor("password")) }*/ - TODO() + return allowedPassword.map { Password(it["password"]!! as String) } } - private fun deleteUselessPasswordHistory(itemsInTheHistory: List>) { + private fun deleteUselessPasswordHistory(itemsInTheHistory: List>) { val leftoverSize = itemsInTheHistory.size - maxHistoryAllowedSize if (leftoverSize > 0) { -// itemsInTheHistory.takeLast(leftoverSize) -// .forEach { itemToDelete -> -// dynamoDbClient.deleteItem( -// DeleteItemRequest.builder() -// .tableName(dynamoPasswordHistoryTableName) -// .key(itemToDelete.filterKeys { it != "password" }) -// .build() -// ) -// } + itemsInTheHistory.takeLast(leftoverSize) + .forEach { itemToDelete -> + jdbcTemplate.update( + "DELETE FROM PASSWORD_HISTORY WHERE user_name=? AND created_at=?", + *itemToDelete.filterKeys { it != "password" }.values.toTypedArray() + ) + + } } } } \ No newline at end of file diff --git a/src/main/resources/data/schema.sql b/src/main/resources/data/schema.sql index 1efe6707..65c2f079 100644 --- a/src/main/resources/data/schema.sql +++ b/src/main/resources/data/schema.sql @@ -60,6 +60,16 @@ CREATE TABLE TICKET context text not null default '{}' ); +CREATE TABLE PASSWORD_HISTORY +( + user_name varchar(255) not null , + created_at bigint not null default 0, + password varchar(255) not null, + + primary key (user_name, password) +); + + CREATE TABLE CLIENT_APPLICATION ( client_app_id varchar(255) not null PRIMARY KEY, diff --git a/src/test/kotlin/com/vauthenticator/server/password/adapter/AbstractPasswordHistoryRepositoryTest.kt b/src/test/kotlin/com/vauthenticator/server/password/adapter/AbstractPasswordHistoryRepositoryTest.kt index 1ecbffa9..d7de327a 100644 --- a/src/test/kotlin/com/vauthenticator/server/password/adapter/AbstractPasswordHistoryRepositoryTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/password/adapter/AbstractPasswordHistoryRepositoryTest.kt @@ -2,7 +2,6 @@ package com.vauthenticator.server.password.adapter import com.vauthenticator.server.password.domain.Password import com.vauthenticator.server.password.domain.PasswordHistoryRepository -import io.mockk.junit5.MockKExtension import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -25,7 +24,6 @@ abstract class AbstractPasswordHistoryRepositoryTest { @Test fun `when store a new password in an empty the history`() { - uut.store(A_USERNAME, Password("A_PASSWORD")) val history = uut.load(A_USERNAME) diff --git a/src/test/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepositoryTest.kt b/src/test/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepositoryTest.kt index f4fd701e..17525bc1 100644 --- a/src/test/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepositoryTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/password/adapter/jdbc/JdbcPasswordHistoryRepositoryTest.kt @@ -1,5 +1,30 @@ package com.vauthenticator.server.password.adapter.jdbc -import org.junit.jupiter.api.Assertions.* +import com.vauthenticator.server.password.adapter.AbstractPasswordHistoryRepositoryTest +import com.vauthenticator.server.password.domain.PasswordHistoryRepository +import com.vauthenticator.server.support.JdbcUtils.jdbcTemplate +import com.vauthenticator.server.support.JdbcUtils.resetDb +import java.time.Clock -class JdbcPasswordHistoryRepositoryTest \ No newline at end of file +class JdbcPasswordHistoryRepositoryTest : AbstractPasswordHistoryRepositoryTest() { + override fun initPasswordHistoryRepository(): PasswordHistoryRepository = + JdbcPasswordHistoryRepository( + 2, + 3, + Clock.systemUTC(), + jdbcTemplate + ) + + override fun resetDatabase() { + resetDb() + } + + override fun loadActualDynamoSizeFor(userName: String): List> { + return jdbcTemplate.query("SELECT * FROM PASSWORD_HISTORY WHERE user_name = ?", {rs,_ -> mapOf( + "user_name" to rs.getString("user_name"), + "created_at" to rs.getLong("created_at"), + "password" to rs.getString("password") + ) }, userName) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/support/JdbcUtils.kt b/src/test/kotlin/com/vauthenticator/server/support/JdbcUtils.kt index 118e5ec0..16e6d533 100644 --- a/src/test/kotlin/com/vauthenticator/server/support/JdbcUtils.kt +++ b/src/test/kotlin/com/vauthenticator/server/support/JdbcUtils.kt @@ -30,6 +30,7 @@ object JdbcUtils { jdbcTemplate.execute("DROP TABLE IF EXISTS ACCOUNT_ROLE;") jdbcTemplate.execute("DROP TABLE IF EXISTS KEYS;") jdbcTemplate.execute("DROP TABLE IF EXISTS TICKET;") + jdbcTemplate.execute("DROP TABLE IF EXISTS PASSWORD_HISTORY;") jdbcTemplate.execute("DROP TABLE IF EXISTS oauth2_authorization;") jdbcTemplate.execute(Files.readString(Paths.get("src/main/resources/data/schema.sql"))) } catch (e: java.lang.Exception) {