From 735fba0e455747a7eec1c039ff12d912108366dc Mon Sep 17 00:00:00 2001 From: mrflick72 Date: Fri, 26 Jul 2024 22:17:09 +0200 Subject: [PATCH] let to accept mfa channel and mfa method to mfa sender and verifier abstraction --- .../SendVerifyEMailChallenge.kt | 4 +-- .../server/mfa/api/MfaChallengeEndPoint.kt | 3 +- .../server/mfa/domain/MfaMethodsEnrollment.kt | 29 +++++++++-------- .../server/mfa/domain/MfaSenderAndVerifier.kt | 19 +++++++----- .../server/mfa/domain/OtpMfa.kt | 19 ++++++------ .../server/mfa/web/MfaController.kt | 8 +++-- .../vauthenticator/server/ticket/Ticket.kt | 31 +++++++++++++++++-- .../SendVerifyEMailChallengeTest.kt | 4 +-- .../mfa/api/MfaChallengeEndPointTest.kt | 3 +- .../domain/AccountAwareOtpMfaVerifierTest.kt | 15 ++++++--- .../mfa/domain/MfaMethodsEnrollmentTest.kt | 4 +-- .../mfa/domain/OtpMfaEmailSenderTest.kt | 4 +-- .../server/mfa/domain/TaimosOtpMfaTest.kt | 2 +- .../server/mfa/web/MfaControllerTest.kt | 14 +++++++-- .../server/support/TicketFixture.kt | 3 +- .../MfaMethodsEnrollmentAssociationTest.kt | 28 ++++++++++++----- 16 files changed, 129 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallenge.kt b/src/main/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallenge.kt index 1e9b961c..fce11d46 100644 --- a/src/main/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallenge.kt +++ b/src/main/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallenge.kt @@ -7,7 +7,7 @@ import com.vauthenticator.server.mfa.domain.MfaMethod import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollment import com.vauthenticator.server.oauth2.clientapp.ClientAppId import com.vauthenticator.server.ticket.Ticket -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_AUTO_ASSOCIATION_CONTEXT_VALUE +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_VALUE import com.vauthenticator.server.ticket.TicketId import org.slf4j.LoggerFactory @@ -31,7 +31,7 @@ class SendVerifyEMailChallenge( account.email, ClientAppId.empty(), false, - mapOf(Ticket.MFA_AUTO_ASSOCIATION_CONTEXT_KEY to MFA_AUTO_ASSOCIATION_CONTEXT_VALUE) + mapOf(Ticket.MFA_SELF_ASSOCIATION_CONTEXT_KEY to MFA_SELF_ASSOCIATION_CONTEXT_VALUE) ) val mailContext = mailContextFrom(verificationTicket) mailVerificationMailSender.sendFor(account, mailContext) diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPoint.kt b/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPoint.kt index 2862c706..0a936cca 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPoint.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPoint.kt @@ -1,5 +1,6 @@ package com.vauthenticator.server.mfa.api +import com.vauthenticator.server.mfa.domain.MfaMethod import com.vauthenticator.server.mfa.domain.OtpMfaSender import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.PutMapping @@ -10,7 +11,7 @@ class MfaChallengeEndPoint(private val otpMfaSender: OtpMfaSender) { @PutMapping("/api/mfa/challenge") fun sendMfaChallenge(authentication: Authentication) { - otpMfaSender.sendMfaChallenge(authentication.name, authentication.name) + otpMfaSender.sendMfaChallenge(authentication.name, MfaMethod.EMAIL_MFA_METHOD, authentication.name) } } \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollment.kt b/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollment.kt index e640dfa9..3a258d60 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollment.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollment.kt @@ -4,10 +4,7 @@ import com.vauthenticator.server.account.Account import com.vauthenticator.server.mfa.repository.MfaAccountMethodsRepository import com.vauthenticator.server.oauth2.clientapp.ClientAppId import com.vauthenticator.server.ticket.* -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_AUTO_ASSOCIATION_CONTEXT_KEY -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_AUTO_ASSOCIATION_CONTEXT_VALUE -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_CHANNEL_CONTEXT_KEY -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_METHOD_CONTEXT_KEY +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_KEY typealias MfaAssociationVerifier = (ticket: Ticket) -> Unit @@ -21,8 +18,8 @@ class MfaMethodsEnrollmentAssociation( associate( ticketId, ) { - if(it.context.content[MFA_AUTO_ASSOCIATION_CONTEXT_KEY] != MFA_AUTO_ASSOCIATION_CONTEXT_VALUE){ - throw InvalidTicketException("Mfa association without code is allowed only if in the ticket context there is $MFA_AUTO_ASSOCIATION_CONTEXT_KEY feature enabled") + if (it.context.isMfaNotSelfAssociable()) { + throw InvalidTicketException("Mfa association without code is allowed only if in the ticket context there is $MFA_SELF_ASSOCIATION_CONTEXT_KEY feature enabled") } } @@ -31,7 +28,14 @@ class MfaMethodsEnrollmentAssociation( fun associate(ticketId: String, code: String) { associate( ticketId, - ) { otpMfaVerifier.verifyMfaChallengeFor(it.userName, MfaChallenge(code)) } + ) { + otpMfaVerifier.verifyMfaChallengeFor( + it.userName, + it.context.mfaMethod(), + it.context.mfaChannel(), + MfaChallenge(code) + ) + } } private fun associate(ticket: String, verifier: MfaAssociationVerifier) { @@ -70,17 +74,16 @@ class MfaMethodsEnrollment( ) if (sendChallengeCode) { - mfaSender.sendMfaChallenge(email, mfaChannel) + mfaSender.sendMfaChallenge(email, mfaMethod, mfaChannel) } return ticketCreator.createTicketFor( account, clientAppId, - TicketContext( - mapOf( - MFA_CHANNEL_CONTEXT_KEY to mfaChannel, - MFA_METHOD_CONTEXT_KEY to mfaMethod.name - ) + ticketContextAdditionalProperties + TicketContext.mfaContextFor( + mfaMethod = mfaMethod, + mfaChannel = mfaChannel, + ticketContextAdditionalProperties = ticketContextAdditionalProperties ) ) } diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaSenderAndVerifier.kt b/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaSenderAndVerifier.kt index 82e7eb68..6b24f409 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaSenderAndVerifier.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/domain/MfaSenderAndVerifier.kt @@ -5,11 +5,11 @@ import com.vauthenticator.server.email.EMailSenderService interface OtpMfaSender { - fun sendMfaChallenge(userName: String, challengeChannel: String) + fun sendMfaChallenge(userName: String, mfaMethod: MfaMethod, mfaChannel: String) } interface OtpMfaVerifier { - fun verifyMfaChallengeFor(userName: String, challenge: MfaChallenge) + fun verifyMfaChallengeFor(userName: String, mfaMethod: MfaMethod, mfaChannel: String, challenge: MfaChallenge) } class OtpMfaEmailSender( @@ -18,11 +18,11 @@ class OtpMfaEmailSender( private val mfaMailSender: EMailSenderService ) : OtpMfaSender { - override fun sendMfaChallenge(userName: String, challengeChannel: String) { + override fun sendMfaChallenge(userName: String, mfaMethod: MfaMethod, mfaChannel: String) { val account = accountRepository.accountFor(userName).get() - val mfaSecret = otpMfa.generateSecretKeyFor(account) + val mfaSecret = otpMfa.generateSecretKeyFor(account, mfaMethod, mfaChannel) val mfaCode = otpMfa.getTOTPCode(mfaSecret).content() - mfaMailSender.sendFor(account, mapOf("email" to challengeChannel, "mfaCode" to mfaCode)) + mfaMailSender.sendFor(account, mapOf("email" to mfaChannel, "mfaCode" to mfaCode)) } } @@ -30,9 +30,14 @@ class AccountAwareOtpMfaVerifier( private val accountRepository: AccountRepository, private val otpMfa: OtpMfa ) : OtpMfaVerifier { - override fun verifyMfaChallengeFor(userName: String, challenge: MfaChallenge) { + override fun verifyMfaChallengeFor( + userName: String, + mfaMethod: MfaMethod, + mfaChannel: String, + challenge: MfaChallenge + ) { val account = accountRepository.accountFor(userName).get() - otpMfa.verify(account, challenge) + otpMfa.verify(account, mfaMethod, mfaChannel, challenge) } } \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/domain/OtpMfa.kt b/src/main/kotlin/com/vauthenticator/server/mfa/domain/OtpMfa.kt index 4c4a4646..8429d662 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/domain/OtpMfa.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/domain/OtpMfa.kt @@ -12,9 +12,9 @@ import org.apache.commons.codec.binary.Hex //todo the interface has to take in account the enrolled method interface OtpMfa { - fun generateSecretKeyFor(account: Account): MfaSecret + fun generateSecretKeyFor(account: Account, mfaMethod: MfaMethod, mfaChannel: String): MfaSecret fun getTOTPCode(secretKey: MfaSecret): MfaChallenge - fun verify(account: Account, optCode: MfaChallenge) + fun verify(account: Account, mfaMethod: MfaMethod, mfaChannel: String, optCode: MfaChallenge) } class TaimosOtpMfa( @@ -26,12 +26,11 @@ class TaimosOtpMfa( private val tokenTimeWindow: Int = properties.timeToLiveInSeconds private val tokenTimeWindowMillis: Long = (tokenTimeWindow * 1000).toLong() - // todo to be improved - override fun generateSecretKeyFor(account: Account): MfaSecret { - //todo - val mfatMethod = - mfaAccountMethodsRepository.findOne(account.email, MfaMethod.EMAIL_MFA_METHOD, account.email).orElseGet { null } - val encryptedSecret = keyRepository.keyFor(mfatMethod.key, KeyPurpose.MFA) + override fun generateSecretKeyFor(account: Account, mfaMethod: MfaMethod, mfaChannel: String): MfaSecret { + val mfaAccountMethod = + mfaAccountMethodsRepository.findOne(account.email, mfaMethod, mfaChannel) + .orElseGet { null } + val encryptedSecret = keyRepository.keyFor(mfaAccountMethod.key, KeyPurpose.MFA) val decryptKeyAsByteArray = keyDecrypter.decryptKey(encryptedSecret.dataKey.encryptedPrivateKeyAsString()) val decryptedKey = Hex.encodeHexString(decoder.decode(decryptKeyAsByteArray)) return MfaSecret(decryptedKey) @@ -48,8 +47,8 @@ class TaimosOtpMfa( ) } - override fun verify(account: Account, optCode: MfaChallenge) { - val mfaSecret = generateSecretKeyFor(account) + override fun verify(account: Account, mfaMethod: MfaMethod, mfaChannel: String, optCode: MfaChallenge) { + val mfaSecret = generateSecretKeyFor(account, mfaMethod, mfaChannel) try { val validated = TimeBasedOneTimePasswordUtil.validateCurrentNumberHex( diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/web/MfaController.kt b/src/main/kotlin/com/vauthenticator/server/mfa/web/MfaController.kt index 5fe46c2d..0b49cde3 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/web/MfaController.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/web/MfaController.kt @@ -29,7 +29,7 @@ class MfaController( @GetMapping("/mfa-challenge/send") fun view(authentication: Authentication): String { - otpMfaSender.sendMfaChallenge(authentication.name, authentication.name) + otpMfaSender.sendMfaChallenge(authentication.name, MfaMethod.EMAIL_MFA_METHOD, authentication.name) return "redirect:/mfa-challenge" } @@ -48,13 +48,15 @@ class MfaController( @PostMapping("/mfa-challenge") fun processSecondFactor( @RequestParam("mfa-code") mfaCode: String, + @RequestParam("mfa-method") mfaMethod: MfaMethod, + @RequestParam("mfa-channel") mfaChannel: String, authentication: Authentication, request: HttpServletRequest, response: HttpServletResponse ) { try { - otpMfaVerifier.verifyMfaChallengeFor(authentication.name, MfaChallenge(mfaCode)) - publisher.publishEvent(MfaSuccessEvent( authentication)) + otpMfaVerifier.verifyMfaChallengeFor(authentication.name, mfaMethod, mfaChannel, MfaChallenge(mfaCode)) + publisher.publishEvent(MfaSuccessEvent(authentication)) nextHopeLoginWorkflowSuccessHandler.onAuthenticationSuccess(request, response, authentication) } catch (e: Exception) { logger.error(e.message, e) diff --git a/src/main/kotlin/com/vauthenticator/server/ticket/Ticket.kt b/src/main/kotlin/com/vauthenticator/server/ticket/Ticket.kt index 524050fa..dee3ef49 100644 --- a/src/main/kotlin/com/vauthenticator/server/ticket/Ticket.kt +++ b/src/main/kotlin/com/vauthenticator/server/ticket/Ticket.kt @@ -1,5 +1,11 @@ package com.vauthenticator.server.ticket +import com.vauthenticator.server.mfa.domain.MfaMethod +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_CHANNEL_CONTEXT_KEY +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_METHOD_CONTEXT_KEY +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_NOT_SELF_ASSOCIATION_CONTEXT_VALUE +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_KEY +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_VALUE import java.time.Duration data class Ticket( @@ -12,8 +18,9 @@ data class Ticket( companion object { const val MFA_CHANNEL_CONTEXT_KEY = "mfaChannel" const val MFA_METHOD_CONTEXT_KEY = "mfaMethod" - const val MFA_AUTO_ASSOCIATION_CONTEXT_KEY = "auto-association" - const val MFA_AUTO_ASSOCIATION_CONTEXT_VALUE = "true" + const val MFA_SELF_ASSOCIATION_CONTEXT_KEY = "selfAssociation" + const val MFA_SELF_ASSOCIATION_CONTEXT_VALUE = "true" + const val MFA_NOT_SELF_ASSOCIATION_CONTEXT_VALUE = "false" } } @@ -21,7 +28,27 @@ data class TicketContext(val content: Map) { companion object { fun empty() = TicketContext(emptyMap()) + fun mfaContextFor( + mfaMethod: MfaMethod, + mfaChannel: String, + autoAssociation: Boolean = false, + ticketContextAdditionalProperties: Map + ) = TicketContext( + mapOf( + MFA_CHANNEL_CONTEXT_KEY to mfaChannel, + MFA_METHOD_CONTEXT_KEY to mfaMethod.name, + MFA_SELF_ASSOCIATION_CONTEXT_KEY to if (autoAssociation) { + MFA_SELF_ASSOCIATION_CONTEXT_VALUE + } else { + MFA_NOT_SELF_ASSOCIATION_CONTEXT_VALUE + } + ) + ticketContextAdditionalProperties + ) } + + fun isMfaNotSelfAssociable() = content[MFA_SELF_ASSOCIATION_CONTEXT_KEY] != MFA_SELF_ASSOCIATION_CONTEXT_VALUE + fun mfaMethod() = MfaMethod.valueOf(content[MFA_METHOD_CONTEXT_KEY]!!) + fun mfaChannel() = content[MFA_CHANNEL_CONTEXT_KEY]!! } data class TicketId(val content: String) diff --git a/src/test/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallengeTest.kt b/src/test/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallengeTest.kt index 3c783a8b..671785a4 100644 --- a/src/test/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallengeTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/account/emailverification/SendVerifyEMailChallengeTest.kt @@ -13,7 +13,7 @@ import com.vauthenticator.server.oauth2.clientapp.Scope import com.vauthenticator.server.oauth2.clientapp.Scopes import com.vauthenticator.server.support.AccountTestFixture.anAccount import com.vauthenticator.server.ticket.Ticket -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_AUTO_ASSOCIATION_CONTEXT_VALUE +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_VALUE import com.vauthenticator.server.ticket.TicketId import io.mockk.every import io.mockk.impl.annotations.MockK @@ -69,7 +69,7 @@ internal class SendVerifyEMailChallengeTest { account.email, ClientAppId.empty(), false, - mapOf(Ticket.MFA_AUTO_ASSOCIATION_CONTEXT_KEY to MFA_AUTO_ASSOCIATION_CONTEXT_VALUE) + mapOf(Ticket.MFA_SELF_ASSOCIATION_CONTEXT_KEY to MFA_SELF_ASSOCIATION_CONTEXT_VALUE) ) } returns ticketId every { mailVerificationMailSender.sendFor(account, requestContext) } just runs diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPointTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPointTest.kt index cb9e76d5..68619d53 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPointTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaChallengeEndPointTest.kt @@ -1,5 +1,6 @@ package com.vauthenticator.server.mfa.api +import com.vauthenticator.server.mfa.domain.MfaMethod import com.vauthenticator.server.mfa.domain.OtpMfaSender import com.vauthenticator.server.support.AccountTestFixture import com.vauthenticator.server.support.SecurityFixture @@ -35,7 +36,7 @@ internal class MfaChallengeEndPointTest { @Test internal fun `when an mfa challenge is sent`() { - every { otpMfaSender.sendMfaChallenge(account.email,account.email) } just runs + every { otpMfaSender.sendMfaChallenge(account.email, MfaMethod.EMAIL_MFA_METHOD, account.email) } just runs mokMvc.perform( put("/api/mfa/challenge") diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/domain/AccountAwareOtpMfaVerifierTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/domain/AccountAwareOtpMfaVerifierTest.kt index 6197c84f..366d4e92 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/domain/AccountAwareOtpMfaVerifierTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/domain/AccountAwareOtpMfaVerifierTest.kt @@ -28,9 +28,9 @@ internal class AccountAwareOtpMfaVerifierTest { val underTest = AccountAwareOtpMfaVerifier(accountRepository, otpMfa) every { accountRepository.accountFor(account.email) } returns Optional.of(account) - every { otpMfa.verify(account, challenge) } just runs + every { otpMfa.verify(account, MfaMethod.EMAIL_MFA_METHOD, account.email, challenge) } just runs - underTest.verifyMfaChallengeFor(account.email, challenge) + underTest.verifyMfaChallengeFor(account.email, MfaMethod.EMAIL_MFA_METHOD, account.email, challenge) } @Test @@ -39,10 +39,17 @@ internal class AccountAwareOtpMfaVerifierTest { val challenge = MfaChallenge("AN_MFA_CHALLENGE") every { accountRepository.accountFor(account.email) } returns Optional.of(account) - every { otpMfa.verify(account, challenge) } throws MfaException("") + every { otpMfa.verify(account, MfaMethod.EMAIL_MFA_METHOD, account.email, challenge) } throws MfaException("") val underTest = AccountAwareOtpMfaVerifier(accountRepository, otpMfa) - assertThrows(MfaException::class.java) { underTest.verifyMfaChallengeFor(account.email, challenge) } + assertThrows(MfaException::class.java) { + underTest.verifyMfaChallengeFor( + account.email, + MfaMethod.EMAIL_MFA_METHOD, + account.email, + challenge + ) + } } } \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollmentTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollmentTest.kt index 21991a31..6a64f144 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollmentTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/domain/MfaMethodsEnrollmentTest.kt @@ -90,7 +90,7 @@ class MfaMethodsEnrollmentTest { ) } returns Optional.of(emailMfaAccountMethod) every { ticketCreator.createTicketFor(account, clientAppId, ticketContext(emailMfaChannel)) } returns ticketId - every { mfaSender.sendMfaChallenge(account.email, emailMfaChannel) } just runs + every { mfaSender.sendMfaChallenge(account.email, EMAIL_MFA_METHOD,emailMfaChannel) } just runs val actual = uut.enroll(account, EMAIL_MFA_METHOD, emailMfaChannel, clientAppId, true) @@ -102,7 +102,7 @@ class MfaMethodsEnrollmentTest { ) } verify { ticketCreator.createTicketFor(account, clientAppId, ticketContext(emailMfaChannel)) } - verify { mfaSender.sendMfaChallenge(account.email, emailMfaChannel) } + verify { mfaSender.sendMfaChallenge(account.email, EMAIL_MFA_METHOD,emailMfaChannel) } assertEquals(ticketId, actual) } diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/domain/OtpMfaEmailSenderTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/domain/OtpMfaEmailSenderTest.kt index 9a230f65..5171d1ab 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/domain/OtpMfaEmailSenderTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/domain/OtpMfaEmailSenderTest.kt @@ -32,7 +32,7 @@ internal class OtpMfaEmailSenderTest { val underTest = OtpMfaEmailSender(accountRepository, otp, mfaMailSender) every { accountRepository.accountFor(account.email) } returns Optional.of(account) - every { otp.generateSecretKeyFor(account) } returns mfaSecret + every { otp.generateSecretKeyFor(account, MfaMethod.EMAIL_MFA_METHOD, account.email) } returns mfaSecret every { otp.getTOTPCode(mfaSecret) } returns mfaChallenge every { mfaMailSender.sendFor( @@ -41,6 +41,6 @@ internal class OtpMfaEmailSenderTest { ) } just runs - underTest.sendMfaChallenge(account.email, account.email) + underTest.sendMfaChallenge(account.email, MfaMethod.EMAIL_MFA_METHOD, account.email) } } \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/domain/TaimosOtpMfaTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/domain/TaimosOtpMfaTest.kt index bc3a4b63..551e020b 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/domain/TaimosOtpMfaTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/domain/TaimosOtpMfaTest.kt @@ -48,7 +48,7 @@ class TaimosOtpMfaTest { every { keyRepository.keyFor(Kid("A_KID"), KeyPurpose.MFA) } returns key every { keyDecrypter.decryptKey("QV9FTkNSWVBURURfS0VZ") } returns "QV9ERUNSWVBURURfU1lNTUVUUklDX0tFWQ==" - val actual = underTest.generateSecretKeyFor(account) + val actual = underTest.generateSecretKeyFor(account, MfaMethod.EMAIL_MFA_METHOD, email) val expectedSecret = Hex.encodeHexString(decoder.decode("QV9ERUNSWVBURURfU1lNTUVUUklDX0tFWQ==")) assertEquals(MfaSecret(expectedSecret), actual) } diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/web/MfaControllerTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/web/MfaControllerTest.kt index fe67e462..4c89e07d 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/web/MfaControllerTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/web/MfaControllerTest.kt @@ -3,6 +3,7 @@ package com.vauthenticator.server.mfa.web import com.vauthenticator.server.i18n.I18nMessageInjector import com.vauthenticator.server.i18n.I18nScope import com.vauthenticator.server.mfa.domain.MfaChallenge +import com.vauthenticator.server.mfa.domain.MfaMethod import com.vauthenticator.server.mfa.domain.OtpMfaSender import com.vauthenticator.server.mfa.domain.OtpMfaVerifier import com.vauthenticator.server.support.AccountTestFixture.anAccount @@ -65,13 +66,13 @@ internal class MfaControllerTest { @Test internal fun `when an mfa challenge is sent`() { - every { otpMfaSender.sendMfaChallenge(account.email, account.email) } just runs + every { otpMfaSender.sendMfaChallenge(account.email, MfaMethod.EMAIL_MFA_METHOD, account.email) } just runs mokMvc.perform( get("/mfa-challenge/send") .principal(principalFor(account.email)) ).andExpect(redirectedUrl("/mfa-challenge")) - verify { otpMfaSender.sendMfaChallenge(account.email, account.email) } + verify { otpMfaSender.sendMfaChallenge(account.email, MfaMethod.EMAIL_MFA_METHOD, account.email) } } @Test @@ -88,7 +89,14 @@ internal class MfaControllerTest { @Test internal fun `when an mfa challenge is verified`() { - every { otpMfaVerifier.verifyMfaChallengeFor(account.email, MfaChallenge("AN_MFA_CHALLENGE_CODE")) } just runs + every { + otpMfaVerifier.verifyMfaChallengeFor( + account.email, + MfaMethod.EMAIL_MFA_METHOD, + account.email, + MfaChallenge("AN_MFA_CHALLENGE_CODE") + ) + } just runs val mfaAuthentication = principalFor(account.email) every { successHandler.onAuthenticationSuccess(any(), any(), mfaAuthentication) } just runs diff --git a/src/test/kotlin/com/vauthenticator/server/support/TicketFixture.kt b/src/test/kotlin/com/vauthenticator/server/support/TicketFixture.kt index 68d814c6..7a5b0709 100644 --- a/src/test/kotlin/com/vauthenticator/server/support/TicketFixture.kt +++ b/src/test/kotlin/com/vauthenticator/server/support/TicketFixture.kt @@ -10,7 +10,8 @@ object TicketFixture { fun ticketContext(email: String) = TicketContext( mapOf( "mfaChannel" to email, - "mfaMethod" to MfaMethod.EMAIL_MFA_METHOD.name + "mfaMethod" to MfaMethod.EMAIL_MFA_METHOD.name, + "selfAssociation" to "false" ) ) diff --git a/src/test/kotlin/com/vauthenticator/server/ticket/MfaMethodsEnrollmentAssociationTest.kt b/src/test/kotlin/com/vauthenticator/server/ticket/MfaMethodsEnrollmentAssociationTest.kt index bd1f4ee0..94c90541 100644 --- a/src/test/kotlin/com/vauthenticator/server/ticket/MfaMethodsEnrollmentAssociationTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/ticket/MfaMethodsEnrollmentAssociationTest.kt @@ -10,8 +10,8 @@ import com.vauthenticator.server.mfa.repository.MfaAccountMethodsRepository import com.vauthenticator.server.oauth2.clientapp.ClientAppId import com.vauthenticator.server.support.AccountTestFixture.anAccount import com.vauthenticator.server.support.TicketFixture -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_AUTO_ASSOCIATION_CONTEXT_KEY -import com.vauthenticator.server.ticket.Ticket.Companion.MFA_AUTO_ASSOCIATION_CONTEXT_VALUE +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_KEY +import com.vauthenticator.server.ticket.Ticket.Companion.MFA_SELF_ASSOCIATION_CONTEXT_VALUE import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension @@ -39,12 +39,12 @@ class MfaMethodsEnrollmentAssociationTest { email, Kid(""), EMAIL_MFA_METHOD, - email + email, ) private val ticket = TicketFixture.ticketFor( RAW_TICKET, userName, - ClientAppId.empty().content + ClientAppId.empty().content, ) private val ticketId = TicketId(RAW_TICKET) @@ -69,7 +69,7 @@ class MfaMethodsEnrollmentAssociationTest { val ticketWithAutoAssociationFeatureEnabled = ticket.copy( context = TicketContext( - mapOf(MFA_AUTO_ASSOCIATION_CONTEXT_KEY to MFA_AUTO_ASSOCIATION_CONTEXT_VALUE) + mapOf(MFA_SELF_ASSOCIATION_CONTEXT_KEY to MFA_SELF_ASSOCIATION_CONTEXT_VALUE) ) ) every { ticketRepository.loadFor(ticketId) } returns of(ticketWithAutoAssociationFeatureEnabled) @@ -84,14 +84,28 @@ class MfaMethodsEnrollmentAssociationTest { @Test fun `when mfa is associated`() { every { ticketRepository.loadFor(ticketId) } returns of(ticket) - every { otpMfaVerifier.verifyMfaChallengeFor(userName, MfaChallenge(CODE)) } just runs + every { + otpMfaVerifier.verifyMfaChallengeFor( + userName, + ticket.context.mfaMethod(), + ticket.context.mfaChannel(), + MfaChallenge(CODE) + ) + } just runs every { ticketRepository.delete(ticket.ticketId) } just runs underTest.associate(RAW_TICKET, CODE) verify { ticketRepository.loadFor(ticketId) } - verify { otpMfaVerifier.verifyMfaChallengeFor(userName, MfaChallenge(CODE)) } + verify { + otpMfaVerifier.verifyMfaChallengeFor( + userName, + ticket.context.mfaMethod(), + ticket.context.mfaChannel(), + MfaChallenge(CODE) + ) + } verify { ticketRepository.delete(ticket.ticketId) } }