diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index b6d471b6ce16d2a6fb54c2b96d5446cf281d028d..146ca2e4b8175e844d913450a30ae3d94519a1b4 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -40,8 +40,8 @@ android { applicationId 'de.rki.coronawarnapp' minSdkVersion 23 targetSdkVersion 29 - versionCode 47 - versionName "1.5.0" + versionCode 48 + versionName "1.5.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/TimeVariables.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/TimeVariables.kt index 99a10696c70e8c606c401de795f3b686418ce689..dc90f38b4cc4d9daacf006ed8cc8296f64f29f54 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/TimeVariables.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/TimeVariables.kt @@ -33,8 +33,10 @@ object TimeVariables { /** * The maximal runtime of a transaction * In milliseconds + * Stay below 10min with this timeout! + * We only 10min background execution time via WorkManager. */ - private const val TRANSACTION_TIMEOUT = 180000L + private const val TRANSACTION_TIMEOUT = 8 * 60 * 1000L /** * Getter function for [TRANSACTION_TIMEOUT] diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt index db82cafc9c5b7084710bd046145f62b75eb560dd..d08f8aa4f3081d61263d85dc552ac002bb0ec473 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt @@ -66,7 +66,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { if (errorResetTool.isResetNoticeToBeShown) { RecoveryByResetDialogFactory(this).showDialog( detailsLink = R.string.errors_generic_text_catastrophic_error_encryption_failure, - onDismiss = { + onPositive = { errorResetTool.isResetNoticeToBeShown = false } ) 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 de29ea8f6815868e084d7e29fc6da8f74729620f..af80cfb1f152fc30ec5ffde942f0acc5b4a53aab 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 @@ -71,8 +71,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/risk/TimeVariablesTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/TimeVariablesTest.kt index 4055ddf1fb9875c2d340e257cfb13c91c5843a4b..4b46519654961026b9e13fea1bfc934cb4d899e2 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/TimeVariablesTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/TimeVariablesTest.kt @@ -16,7 +16,7 @@ class TimeVariablesTest { @Test fun getTransactionTimeout() { - Assert.assertEquals(TimeVariables.getTransactionTimeout(), 180000L) + Assert.assertEquals(TimeVariables.getTransactionTimeout(), 480000L) } @Test 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 7daab4ac94863c9880ca2387accf09aec4a4710b..d420042e18ea6ef9d24234156ff96c8244f3dbe2 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 @@ -14,7 +14,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() { @@ -169,6 +171,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()