From 7b299f0ad0b0cd398dd9be63d6dde8be334ddbb6 Mon Sep 17 00:00:00 2001 From: mrflick72 Date: Sun, 27 Oct 2024 00:09:08 +0200 Subject: [PATCH] add mfa jdbc repository --- .../jdbc/JdbcMfaAccountMethodsRepository.kt | 100 ++++++++++++++++++ src/main/resources/data/schema.sql | 20 +++- ...AbstractMfaAccountMethodsRepositoryTest.kt | 3 + .../DynamoMfaAccountMethodsRepositoryTest.kt | 7 +- .../JdbcMfaAccountMethodsRepositoryTest.kt | 22 ++++ .../server/support/JdbcUtils.kt | 1 + 6 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepository.kt create mode 100644 src/test/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepositoryTest.kt diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepository.kt b/src/main/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepository.kt new file mode 100644 index 00000000..c1b44b34 --- /dev/null +++ b/src/main/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepository.kt @@ -0,0 +1,100 @@ +package com.vauthenticator.server.mfa.adapter.jdbc + +import com.vauthenticator.server.keys.domain.* +import com.vauthenticator.server.mfa.domain.MfaAccountMethod +import com.vauthenticator.server.mfa.domain.MfaAccountMethodsRepository +import com.vauthenticator.server.mfa.domain.MfaDeviceId +import com.vauthenticator.server.mfa.domain.MfaMethod +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.transaction.annotation.Transactional +import java.sql.ResultSet +import java.util.* + +@Transactional +class JdbcMfaAccountMethodsRepository( + private val jdbcTemplate: JdbcTemplate, + private val keyRepository: KeyRepository, + private val masterKid: MasterKid, + private val mfaDeviceIdGenerator: () -> MfaDeviceId +) : MfaAccountMethodsRepository { + override fun findBy(userName: String, mfaMfaMethod: MfaMethod, mfaChannel: String): Optional = + Optional.ofNullable( + jdbcTemplate.query( + "SELECT * FROM MFA_ACCOUNT_METHODS WHERE user_name=? AND mfa_method=? AND mfa_channel=?", + { rs, _ -> mfaAccountMethodFrom(rs) }, + userName, mfaMfaMethod.name, mfaChannel + ).firstOrNull() + ) + + + override fun findBy(deviceId: MfaDeviceId): Optional = + Optional.ofNullable( + jdbcTemplate.query( + "SELECT * FROM MFA_ACCOUNT_METHODS WHERE mfa_device_id=?", + { rs, _ -> mfaAccountMethodFrom(rs) }, + deviceId.content + ).firstOrNull() + ) + + override fun findAll(userName: String): List = + jdbcTemplate.query("SELECT * FROM MFA_ACCOUNT_METHODS") + { rs, _ -> mfaAccountMethodFrom(rs) } + + + override fun save( + userName: String, + mfaMfaMethod: MfaMethod, + mfaChannel: String, + associated: Boolean + ): MfaAccountMethod { + val kid = keyRepository.createKeyFrom(masterKid, KeyType.SYMMETRIC, KeyPurpose.MFA) + val mfaDeviceId = mfaDeviceIdGenerator.invoke() + + jdbcTemplate.update( + "INSERT INTO MFA_ACCOUNT_METHODS (user_name, mfa_device_id, mfa_method, mfa_channel, key_id, associated) VALUES (?,?,?,?,?,?)", + userName, mfaDeviceId.content, mfaMfaMethod.name, mfaChannel, kid.content(), associated + ) + + return MfaAccountMethod(userName, mfaDeviceId, kid, mfaMfaMethod, mfaChannel, associated) + } + + + override fun setAsDefault(userName: String, deviceId: MfaDeviceId) { + Optional.ofNullable( + jdbcTemplate.query( + "SELECT mfa_device_id FROM MFA_ACCOUNT_METHODS WHERE user_name=? AND default_mfa_method=true", + { rs, _ -> MfaDeviceId(rs.getString("mfa_device_id")) }, + userName + ).firstOrNull() + ).ifPresent { + jdbcTemplate.update( + "UPDATE MFA_ACCOUNT_METHODS SET default_mfa_method = false WHERE user_name=? AND mfa_device_id=?", + userName, it.content + ) + } + + jdbcTemplate.update( + "UPDATE MFA_ACCOUNT_METHODS SET default_mfa_method = true WHERE user_name=? AND mfa_device_id=?", + userName, deviceId.content + ) + } + + override fun getDefaultDevice(userName: String): Optional = + Optional.ofNullable( + jdbcTemplate.query( + "SELECT mfa_device_id FROM MFA_ACCOUNT_METHODS WHERE user_name=? AND default_mfa_method=true", + { rs, _ -> MfaDeviceId(rs.getString("mfa_device_id")) }, + userName + ).firstOrNull() + ) + + private fun mfaAccountMethodFrom(rs: ResultSet) = MfaAccountMethod( + userName = rs.getString("user_name"), + mfaDeviceId = MfaDeviceId(rs.getString("mfa_device_id")), + key = Kid(rs.getString("key_id")), + mfaMethod = MfaMethod.valueOf(rs.getString("mfa_method")), + mfaChannel = rs.getString("mfa_channel"), + associated = rs.getBoolean("associated"), + ) + +} \ No newline at end of file diff --git a/src/main/resources/data/schema.sql b/src/main/resources/data/schema.sql index 65c2f079..5f691314 100644 --- a/src/main/resources/data/schema.sql +++ b/src/main/resources/data/schema.sql @@ -62,13 +62,27 @@ CREATE TABLE TICKET CREATE TABLE PASSWORD_HISTORY ( - user_name varchar(255) not null , - created_at bigint not null default 0, - password varchar(255) not null, + 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 MFA_ACCOUNT_METHODS +( + user_name varchar(255) not null, + mfa_device_id varchar(255) not null, + mfa_method varchar(255) not null, + mfa_channel varchar(255) not null, + key_id varchar(255) not null, + associated varchar(255) not null, + default_mfa_method boolean default false, + + primary key (user_name, mfa_channel) +); + +CREATE INDEX mfa_account_methods_mfa_device_id ON MFA_ACCOUNT_METHODS (mfa_device_id); CREATE TABLE CLIENT_APPLICATION ( diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/adapter/AbstractMfaAccountMethodsRepositoryTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/adapter/AbstractMfaAccountMethodsRepositoryTest.kt index 3ca002fc..fb2687b6 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/adapter/AbstractMfaAccountMethodsRepositoryTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/adapter/AbstractMfaAccountMethodsRepositoryTest.kt @@ -86,6 +86,9 @@ abstract class AbstractMfaAccountMethodsRepositoryTest { @Test fun `when decide what mfa use as default`() { + every { keyRepository.createKeyFrom(masterKid, KeyType.SYMMETRIC, KeyPurpose.MFA) } returns key + uut.save(email, MfaMethod.EMAIL_MFA_METHOD, email, true) + val expected = Optional.of(mfaDeviceId) uut.setAsDefault(email, mfaDeviceId) val defaultDevice = uut.getDefaultDevice(email) diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/adapter/dynamodb/DynamoMfaAccountMethodsRepositoryTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/adapter/dynamodb/DynamoMfaAccountMethodsRepositoryTest.kt index f9c4570d..db7d4275 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/adapter/dynamodb/DynamoMfaAccountMethodsRepositoryTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/adapter/dynamodb/DynamoMfaAccountMethodsRepositoryTest.kt @@ -10,17 +10,16 @@ import io.mockk.junit5.MockKExtension import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(MockKExtension::class) -class DynamoMfaAccountMethodsRepositoryTest : AbstractMfaAccountMethodsRepositoryTest(){ +class DynamoMfaAccountMethodsRepositoryTest : AbstractMfaAccountMethodsRepositoryTest() { - override fun initMfaAccountMethodsRepository(): MfaAccountMethodsRepository { - return DynamoMfaAccountMethodsRepository( + override fun initMfaAccountMethodsRepository() = + DynamoMfaAccountMethodsRepository( dynamoMfaAccountMethodsTableName, dynamoDefaultMfaAccountMethodsTableName, dynamoDbClient, keyRepository, masterKid ) { mfaDeviceId } - } override fun resetDatabase() { resetDynamoDb() diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepositoryTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepositoryTest.kt new file mode 100644 index 00000000..c9f4710c --- /dev/null +++ b/src/test/kotlin/com/vauthenticator/server/mfa/adapter/jdbc/JdbcMfaAccountMethodsRepositoryTest.kt @@ -0,0 +1,22 @@ +package com.vauthenticator.server.mfa.adapter.jdbc + +import com.vauthenticator.server.mfa.adapter.AbstractMfaAccountMethodsRepositoryTest +import com.vauthenticator.server.support.JdbcUtils.jdbcTemplate +import com.vauthenticator.server.support.JdbcUtils.resetDb +import io.mockk.junit5.MockKExtension +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(MockKExtension::class) +class JdbcMfaAccountMethodsRepositoryTest : AbstractMfaAccountMethodsRepositoryTest() { + override fun initMfaAccountMethodsRepository() = JdbcMfaAccountMethodsRepository( + jdbcTemplate, + keyRepository, + masterKid + ) { mfaDeviceId } + + + override fun resetDatabase() { + resetDb() + } + +} \ 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 16e6d533..ccc4fb94 100644 --- a/src/test/kotlin/com/vauthenticator/server/support/JdbcUtils.kt +++ b/src/test/kotlin/com/vauthenticator/server/support/JdbcUtils.kt @@ -31,6 +31,7 @@ object JdbcUtils { 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 MFA_ACCOUNT_METHODS;") 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) {