Skip to content

Commit f4f6696

Browse files
PM-19334: Propagate errors to the UI (#4893)
1 parent 475a82e commit f4f6696

File tree

18 files changed

+136
-115
lines changed

18 files changed

+136
-115
lines changed

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/KeyManagerImpl.kt

+22-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.security.KeyChain
55
import android.security.KeyChainException
66
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsCertificate
77
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost
8+
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
89
import com.x8bit.bitwarden.data.platform.manager.model.ImportPrivateKeyResult
910
import timber.log.Timber
1011
import java.io.IOException
@@ -24,7 +25,7 @@ class KeyManagerImpl(
2425
private val context: Context,
2526
) : KeyManager {
2627

27-
@Suppress("CyclomaticComplexMethod")
28+
@Suppress("LongMethod", "CyclomaticComplexMethod")
2829
override fun importMutualTlsCertificate(
2930
key: ByteArray,
3031
alias: String,
@@ -35,51 +36,59 @@ class KeyManagerImpl(
3536
.inputStream()
3637
.use { stream ->
3738
try {
38-
KeyStore.getInstance(KEYSTORE_TYPE_PKCS12)
39+
KeyStore
40+
.getInstance(KEYSTORE_TYPE_PKCS12)
3941
.also { it.load(stream, password.toCharArray()) }
4042
} catch (e: KeyStoreException) {
4143
Timber.Forest.e(e, "Failed to load PKCS12 bytes")
42-
return ImportPrivateKeyResult.Error.UnsupportedKey
44+
return ImportPrivateKeyResult.Error.UnsupportedKey(throwable = e)
4345
} catch (e: IOException) {
4446
Timber.Forest.e(e, "Format or password error while loading PKCS12 bytes")
4547
return when (e.cause) {
4648
is UnrecoverableKeyException -> {
47-
ImportPrivateKeyResult.Error.UnrecoverableKey
49+
ImportPrivateKeyResult.Error.UnrecoverableKey(throwable = e)
4850
}
4951

5052
else -> {
51-
ImportPrivateKeyResult.Error.KeyStoreOperationFailed
53+
ImportPrivateKeyResult.Error.KeyStoreOperationFailed(throwable = e)
5254
}
5355
}
5456
} catch (e: CertificateException) {
5557
Timber.Forest.e(e, "Unable to load certificate chain")
56-
return ImportPrivateKeyResult.Error.InvalidCertificateChain
58+
return ImportPrivateKeyResult.Error.InvalidCertificateChain(throwable = e)
5759
} catch (e: NoSuchAlgorithmException) {
5860
Timber.Forest.e(e, "Cryptographic algorithm not supported")
59-
return ImportPrivateKeyResult.Error.UnsupportedKey
61+
return ImportPrivateKeyResult.Error.UnsupportedKey(throwable = e)
6062
}
6163
}
6264

6365
// Step 2: Get a list of aliases and choose the first one.
6466
val internalAlias = pkcs12KeyStore.aliases()
6567
?.takeIf { it.hasMoreElements() }
6668
?.nextElement()
67-
?: return ImportPrivateKeyResult.Error.UnsupportedKey
69+
?: return ImportPrivateKeyResult.Error.UnsupportedKey(
70+
throwable = MissingPropertyException("Internal Alias"),
71+
)
6872

6973
// Step 3: Extract PrivateKey and X.509 certificate from the KeyStore and verify
7074
// certificate alias.
7175
val privateKey = try {
72-
pkcs12KeyStore.getKey(internalAlias, password.toCharArray())
73-
?: return ImportPrivateKeyResult.Error.UnrecoverableKey
76+
pkcs12KeyStore
77+
.getKey(internalAlias, password.toCharArray())
78+
?: return ImportPrivateKeyResult.Error.UnrecoverableKey(
79+
throwable = MissingPropertyException("Private Key"),
80+
)
7481
} catch (e: UnrecoverableKeyException) {
7582
Timber.Forest.e(e, "Failed to get private key")
76-
return ImportPrivateKeyResult.Error.UnrecoverableKey
83+
return ImportPrivateKeyResult.Error.UnrecoverableKey(throwable = e)
7784
}
7885

7986
val certChain: Array<Certificate> = pkcs12KeyStore
8087
.getCertificateChain(internalAlias)
8188
?.takeUnless { it.isEmpty() }
82-
?: return ImportPrivateKeyResult.Error.InvalidCertificateChain
89+
?: return ImportPrivateKeyResult.Error.InvalidCertificateChain(
90+
throwable = MissingPropertyException("Certificate Chain"),
91+
)
8392

8493
// Step 4: Store the private key and X.509 certificate in the AndroidKeyStore if the alias
8594
// does not exists.
@@ -92,7 +101,7 @@ class KeyManagerImpl(
92101
setKeyEntry(alias, privateKey, null, certChain)
93102
} catch (e: KeyStoreException) {
94103
Timber.Forest.e(e, "Failed to import key into Android KeyStore")
95-
return ImportPrivateKeyResult.Error.KeyStoreOperationFailed
104+
return ImportPrivateKeyResult.Error.KeyStoreOperationFailed(throwable = e)
96105
}
97106
}
98107
return ImportPrivateKeyResult.Success(alias)

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/ImportPrivateKeyResult.kt

+19-5
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,44 @@ sealed class ImportPrivateKeyResult {
1616
* Represents a generic error during the import process.
1717
*/
1818
sealed class Error : ImportPrivateKeyResult() {
19+
/**
20+
* The underlying error.
21+
*/
22+
abstract val throwable: Throwable?
1923

2024
/**
2125
* Indicates that the provided key is unrecoverable or the password is incorrect.
2226
*/
23-
data object UnrecoverableKey : Error()
27+
data class UnrecoverableKey(
28+
override val throwable: Throwable,
29+
) : Error()
2430

2531
/**
2632
* Indicates that the certificate chain associated with the key is invalid.
2733
*/
28-
data object InvalidCertificateChain : Error()
34+
data class InvalidCertificateChain(
35+
override val throwable: Throwable,
36+
) : Error()
2937

3038
/**
3139
* Indicates that the specified alias is already in use.
3240
*/
33-
data object DuplicateAlias : Error()
41+
data object DuplicateAlias : Error() {
42+
override val throwable: Throwable? = null
43+
}
3444

3545
/**
3646
* Indicates that an error occurred during the key store operation.
3747
*/
38-
data object KeyStoreOperationFailed : Error()
48+
data class KeyStoreOperationFailed(
49+
override val throwable: Throwable,
50+
) : Error()
3951

4052
/**
4153
* Indicates the provided key is not supported.
4254
*/
43-
data object UnsupportedKey : Error()
55+
data class UnsupportedKey(
56+
override val throwable: Throwable,
57+
) : Error()
4458
}
4559
}

app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,8 @@ class SettingsRepositoryImpl(
494494
}
495495

496496
override suspend fun setupBiometricsKey(cipher: Cipher): BiometricsKeyResult {
497-
val userId = activeUserId ?: return BiometricsKeyResult.Error
497+
val userId = activeUserId
498+
?: return BiometricsKeyResult.Error(error = NoActiveUserException())
498499
return vaultSdkSource
499500
.getUserEncryptionKey(userId = userId)
500501
.onSuccess { biometricsKey ->
@@ -508,7 +509,7 @@ class SettingsRepositoryImpl(
508509
}
509510
.fold(
510511
onSuccess = { BiometricsKeyResult.Success },
511-
onFailure = { BiometricsKeyResult.Error },
512+
onFailure = { BiometricsKeyResult.Error(error = it) },
512513
)
513514
}
514515

app/src/main/java/com/x8bit/bitwarden/data/platform/repository/model/BiometricsKeyResult.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ sealed class BiometricsKeyResult {
1212
/**
1313
* Generic error while setting up the biometrics key.
1414
*/
15-
data object Error : BiometricsKeyResult()
15+
data class Error(
16+
val error: Throwable,
17+
) : BiometricsKeyResult()
1618
}

app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/FindFido2CredentialsResult.kt

-19
This file was deleted.

app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/SaveCredentialResult.kt

-17
This file was deleted.

app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,10 @@ class CipherManagerImpl(
446446
?: return IllegalStateException("Attachment does not have a url").asFailure()
447447

448448
val encryptedFile = when (val result = fileManager.downloadFileToCache(url)) {
449-
DownloadResult.Failure -> return IllegalStateException("Download failed").asFailure()
449+
is DownloadResult.Failure -> {
450+
return IllegalStateException("Download failed", result.error).asFailure()
451+
}
452+
450453
is DownloadResult.Success -> result.file
451454
}
452455

app/src/main/java/com/x8bit/bitwarden/data/vault/manager/FileManagerImpl.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class FileManagerImpl(
4444
.getDataStream(url)
4545
.fold(
4646
onSuccess = { it },
47-
onFailure = { return DownloadResult.Failure },
47+
onFailure = { return DownloadResult.Failure(error = it) },
4848
)
4949

5050
// Create a temporary file in cache to write to
@@ -66,7 +66,7 @@ class FileManagerImpl(
6666
}
6767
fos.flush()
6868
} catch (e: RuntimeException) {
69-
return@withContext DownloadResult.Failure
69+
return@withContext DownloadResult.Failure(error = e)
7070
}
7171
}
7272
}
@@ -94,7 +94,7 @@ class FileManagerImpl(
9494
}
9595
}
9696
true
97-
} catch (exception: RuntimeException) {
97+
} catch (_: RuntimeException) {
9898
false
9999
}
100100
}
@@ -111,7 +111,7 @@ class FileManagerImpl(
111111
}
112112
}
113113
true
114-
} catch (exception: RuntimeException) {
114+
} catch (_: RuntimeException) {
115115
false
116116
}
117117
}

app/src/main/java/com/x8bit/bitwarden/data/vault/manager/model/DownloadResult.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ sealed class DownloadResult {
1414
/**
1515
* The download failed.
1616
*/
17-
data object Failure : DownloadResult()
17+
data class Failure(
18+
val error: Throwable,
19+
) : DownloadResult()
1820
}

app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockViewModel.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class SetupUnlockViewModel @Inject constructor(
164164
settingsRepository.storeUnlockPin(
165165
pin = state.pin,
166166
shouldRequireMasterPasswordOnRestart =
167-
state.shouldRequireMasterPasswordOnRestart,
167+
state.shouldRequireMasterPasswordOnRestart,
168168
)
169169
}
170170
}
@@ -182,7 +182,7 @@ class SetupUnlockViewModel @Inject constructor(
182182
action: SetupUnlockAction.Internal.BiometricsKeyResultReceive,
183183
) {
184184
when (action.result) {
185-
BiometricsKeyResult.Error -> {
185+
is BiometricsKeyResult.Error -> {
186186
mutableStateFlow.update {
187187
it.copy(
188188
dialogState = null,

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ class AccountSecurityViewModel @Inject constructor(
7474
?.hasMasterPassword != false,
7575
isUnlockWithPinEnabled = settingsRepository.isUnlockWithPinEnabled,
7676
shouldShowEnableAuthenticatorSync =
77-
featureFlagManager.getFeatureFlag(FlagKey.AuthenticatorSync) &&
78-
!isBuildVersionBelow(Build.VERSION_CODES.S),
77+
featureFlagManager.getFeatureFlag(FlagKey.AuthenticatorSync) &&
78+
!isBuildVersionBelow(Build.VERSION_CODES.S),
7979
userId = userId,
8080
vaultTimeout = settingsRepository.vaultTimeout,
8181
vaultTimeoutAction = settingsRepository.vaultTimeoutAction,
@@ -365,7 +365,7 @@ class AccountSecurityViewModel @Inject constructor(
365365
settingsRepository.storeUnlockPin(
366366
pin = state.pin,
367367
shouldRequireMasterPasswordOnRestart =
368-
state.shouldRequireMasterPasswordOnRestart,
368+
state.shouldRequireMasterPasswordOnRestart,
369369
)
370370
}
371371
}
@@ -464,7 +464,7 @@ class AccountSecurityViewModel @Inject constructor(
464464
action: AccountSecurityAction.Internal.BiometricsKeyResultReceive,
465465
) {
466466
when (action.result) {
467-
BiometricsKeyResult.Error -> {
467+
is BiometricsKeyResult.Error -> {
468468
mutableStateFlow.update {
469469
it.copy(
470470
dialog = null,

app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ class AuthRepositoryTest {
316316
GetTokenResponseJson.Success::toUserState,
317317
UserStateJson::toRemovedPasswordUserStateJson,
318318
)
319-
unmockkStatic(
319+
mockkConstructor(
320320
NoActiveUserException::class,
321321
MissingPropertyException::class,
322322
)

0 commit comments

Comments
 (0)