diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt b/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt index a736b2cf..8d93ed24 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt @@ -1,6 +1,5 @@ package com.vauthenticator.server.mfa.api -import com.vauthenticator.server.account.repository.AccountRepository import com.vauthenticator.server.extentions.clientAppId import com.vauthenticator.server.mask.SensitiveEmailMasker import com.vauthenticator.server.mfa.domain.EmailMfaDevice @@ -12,8 +11,10 @@ import com.vauthenticator.server.oauth2.clientapp.Scope import com.vauthenticator.server.oauth2.clientapp.Scopes import com.vauthenticator.server.role.PermissionValidator import jakarta.servlet.http.HttpSession +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity.ok +import org.springframework.http.ResponseEntity.status import org.springframework.security.core.Authentication import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken import org.springframework.web.bind.annotation.GetMapping @@ -26,7 +27,6 @@ class MfaEnrolmentAssociationEndPoint( private val sensitiveEmailMasker: SensitiveEmailMasker, private val mfaAccountMethodsRepository: MfaAccountMethodsRepository, private val mfaMethodsEnrollment: MfaMethodsEnrollment, - private val accountRepository: AccountRepository, private val mfaMethodsEnrolmentAssociation: MfaMethodsEnrollmentAssociation, private val permissionValidator: PermissionValidator ) { @@ -56,7 +56,7 @@ class MfaEnrolmentAssociationEndPoint( authentication: JwtAuthenticationToken, httpSession: HttpSession, @RequestBody enrolling: MfaEnrollmentRequest - ): ResponseEntity { + ): ResponseEntity { permissionValidator.validate(authentication, httpSession, Scopes.from(Scope.MFA_ENROLLMENT)) val ticketId = mfaMethodsEnrollment.enroll( authentication.name, @@ -65,7 +65,7 @@ class MfaEnrolmentAssociationEndPoint( authentication.clientAppId(), true ) - return ok(ticketId.content) + return status(HttpStatus.CREATED).body(MfaEnrollmentResponse(ticketId.content)) } @PostMapping("/api/mfa/associate") @@ -73,9 +73,10 @@ class MfaEnrolmentAssociationEndPoint( httpSession: HttpSession, authentication: JwtAuthenticationToken, @RequestBody associationRequest: MfaEnrollmentAssociationRequest, - ) { + ): ResponseEntity { permissionValidator.validate(authentication, httpSession, Scopes.from(Scope.MFA_ENROLLMENT)) mfaMethodsEnrolmentAssociation.associate(associationRequest.ticket, associationRequest.code) + return ResponseEntity.noContent().build() } } @@ -85,6 +86,9 @@ data class MfaEnrollmentRequest( val mfaMethod: MfaMethod, ) +data class MfaEnrollmentResponse( + val ticket: String +) data class MfaEnrollmentAssociationRequest( val ticket: String, diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt index affe5d80..764c44da 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt @@ -2,24 +2,33 @@ package com.vauthenticator.server.mfa.api import com.fasterxml.jackson.databind.ObjectMapper import com.vauthenticator.server.account.repository.AccountRepository +import com.vauthenticator.server.clientapp.A_CLIENT_APP_ID import com.vauthenticator.server.mask.SensitiveEmailMasker import com.vauthenticator.server.mfa.domain.EmailMfaDevice import com.vauthenticator.server.mfa.domain.MfaMethod.EMAIL_MFA_METHOD import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollment import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollmentAssociation import com.vauthenticator.server.mfa.repository.MfaAccountMethodsRepository +import com.vauthenticator.server.oauth2.clientapp.ClientAppId +import com.vauthenticator.server.oauth2.clientapp.Scope +import com.vauthenticator.server.oauth2.clientapp.Scopes import com.vauthenticator.server.role.PermissionValidator import com.vauthenticator.server.support.AccountTestFixture import com.vauthenticator.server.support.MfaFixture.accountMfaAssociatedMfaMethods import com.vauthenticator.server.support.SecurityFixture.principalFor +import com.vauthenticator.server.ticket.TicketId import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension +import io.mockk.just +import io.mockk.runs import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.test.web.servlet.setup.MockMvcBuilders @@ -56,7 +65,6 @@ class MfaEnrolmentAssociationEndPointTest { sensitiveEmailMasker, mfaAccountMethodsRepository, mfaMethodsEnrollment, - accountRepository, mfaMethodsEnrolmentAssociation, permissionValidator ) @@ -87,4 +95,67 @@ class MfaEnrolmentAssociationEndPointTest { ) ) } + + @Test + fun `when a new mfa channel is enrolled`() { + val account = AccountTestFixture.anAccount() + val email = account.email + + val authentication = principalFor( + clientAppId = A_CLIENT_APP_ID, + email = email, + authorities = listOf("USER"), + scopes = listOf(Scope.MFA_ENROLLMENT.content) + ) + every { permissionValidator.validate(authentication, any(), Scopes.from(Scope.MFA_ENROLLMENT)) } just runs + every { + mfaMethodsEnrollment.enroll( + account.email, + EMAIL_MFA_METHOD, + account.email, + ClientAppId(A_CLIENT_APP_ID), + true + ) + } returns TicketId("A_TICKET") + + mokMvc.perform( + post("/api/mfa/enrollment") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(MfaEnrollmentRequest(account.email, EMAIL_MFA_METHOD))) + .principal(authentication) + ) + .andExpect(status().isCreated) + .andExpect( + content().json( + objectMapper.writeValueAsString( + MfaEnrollmentResponse("A_TICKET") + ) + ) + ) + + } + + + @Test + fun `when a new enrolled mfa is associated`() { + val account = AccountTestFixture.anAccount() + val email = account.email + + val authentication = principalFor( + clientAppId = A_CLIENT_APP_ID, + email = email, + authorities = listOf("USER"), + scopes = listOf(Scope.MFA_ENROLLMENT.content) + ) + every { permissionValidator.validate(authentication, any(), Scopes.from(Scope.MFA_ENROLLMENT)) } just runs + every { mfaMethodsEnrolmentAssociation.associate("A_TICKET", "A_CODE") } just runs + + mokMvc.perform( + post("/api/mfa/associate") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(MfaEnrollmentAssociationRequest("A_TICKET", "A_CODE"))) + .principal(authentication) + ) + .andExpect(status().isNoContent) + } } \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/role/PermissionValidatorTest.kt b/src/test/kotlin/com/vauthenticator/server/role/PermissionValidatorTest.kt index 8903596e..9a667a6a 100644 --- a/src/test/kotlin/com/vauthenticator/server/role/PermissionValidatorTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/role/PermissionValidatorTest.kt @@ -68,7 +68,7 @@ class PermissionValidatorTest { val principal = SecurityFixture.principalFor( clientAppId = clientAppId.content, - mail = EMAIL, + email = EMAIL, scopes = scopesValues ) uut.validate(principal = principal, session = session, scopes = scopes) @@ -80,7 +80,7 @@ class PermissionValidatorTest { val principal = SecurityFixture.principalFor( clientAppId = clientAppId.content, - mail = EMAIL, + email = EMAIL, scopes = emptyList() ) assertThrows(InsufficientClientApplicationScopeException::class.java) { diff --git a/src/test/kotlin/com/vauthenticator/server/support/SecurityFixture.kt b/src/test/kotlin/com/vauthenticator/server/support/SecurityFixture.kt index 99806313..353e8642 100644 --- a/src/test/kotlin/com/vauthenticator/server/support/SecurityFixture.kt +++ b/src/test/kotlin/com/vauthenticator/server/support/SecurityFixture.kt @@ -54,11 +54,11 @@ object SecurityFixture { fun principalFor( clientAppId: String, - mail: String, + email: String, authorities: List = emptyList(), scopes: List = emptyList() ) = - signedJWTFor(clientAppId, mail, scopes).let { signedJWT -> + signedJWTFor(clientAppId, email, scopes).let { signedJWT -> JwtAuthenticationToken( Jwt( simpleJwtFor(clientAppId), @@ -68,7 +68,7 @@ object SecurityFixture { signedJWT.payload.toJSONObject() ), authorities.map(::SimpleGrantedAuthority), - mail + email ) }