diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt index 3fc77b8a43548980472ffa8020e6d20b1ec11299..121f7f19ff5c474b695211afb0405691d5f91f33 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizer.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.server.protocols.internal.ppdd.ElsOtp import de.rki.coronawarnapp.server.protocols.internal.ppdd.ElsOtpRequestAndroid +import de.rki.coronawarnapp.util.CWADebug import kotlinx.coroutines.flow.first import org.joda.time.Instant import timber.log.Timber @@ -26,6 +27,11 @@ class LogUploadAuthorizer @Inject constructor( suspend fun getAuthorizedOTP(otp: UUID = UUID.randomUUID()): LogUploadOtp { Timber.tag(TAG).d("getAuthorizedOTP() trying to authorize %s", otp) + // TODO ¯\_(ツ)_/¯ + if (!CWADebug.isDeviceForTestersBuild) { + throw UnsupportedOperationException() + } + val elsOtp = ElsOtp.ELSOneTimePassword.newBuilder().apply { setOtp(otp.toString()) }.build() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt index 8dbdda01a81613628f5c0ad3173d64397918f5d0..b145c1dca4989fa25452abc6189337080263a5f3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/BuildConfigWrap.kt @@ -6,6 +6,12 @@ import de.rki.coronawarnapp.BuildConfig @Suppress("MayBeConstant") object BuildConfigWrap { + val DEBUG: Boolean = BuildConfig.DEBUG + val FLAVOR: String = BuildConfig.FLAVOR + val BUILD_TYPE: String = BuildConfig.BUILD_TYPE + + val GIT_COMMIT_SHORT_HASH: String = BuildConfig.GIT_COMMIT_SHORT_HASH + val ENVIRONMENT_JSONDATA = BuildConfig.ENVIRONMENT_JSONDATA val ENVIRONMENT_TYPE_DEFAULT = BuildConfig.ENVIRONMENT_TYPE_DEFAULT diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt index b3540383e0d0dce5408ef69d4ca2f5cfe591398e..3e891ee68f66768355aad77d441fa5914cc4f92c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt @@ -57,7 +57,7 @@ class InformationFragment : Fragment(R.layout.fragment_information), AutoInject setButtonOnClickListener() setAccessibilityDelegate() - // TODO Hidden until further clarification regarding release schedule is available + // TODO ¯\_(ツ)_/¯ binding.informationDebuglog.rootLayout.isGone = !CWADebug.isDeviceForTestersBuild } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt index d08cc5a6c3dd54ec1e878886eeb09813c8d87455..9cfc42f4eb2fac80e98b96c934975aeb17e90f36 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CWADebug.kt @@ -4,14 +4,17 @@ import android.annotation.SuppressLint import android.app.Application import android.os.Build import androidx.annotation.VisibleForTesting -import de.rki.coronawarnapp.BuildConfig import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger +import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.util.debug.UncaughtExceptionLogger import de.rki.coronawarnapp.util.di.ApplicationComponent import timber.log.Timber object CWADebug { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + var debugLoggerFactory: (Application) -> DebugLogger = { DebugLogger(context = it) } + @SuppressLint("StaticFieldLeak") lateinit var debugLogger: DebugLogger @@ -24,15 +27,19 @@ object CWADebug { setupExceptionHandler() - debugLogger = DebugLogger(context = application).also { - it.init() + debugLogger = debugLoggerFactory(application).also { + // TODO Disabled until all parties are satisfied, search for ¯\_(ツ)_/¯ + if (isDeviceForTestersBuild) it.init() } logDeviceInfos() } fun initAfterInjection(component: ApplicationComponent) { - debugLogger.setInjectionIsReady(component) + // TODO ¯\_(ツ)_/¯ + if (isDeviceForTestersBuild) { + debugLogger.setInjectionIsReady(component) + } } val isLogging: Boolean @@ -42,12 +49,13 @@ object CWADebug { } val isDebugBuildOrMode: Boolean - get() = BuildConfig.DEBUG || buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS + get() = BuildConfigWrap.DEBUG || buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS val buildFlavor: BuildFlavor - get() = BuildFlavor.values().single { it.rawValue == BuildConfig.FLAVOR } + get() = BuildFlavor.values().single { it.rawValue == BuildConfigWrap.FLAVOR } - val isDeviceForTestersBuild: Boolean = buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS + val isDeviceForTestersBuild: Boolean + get() = buildFlavor == BuildFlavor.DEVICE_FOR_TESTERS enum class BuildFlavor(val rawValue: String) { DEVICE("device"), @@ -64,8 +72,8 @@ object CWADebug { } fun logDeviceInfos() { - Timber.i("CWA version: %s (%s)", BuildConfig.VERSION_CODE, BuildConfig.GIT_COMMIT_SHORT_HASH) - Timber.i("CWA flavor: %s (%s)", BuildConfig.FLAVOR, BuildConfig.BUILD_TYPE) + Timber.i("CWA version: %s (%s)", BuildConfigWrap.VERSION_CODE, BuildConfigWrap.GIT_COMMIT_SHORT_HASH) + Timber.i("CWA flavor: %s (%s)", BuildConfigWrap.FLAVOR, BuildConfigWrap.BUILD_TYPE) Timber.i("Build.FINGERPRINT: %s", Build.FINGERPRINT) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt index 9211c208ac46fab9ed713599db11e38789e7213c..f7557924b1dada96fa221eb94ec53a28a6396af8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt @@ -13,9 +13,13 @@ class UncaughtExceptionLogger( override fun uncaughtException(thread: Thread, error: Throwable) { Timber.tag(thread.name).e(error, "Uncaught exception!") - if (CWADebug.isLogging) { - // Make sure this crash is written before killing the app. - Thread.sleep(1500) + try { + if (CWADebug.isLogging) { + // Make sure this crash is written before killing the app. + Thread.sleep(1500) + } + } catch (e: Exception) { + Timber.w("Couldn't delay exception for debug logger.") } wrappedHandler?.uncaughtException(thread, error) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt index b2a8b3c9fa39ca76780f3ce666666641dd33f53b..d04943c8dd24e4bde7879594aef7b5f87bed8c2a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/auth/LogUploadAuthorizerTest.kt @@ -6,6 +6,8 @@ import de.rki.coronawarnapp.appconfig.LogUploadConfig import de.rki.coronawarnapp.appconfig.SafetyNetRequirements import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid +import de.rki.coronawarnapp.util.CWADebug +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -13,6 +15,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just +import io.mockk.mockkObject import io.mockk.slot import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest @@ -46,6 +49,9 @@ class LogUploadAuthorizerTest : BaseIOTest() { fun setup() { MockKAnnotations.init(this) + mockkObject(CWADebug) + every { CWADebug.isDeviceForTestersBuild } returns true + every { configData.logUpload } returns logUploadConfig every { logUploadConfig.safetyNetRequirements } returns safetyNetRequirements @@ -82,4 +88,16 @@ class LogUploadAuthorizerTest : BaseIOTest() { attestationRequestSlot.captured.configData shouldBe configData attestationRequestSlot.captured.checkDeviceTime shouldBe false } + + @Test + fun `upload is not possible on prod builds`() = runBlockingTest { + every { CWADebug.isDeviceForTestersBuild } returns false + + val expectedOtp = UUID.fromString("15cff19f-af26-41bc-94f2-c1a65075e894") + val instance = createInstance() + + shouldThrow<UnsupportedOperationException> { + instance.getAuthorizedOTP(otp = expectedOtp) + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt index 45a8b3e1ef2b03347324d72f0a8c4ad9ca32b37d..52a0c791b7ad45b9a47eaa8ad202002480ee68bd 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragmentViewModelTest.kt @@ -4,9 +4,12 @@ import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.ui.Country import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just import io.mockk.verify import kotlinx.coroutines.flow.flowOf import org.junit.jupiter.api.BeforeEach @@ -25,6 +28,7 @@ class InteroperabilityConfigurationFragmentViewModelTest { fun setupFreshViewModel() { MockKAnnotations.init(this) + coEvery { interoperabilityRepository.refreshCountries() } just Runs every { interoperabilityRepository.countryList } returns flowOf(Country.values().toList()) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/CWADebugTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/CWADebugTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c29454f47f6d852cee30ccc20efc6dd262b52d6e --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/CWADebugTest.kt @@ -0,0 +1,77 @@ +package de.rki.coronawarnapp.util + +import android.app.Application +import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.util.di.ApplicationComponent +import io.kotest.matchers.shouldBe +import io.mockk.Called +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import io.mockk.verifySequence +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import java.io.File + +class CWADebugTest : BaseTest() { + + @MockK lateinit var application: Application + @MockK lateinit var appComponent: ApplicationComponent + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { application.cacheDir } returns File("cache") + + mockkObject(BuildConfigWrap) + every { BuildConfigWrap.FLAVOR } returns "device" + } + + @Test + fun `flavor check`() { + CWADebug.isDeviceForTestersBuild shouldBe false + + every { BuildConfigWrap.FLAVOR } returns "deviceForTesters" + CWADebug.isDeviceForTestersBuild shouldBe true + CWADebug.buildFlavor shouldBe CWADebug.BuildFlavor.DEVICE_FOR_TESTERS + + every { BuildConfigWrap.FLAVOR } returns "device" + CWADebug.buildFlavor shouldBe CWADebug.BuildFlavor.DEVICE + CWADebug.isDeviceForTestersBuild shouldBe false + } + + @Test + fun `logging is only initialized on tester builds`() { + val debugLogger: DebugLogger = mockk() + CWADebug.debugLoggerFactory = { debugLogger } + CWADebug.init(application) + CWADebug.initAfterInjection(appComponent) + verify { debugLogger wasNot Called } + } + + @Test + fun `logging is initialized on deviceForTester builds`() { + every { BuildConfigWrap.FLAVOR } returns "deviceForTesters" + + val debugLogger = mockk<DebugLogger>().apply { + every { init() } just Runs + every { setInjectionIsReady(appComponent) } just Runs + } + + CWADebug.debugLoggerFactory = { debugLogger } + CWADebug.init(application) + CWADebug.initAfterInjection(appComponent) + verifySequence { + debugLogger.init() + debugLogger.setInjectionIsReady(appComponent) + } + } +}