From 5d40d68b3fab4ab962376f41a30ad0e3a8283e1e Mon Sep 17 00:00:00 2001 From: David Perez Date: Thu, 4 Apr 2024 09:54:05 -0500 Subject: [PATCH] Update AuthRequestManager for TDE (#1223) --- .../data/auth/manager/AuthRequestManager.kt | 5 ++ .../auth/manager/AuthRequestManagerImpl.kt | 37 +++++++++++ .../auth/manager/AuthRequestManagerTest.kt | 66 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManager.kt index 798e24433..60f6d8dba 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManager.kt @@ -36,6 +36,11 @@ interface AuthRequestManager { */ fun getAuthRequestsWithUpdates(): Flow + /** + * Get an [AuthRequest] by its request ID. + */ + suspend fun getAuthRequestIfApproved(requestId: String): Result + /** * Get a list of the current user's [AuthRequest]s. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt index 5f492dd72..86af656e7 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.manager import com.bitwarden.core.AuthRequestResponse 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.service.AuthRequestsService import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService @@ -246,6 +247,31 @@ class AuthRequestManagerImpl( ) } + override suspend fun getAuthRequestIfApproved(requestId: String): Result = + 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 = authRequestsService .getAuthRequests() @@ -335,6 +361,17 @@ class AuthRequestManagerImpl( fingerprint = authRequestResponse.fingerprint, authRequestType = authRequestType, ) + .onSuccess { + if (authRequestType == AuthRequestTypeJson.ADMIN_APPROVAL) { + authDiskSource.storePendingAuthRequest( + userId = requireNotNull(activeUserId), + pendingAuthRequest = PendingAuthRequestJson( + requestId = it.id, + requestPrivateKey = authRequestResponse.privateKey, + ), + ) + } + } .map { request -> AuthRequest( id = request.id, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt index b35e44212..0468c3978 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt @@ -4,6 +4,7 @@ import app.cash.turbine.test import com.bitwarden.core.AuthRequestResponse 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.PendingAuthRequestJson 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.network.model.AuthRequestTypeJson @@ -29,6 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.time.Clock import java.time.Instant @@ -240,6 +242,7 @@ class AuthRequestManagerTest { fun `createAuthRequestWithUpdates with createNewAuthRequest Success and getAuthRequestUpdate with old creation date should emit Expired`() = runTest { val email = "email@email.com" + fakeAuthDiskSource.userState = SINGLE_USER_STATE val authRequestResponse = AUTH_REQUEST_RESPONSE val authRequestResponseJson = AuthRequestsResponseJson.AuthRequest( id = "1", @@ -300,6 +303,13 @@ class AuthRequestManagerTest { assertEquals(CreateAuthRequestResult.Expired, awaitItem()) awaitComplete() } + fakeAuthDiskSource.assertPendingAuthRequest( + userId = USER_ID, + pendingAuthRequest = PendingAuthRequestJson( + requestId = authRequestResponseJson.id, + requestPrivateKey = authRequestResponse.privateKey, + ), + ) } @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 fun `getAuthRequests should return failure when service returns failure`() = runTest { coEvery { authRequestsService.getAuthRequests() } returns Throwable("Fail").asFailure()