Update AuthRequestManager for TDE (#1223)

This commit is contained in:
David Perez 2024-04-04 09:54:05 -05:00 committed by Álison Fernandes
parent f09d9473f7
commit 5d40d68b3f
3 changed files with 108 additions and 0 deletions

View file

@ -36,6 +36,11 @@ interface AuthRequestManager {
*/ */
fun getAuthRequestsWithUpdates(): Flow<AuthRequestsUpdatesResult> fun getAuthRequestsWithUpdates(): Flow<AuthRequestsUpdatesResult>
/**
* Get an [AuthRequest] by its request ID.
*/
suspend fun getAuthRequestIfApproved(requestId: String): Result<AuthRequest>
/** /**
* Get a list of the current user's [AuthRequest]s. * Get a list of the current user's [AuthRequest]s.
*/ */

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.manager
import com.bitwarden.core.AuthRequestResponse import com.bitwarden.core.AuthRequestResponse
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsService import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
@ -246,6 +247,31 @@ class AuthRequestManagerImpl(
) )
} }
override suspend fun getAuthRequestIfApproved(requestId: String): Result<AuthRequest> =
authRequestsService
.getAuthRequest(requestId)
.flatMap { request ->
if (request.requestApproved == true) {
getFingerprintPhrase(request.publicKey).map { fingerprint ->
AuthRequest(
id = request.id,
publicKey = request.publicKey,
platform = request.platform,
ipAddress = request.ipAddress,
key = request.key,
masterPasswordHash = request.masterPasswordHash,
creationDate = request.creationDate,
responseDate = request.responseDate,
requestApproved = true,
originUrl = request.originUrl,
fingerprint = fingerprint,
)
}
} else {
IllegalStateException("Request not approved.").asFailure()
}
}
override suspend fun getAuthRequests(): AuthRequestsResult = override suspend fun getAuthRequests(): AuthRequestsResult =
authRequestsService authRequestsService
.getAuthRequests() .getAuthRequests()
@ -335,6 +361,17 @@ class AuthRequestManagerImpl(
fingerprint = authRequestResponse.fingerprint, fingerprint = authRequestResponse.fingerprint,
authRequestType = authRequestType, authRequestType = authRequestType,
) )
.onSuccess {
if (authRequestType == AuthRequestTypeJson.ADMIN_APPROVAL) {
authDiskSource.storePendingAuthRequest(
userId = requireNotNull(activeUserId),
pendingAuthRequest = PendingAuthRequestJson(
requestId = it.id,
requestPrivateKey = authRequestResponse.privateKey,
),
)
}
}
.map { request -> .map { request ->
AuthRequest( AuthRequest(
id = request.id, id = request.id,

View file

@ -4,6 +4,7 @@ import app.cash.turbine.test
import com.bitwarden.core.AuthRequestResponse import com.bitwarden.core.AuthRequestResponse
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
@ -29,6 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.time.Clock import java.time.Clock
import java.time.Instant import java.time.Instant
@ -240,6 +242,7 @@ class AuthRequestManagerTest {
fun `createAuthRequestWithUpdates with createNewAuthRequest Success and getAuthRequestUpdate with old creation date should emit Expired`() = fun `createAuthRequestWithUpdates with createNewAuthRequest Success and getAuthRequestUpdate with old creation date should emit Expired`() =
runTest { runTest {
val email = "email@email.com" val email = "email@email.com"
fakeAuthDiskSource.userState = SINGLE_USER_STATE
val authRequestResponse = AUTH_REQUEST_RESPONSE val authRequestResponse = AUTH_REQUEST_RESPONSE
val authRequestResponseJson = AuthRequestsResponseJson.AuthRequest( val authRequestResponseJson = AuthRequestsResponseJson.AuthRequest(
id = "1", id = "1",
@ -300,6 +303,13 @@ class AuthRequestManagerTest {
assertEquals(CreateAuthRequestResult.Expired, awaitItem()) assertEquals(CreateAuthRequestResult.Expired, awaitItem())
awaitComplete() awaitComplete()
} }
fakeAuthDiskSource.assertPendingAuthRequest(
userId = USER_ID,
pendingAuthRequest = PendingAuthRequestJson(
requestId = authRequestResponseJson.id,
requestPrivateKey = authRequestResponse.privateKey,
),
)
} }
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@ -777,6 +787,62 @@ class AuthRequestManagerTest {
} }
} }
@Test
fun `getAuthRequestIfApproved should return failure when service returns failure`() = runTest {
val requestId = "requestId"
coEvery {
authRequestsService.getAuthRequest(requestId)
} returns Throwable("Fail").asFailure()
val result = repository.getAuthRequestIfApproved(requestId)
coVerify(exactly = 1) {
authRequestsService.getAuthRequest(requestId)
}
assertTrue(result.isFailure)
}
@Test
fun `getAuthRequestIfApproved should return failure when request is not approved`() = runTest {
val requestId = "requestId"
val response = AUTH_REQUESTS_RESPONSE_JSON_AUTH_RESPONSE.copy(requestApproved = false)
coEvery { authRequestsService.getAuthRequest(requestId) } returns response.asSuccess()
val result = repository.getAuthRequestIfApproved(requestId)
coVerify(exactly = 1) {
authRequestsService.getAuthRequest(requestId)
}
assertTrue(result.isFailure)
}
@Test
fun `getAuthRequestIfApproved should return success when request is approved`() = runTest {
val requestId = "requestId"
fakeAuthDiskSource.userState = SINGLE_USER_STATE
coEvery {
authRequestsService.getAuthRequest(requestId)
} returns AUTH_REQUESTS_RESPONSE_JSON_AUTH_RESPONSE.asSuccess()
fakeAuthDiskSource.userState = SINGLE_USER_STATE
coEvery {
authSdkSource.getUserFingerprint(
email = EMAIL,
publicKey = AUTH_REQUESTS_RESPONSE_JSON_AUTH_RESPONSE.publicKey,
)
} returns FINGER_PRINT.asSuccess()
val result = repository.getAuthRequestIfApproved(requestId)
coVerify(exactly = 1) {
authRequestsService.getAuthRequest(requestId)
authSdkSource.getUserFingerprint(
email = EMAIL,
publicKey = AUTH_REQUESTS_RESPONSE_JSON_AUTH_RESPONSE.publicKey,
)
}
assertEquals(AUTH_REQUEST.asSuccess(), result)
}
@Test @Test
fun `getAuthRequests should return failure when service returns failure`() = runTest { fun `getAuthRequests should return failure when service returns failure`() = runTest {
coEvery { authRequestsService.getAuthRequests() } returns Throwable("Fail").asFailure() coEvery { authRequestsService.getAuthRequests() } returns Throwable("Fail").asFailure()