diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/errors/RecoveryByResetDialogFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/errors/RecoveryByResetDialogFactory.kt index da52bb3e6e5ceb049fee5dc6cdd0eec93fed6c8b..c8804aaad1d50729150f7a593bea8d8c80356665 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/errors/RecoveryByResetDialogFactory.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/errors/RecoveryByResetDialogFactory.kt @@ -14,17 +14,20 @@ class RecoveryByResetDialogFactory(private val fragment: Fragment) { fun showDialog( @StringRes detailsLink: Int, - onDismiss: () -> Unit + onPositive: () -> Unit ) { - AlertDialog.Builder(context) + val dialog = AlertDialog.Builder(context) .setTitle(R.string.errors_generic_headline) .setMessage(R.string.errors_generic_text_catastrophic_error_recovery_via_reset) .setCancelable(false) - .setOnDismissListener { onDismiss() } - .setNeutralButton(R.string.errors_generic_button_negative) { _, _ -> - ExternalActionHelper.openUrl(fragment, context.getString(detailsLink)) + .setNeutralButton(R.string.errors_generic_button_negative, null) + .setPositiveButton(R.string.errors_generic_button_positive) { _, _ -> + onPositive() } - .setPositiveButton(R.string.errors_generic_button_positive) { _, _ -> } - .show() + .create() + dialog.show() + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener { + ExternalActionHelper.openUrl(fragment, context.getString(detailsLink)) + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt index 297085ac4c254d5360524dd4d72133d7f62fbb43..785a30392ac5d339e9211e408f640534d9f93afd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt @@ -74,8 +74,8 @@ class EncryptionErrorResetTool @Inject constructor( } isResetWindowConsumed = true - val keyException = error.causes().singleOrNull { it is GeneralSecurityException } - if (keyException == null) { + val keyException = error.causes().lastOrNull() + if (keyException == null || keyException !is GeneralSecurityException) { Timber.v("Error has no GeneralSecurityException as cause -> no reset.") return false } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt index f1612cffdbd534e0dbb21d846b6441fd5c7e4363..bb6c5932ee2145053830a4bbe118a6ecc9ebf799 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/security/EncryptionResetToolTest.kt @@ -16,7 +16,9 @@ import org.junit.jupiter.api.Test import testhelpers.BaseIOTest import testhelpers.preferences.MockSharedPreferences import java.io.File +import java.io.IOException import java.security.GeneralSecurityException +import java.security.KeyException import java.security.KeyStoreException class EncryptionResetToolTest : BaseIOTest() { @@ -199,6 +201,86 @@ class EncryptionResetToolTest : BaseIOTest() { } } + @Test + fun `nested exception may have the same base exception type, ie GeneralSecurityException`() { + // https://github.com/corona-warn-app/cwa-app-android/issues/642#issuecomment-712188157 + createMockFiles() + + createInstance().tryResetIfNecessary( + KeyException( // subclass of GeneralSecurityException + "Permantly failed to instantiate encrypted preferences", + SecurityException( + "Could not decrypt key. decryption failed", + GeneralSecurityException("decryption failed") + ) + ) + ) shouldBe true + + encryptedPrefsFile.exists() shouldBe false + encryptedDatabaseFile.exists() shouldBe false + + mockPreferences.dataMapPeek.apply { + this["ea1851.reset.performedAt"] shouldNotBe null + this["ea1851.reset.windowconsumed"] shouldBe true + this["ea1851.reset.shownotice"] shouldBe true + } + } + + @Test + fun `exception check does not care about the first exception type`() { + createMockFiles() + + createInstance().tryResetIfNecessary( + CwaSecurityException( + KeyException( // subclass of GeneralSecurityException + "Permantly failed to instantiate encrypted preferences", + SecurityException( + "Could not decrypt key. decryption failed", + GeneralSecurityException("decryption failed") + ) + ) + ) + ) shouldBe true + + encryptedPrefsFile.exists() shouldBe false + encryptedDatabaseFile.exists() shouldBe false + + mockPreferences.dataMapPeek.apply { + this["ea1851.reset.performedAt"] shouldNotBe null + this["ea1851.reset.windowconsumed"] shouldBe true + this["ea1851.reset.shownotice"] shouldBe true + } + } + + @Test + fun `exception check DOES care about the most nested exception`() { + createMockFiles() + + createInstance().tryResetIfNecessary( + CwaSecurityException( + KeyException( // subclass of GeneralSecurityException + "Permantly failed to instantiate encrypted preferences", + SecurityException( + "Could not decrypt key. decryption failed", + GeneralSecurityException( + "decryption failed", + IOException("I am unexpeted") + ) + ) + ) + ) + ) shouldBe false + + encryptedPrefsFile.exists() shouldBe true + encryptedDatabaseFile.exists() shouldBe true + + mockPreferences.dataMapPeek.apply { + this["ea1851.reset.performedAt"] shouldBe null + this["ea1851.reset.windowconsumed"] shouldBe true + this["ea1851.reset.shownotice"] shouldBe null + } + } + @Test fun `we want only a specific type of GeneralSecurityException`() { createMockFiles()