Run detekt on authenticatorbridge module (#4940)

This commit is contained in:
Patrick Honkonen
2025-03-31 10:22:18 -04:00
committed by GitHub
parent 2e11c81f45
commit 6369b20f18
9 changed files with 42 additions and 46 deletions

View File

@@ -1,7 +1,5 @@
package com.bitwarden.authenticatorbridge.manager package com.bitwarden.authenticatorbridge.manager
import android.content.Intent
import android.net.Uri
import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService import com.bitwarden.authenticatorbridge.IAuthenticatorBridgeService
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow

View File

@@ -7,7 +7,6 @@ import android.content.ServiceConnection
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
@@ -46,7 +45,8 @@ private const val AUTHENTICATOR_BRIDGE_SERVICE_CLASS =
internal class AuthenticatorBridgeManagerImpl( internal class AuthenticatorBridgeManagerImpl(
private val connectionType: AuthenticatorBridgeConnectionType, private val connectionType: AuthenticatorBridgeConnectionType,
private val symmetricKeyStorageProvider: SymmetricKeyStorageProvider, private val symmetricKeyStorageProvider: SymmetricKeyStorageProvider,
callbackProvider: AuthenticatorBridgeCallbackProvider = StubAuthenticatorBridgeCallbackProvider(), callbackProvider: AuthenticatorBridgeCallbackProvider =
StubAuthenticatorBridgeCallbackProvider(),
context: Context, context: Context,
processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(), processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(),
) : AuthenticatorBridgeManager { ) : AuthenticatorBridgeManager {
@@ -67,7 +67,7 @@ internal class AuthenticatorBridgeManagerImpl(
isBuildVersionBelow(Build.VERSION_CODES.S) -> AccountSyncState.OsVersionNotSupported isBuildVersionBelow(Build.VERSION_CODES.S) -> AccountSyncState.OsVersionNotSupported
!isBitwardenAppInstalled() -> AccountSyncState.AppNotInstalled !isBitwardenAppInstalled() -> AccountSyncState.AppNotInstalled
else -> AccountSyncState.Loading else -> AccountSyncState.Loading
} },
) )
/** /**
@@ -117,7 +117,7 @@ internal class AuthenticatorBridgeManagerImpl(
} }
?.fold( ?.fold(
onFailure = { false }, onFailure = { false },
onSuccess = { true } onSuccess = { true },
) )
?: false ?: false
@@ -201,7 +201,6 @@ internal class AuthenticatorBridgeManagerImpl(
return return
}, },
) )
} }
if (symmetricKeyStorageProvider.symmetricKey == null) { if (symmetricKeyStorageProvider.symmetricKey == null) {

View File

@@ -30,7 +30,7 @@ sealed class AccountSyncState {
/** /**
* OS version can't support account syncing. * OS version can't support account syncing.
*/ */
data object OsVersionNotSupported: AccountSyncState() data object OsVersionNotSupported : AccountSyncState()
/** /**
* Accounts successfully synced. * Accounts successfully synced.

View File

@@ -1,7 +1,5 @@
package com.bitwarden.authenticatorbridge.model package com.bitwarden.authenticatorbridge.model
import java.time.Instant
/** /**
* Domain level model representing shared account data. * Domain level model representing shared account data.
* *

View File

@@ -1,9 +1,7 @@
package com.bitwarden.authenticatorbridge.model package com.bitwarden.authenticatorbridge.model
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.time.Instant
/** /**
* Models a serializable list of shared accounts to be shared with other applications. * Models a serializable list of shared accounts to be shared with other applications.
@@ -46,5 +44,3 @@ internal data class SharedAccountDataJson(
val totpUris: List<String>, val totpUris: List<String>,
) )
} }

View File

@@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions")
package com.bitwarden.authenticatorbridge.util package com.bitwarden.authenticatorbridge.util
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
@@ -11,7 +13,6 @@ import com.bitwarden.authenticatorbridge.model.SharedAccountDataJson
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyData import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyData
import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyFingerprintData import com.bitwarden.authenticatorbridge.model.SymmetricEncryptionKeyFingerprintData
import com.bitwarden.authenticatorbridge.model.toByteArrayContainer import com.bitwarden.authenticatorbridge.model.toByteArrayContainer
import kotlinx.serialization.encodeToString
import java.security.MessageDigest import java.security.MessageDigest
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.KeyGenerator import javax.crypto.KeyGenerator
@@ -25,6 +26,7 @@ import javax.crypto.spec.SecretKeySpec
* This is intended to be used for implementing * This is intended to be used for implementing
* [IAuthenticatorBridgeService.getSymmetricEncryptionKeyData]. * [IAuthenticatorBridgeService.getSymmetricEncryptionKeyData].
*/ */
@Suppress("MagicNumber")
fun generateSecretKey(): Result<SecretKey> = runCatching { fun generateSecretKey(): Result<SecretKey> = runCatching {
val keygen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES) val keygen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES)
keygen.init(256) keygen.init(256)
@@ -35,8 +37,9 @@ fun generateSecretKey(): Result<SecretKey> = runCatching {
* Generate a fingerprint for the given symmetric key. * Generate a fingerprint for the given symmetric key.
* *
* This is intended to be used for implementing * This is intended to be used for implementing
* [IAuthenticatorBridgeService.checkSymmetricEncryptionKeyFingerprint], which allows callers of the service * [IAuthenticatorBridgeService.checkSymmetricEncryptionKeyFingerprint], which allows callers of the
* to verify that they have the correct symmetric key without actually having to send the key. * service to verify that they have the correct symmetric key without actually having to send the
* key.
*/ */
fun SymmetricEncryptionKeyData.toFingerprint(): Result<SymmetricEncryptionKeyFingerprintData> = fun SymmetricEncryptionKeyData.toFingerprint(): Result<SymmetricEncryptionKeyFingerprintData> =
runCatching { runCatching {
@@ -87,7 +90,7 @@ internal fun EncryptedSharedAccountData.decrypt(
val cipher = generateCipher() val cipher = generateCipher()
cipher.init(Cipher.DECRYPT_MODE, key, iv) cipher.init(Cipher.DECRYPT_MODE, key, iv)
val decryptedModel = JSON.decodeFromString<SharedAccountDataJson>( val decryptedModel = JSON.decodeFromString<SharedAccountDataJson>(
cipher.doFinal(this.encryptedAccountsJson.byteArray).decodeToString() cipher.doFinal(this.encryptedAccountsJson.byteArray).decodeToString(),
) )
decryptedModel.toDomainModel() decryptedModel.toDomainModel()
} }
@@ -130,7 +133,7 @@ fun EncryptedAddTotpLoginItemData.decrypt(
val cipher = generateCipher() val cipher = generateCipher()
cipher.init(Cipher.DECRYPT_MODE, key, iv) cipher.init(Cipher.DECRYPT_MODE, key, iv)
val decryptedModel = JSON.decodeFromString<AddTotpLoginItemDataJson>( val decryptedModel = JSON.decodeFromString<AddTotpLoginItemDataJson>(
cipher.doFinal(this.encryptedTotpUriJson.byteArray).decodeToString() cipher.doFinal(this.encryptedTotpUriJson.byteArray).decodeToString(),
) )
decryptedModel.toDomainModel() decryptedModel.toDomainModel()
} }
@@ -158,7 +161,7 @@ private fun generateCipher(): Cipher =
Cipher.getInstance( Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.KEY_ALGORITHM_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.BLOCK_MODE_CBC + "/" +
"PKCS5PADDING" "PKCS5PADDING",
) )
/** /**
@@ -173,7 +176,7 @@ private fun SharedAccountData.toJsonModel() = SharedAccountDataJson(
email = account.email, email = account.email,
totpUris = account.totpUris, totpUris = account.totpUris,
) )
} },
) )
/** /**
@@ -188,7 +191,7 @@ private fun SharedAccountDataJson.toDomainModel() = SharedAccountData(
email = account.email, email = account.email,
totpUris = account.totpUris, totpUris = account.totpUris,
) )
} },
) )
/** /**
@@ -199,7 +202,8 @@ private fun AddTotpLoginItemDataJson.toDomainModel() = AddTotpLoginItemData(
) )
/** /**
* Helper function for converting [AddTotpLoginItemData] to a serializable [AddTotpLoginItemDataJson]. * Helper function for converting [AddTotpLoginItemData] to a serializable
* [AddTotpLoginItemDataJson].
*/ */
private fun AddTotpLoginItemData.toJsonModel() = AddTotpLoginItemDataJson( private fun AddTotpLoginItemData.toJsonModel() = AddTotpLoginItemDataJson(
totpUri = totpUri, totpUri = totpUri,

View File

@@ -164,6 +164,7 @@ class AuthenticatorBridgeManagerTest {
verify { context.bindService(any(), any(), Context.BIND_AUTO_CREATE) } verify { context.bindService(any(), any(), Context.BIND_AUTO_CREATE) }
} }
@Suppress("MaxLineLength")
@Test @Test
fun `onStart when Bitwarden app is present and bindService succeeds should set state to Loading before service calls back`() { fun `onStart when Bitwarden app is present and bindService succeeds should set state to Loading before service calls back`() {
val mockIntent: Intent = mockk() val mockIntent: Intent = mockk()
@@ -195,7 +196,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true
@@ -224,7 +225,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true
@@ -243,7 +244,7 @@ class AuthenticatorBridgeManagerTest {
fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY
every { every {
mockBridgeService.checkSymmetricEncryptionKeyFingerprint( mockBridgeService.checkSymmetricEncryptionKeyFingerprint(
SYMMETRIC_KEY.toFingerprint().getOrNull() SYMMETRIC_KEY.toFingerprint().getOrNull(),
) )
} returns true } returns true
val serviceConnection = slot<ServiceConnection>() val serviceConnection = slot<ServiceConnection>()
@@ -257,7 +258,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true
@@ -278,7 +279,7 @@ class AuthenticatorBridgeManagerTest {
fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY
every { every {
mockBridgeService.checkSymmetricEncryptionKeyFingerprint( mockBridgeService.checkSymmetricEncryptionKeyFingerprint(
SYMMETRIC_KEY.toFingerprint().getOrNull() SYMMETRIC_KEY.toFingerprint().getOrNull(),
) )
} returns false } returns false
every { every {
@@ -295,7 +296,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true
@@ -313,7 +314,7 @@ class AuthenticatorBridgeManagerTest {
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
fun `onAccountsSync should set AccountSyncState to decrypted response`() { fun `onAccountsSync should set AccountSyncState to decrypted response`() {
val expectedAccounts = listOf<SharedAccountData.Account>( val expectedAccounts = listOf<SharedAccountData.Account>(
mockk() mockk(),
) )
val encryptedAccounts: EncryptedSharedAccountData = mockk() val encryptedAccounts: EncryptedSharedAccountData = mockk()
val decryptedAccounts: SharedAccountData = mockk { val decryptedAccounts: SharedAccountData = mockk {
@@ -323,7 +324,7 @@ class AuthenticatorBridgeManagerTest {
every { encryptedAccounts.decrypt(SYMMETRIC_KEY) } returns Result.success(decryptedAccounts) every { encryptedAccounts.decrypt(SYMMETRIC_KEY) } returns Result.success(decryptedAccounts)
every { every {
mockBridgeService.checkSymmetricEncryptionKeyFingerprint( mockBridgeService.checkSymmetricEncryptionKeyFingerprint(
SYMMETRIC_KEY.toFingerprint().getOrNull() SYMMETRIC_KEY.toFingerprint().getOrNull(),
) )
} returns true } returns true
fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY
@@ -340,7 +341,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true
@@ -363,7 +364,7 @@ class AuthenticatorBridgeManagerTest {
val encryptedAccounts: EncryptedSharedAccountData = mockk() val encryptedAccounts: EncryptedSharedAccountData = mockk()
every { every {
mockBridgeService.checkSymmetricEncryptionKeyFingerprint( mockBridgeService.checkSymmetricEncryptionKeyFingerprint(
SYMMETRIC_KEY.toFingerprint().getOrNull() SYMMETRIC_KEY.toFingerprint().getOrNull(),
) )
} returns true } returns true
fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY
@@ -380,7 +381,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true
@@ -406,7 +407,7 @@ class AuthenticatorBridgeManagerTest {
every { encryptedAccounts.decrypt(SYMMETRIC_KEY) } returns Result.failure(RuntimeException()) every { encryptedAccounts.decrypt(SYMMETRIC_KEY) } returns Result.failure(RuntimeException())
every { every {
mockBridgeService.checkSymmetricEncryptionKeyFingerprint( mockBridgeService.checkSymmetricEncryptionKeyFingerprint(
SYMMETRIC_KEY.toFingerprint().getOrNull() SYMMETRIC_KEY.toFingerprint().getOrNull(),
) )
} returns true } returns true
fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY fakeSymmetricKeyStorageProvider.symmetricKey = SYMMETRIC_KEY
@@ -423,7 +424,7 @@ class AuthenticatorBridgeManagerTest {
context.bindService( context.bindService(
any(), any(),
capture(serviceConnection), capture(serviceConnection),
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE,
) )
} returns true } returns true

View File

@@ -10,7 +10,7 @@ class AuthenticatorBridgeConnectionTypeExtensionsTest {
fun `toPackageName RELEASE should map to correct release package`() { fun `toPackageName RELEASE should map to correct release package`() {
assertEquals( assertEquals(
"com.x8bit.bitwarden", "com.x8bit.bitwarden",
AuthenticatorBridgeConnectionType.RELEASE.toPackageName() AuthenticatorBridgeConnectionType.RELEASE.toPackageName(),
) )
} }
@@ -18,7 +18,7 @@ class AuthenticatorBridgeConnectionTypeExtensionsTest {
fun `toPackageName DEV should map to correct dev package`() { fun `toPackageName DEV should map to correct dev package`() {
assertEquals( assertEquals(
"com.x8bit.bitwarden.dev", "com.x8bit.bitwarden.dev",
AuthenticatorBridgeConnectionType.DEV.toPackageName() AuthenticatorBridgeConnectionType.DEV.toPackageName(),
) )
} }
} }

View File

@@ -38,7 +38,7 @@ class EncryptionUtilTest {
@Test @Test
fun `toFingerprint should return success when there are no internal exceptions`() { fun `toFingerprint should return success when there are no internal exceptions`() {
val keyData = SymmetricEncryptionKeyData( val keyData = SymmetricEncryptionKeyData(
symmetricEncryptionKey = generateSecretKey().getOrThrow().encoded.toByteArrayContainer() symmetricEncryptionKey = generateSecretKey().getOrThrow().encoded.toByteArrayContainer(),
) )
val result = keyData.toFingerprint() val result = keyData.toFingerprint()
assertTrue(result.isSuccess) assertTrue(result.isSuccess)
@@ -49,7 +49,7 @@ class EncryptionUtilTest {
mockkStatic(MessageDigest::class) mockkStatic(MessageDigest::class)
every { MessageDigest.getInstance("SHA-256") } throws NoSuchAlgorithmException() every { MessageDigest.getInstance("SHA-256") } throws NoSuchAlgorithmException()
val keyData = SymmetricEncryptionKeyData( val keyData = SymmetricEncryptionKeyData(
symmetricEncryptionKey = generateSecretKey().getOrThrow().encoded.toByteArrayContainer() symmetricEncryptionKey = generateSecretKey().getOrThrow().encoded.toByteArrayContainer(),
) )
val result = keyData.toFingerprint() val result = keyData.toFingerprint()
assertTrue(result.isFailure) assertTrue(result.isFailure)
@@ -100,7 +100,7 @@ class EncryptionUtilTest {
.decrypt(SYMMETRIC_KEY) .decrypt(SYMMETRIC_KEY)
assertEquals( assertEquals(
SHARED_ACCOUNT_DATA, SHARED_ACCOUNT_DATA,
result.getOrThrow() result.getOrThrow(),
) )
} }
@@ -148,7 +148,7 @@ class EncryptionUtilTest {
.decrypt(SYMMETRIC_KEY) .decrypt(SYMMETRIC_KEY)
assertEquals( assertEquals(
ADD_TOTP_ITEM, ADD_TOTP_ITEM,
result.getOrThrow() result.getOrThrow(),
) )
} }
@@ -168,16 +168,16 @@ private val SHARED_ACCOUNT_DATA = SharedAccountData(
email = "johnyapples@test.com", email = "johnyapples@test.com",
environmentLabel = "bitwarden.com", environmentLabel = "bitwarden.com",
totpUris = listOf("test.com"), totpUris = listOf("test.com"),
) ),
) ),
) )
private val ADD_TOTP_ITEM = AddTotpLoginItemData( private val ADD_TOTP_ITEM = AddTotpLoginItemData(
totpUri = "test.com" totpUri = "test.com",
) )
private val SYMMETRIC_KEY = SymmetricEncryptionKeyData( private val SYMMETRIC_KEY = SymmetricEncryptionKeyData(
symmetricEncryptionKey = generateSecretKey().getOrThrow().encoded.toByteArrayContainer() symmetricEncryptionKey = generateSecretKey().getOrThrow().encoded.toByteArrayContainer(),
) )
private val ENCRYPTED_SHARED_ACCOUNT_DATA = private val ENCRYPTED_SHARED_ACCOUNT_DATA =