Skip to content
This repository was archived by the owner on Jun 20, 2023. It is now read-only.

SRS errors (EXPOSUREAPP-14175) #5701

Merged
merged 6 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ class SrsLocalChecker @Inject constructor(
reliableDuration,
onboardingInHours
)
throw SrsSubmissionException(ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED)
throw SrsSubmissionException(
errorCode = ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED,
errorArgs = arrayOf(onboardingInHours.toHours(), onboardingInHours.toHours())
)
}

val durationSinceSubmission = Duration.between(
Expand All @@ -74,7 +77,10 @@ class SrsLocalChecker @Inject constructor(
durationSinceSubmission,
submissionsInDays
)
throw SrsSubmissionException(ErrorCode.SUBMISSION_TOO_EARLY)
throw SrsSubmissionException(
errorCode = ErrorCode.SUBMISSION_TOO_EARLY,
errorArgs = arrayOf(submissionsInDays.toDays())
)
}

Timber.d("Local prerequisites are met -> Congratulations!")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,76 @@
package de.rki.coronawarnapp.srs.core.error

import android.content.Context
import androidx.annotation.StringRes
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.util.HasHumanReadableError
import de.rki.coronawarnapp.util.HumanReadableError
import timber.log.Timber

class SrsSubmissionException(
val errorCode: ErrorCode,
private val errorArgs: Array<Any> = emptyArray(),
override val cause: Throwable? = null
) : Exception(errorCode.code, cause) {

enum class ErrorCode(val code: String) {
DEVICE_TIME_INCORRECT("DEVICE_TIME_INCORRECT"),
DEVICE_TIME_UNVERIFIED("DEVICE_TIME_UNVERIFIED"),
SRS_OTP_CLIENT_ERROR("SRS_OTP_CLIENT_ERROR"),
SRS_OTP_NO_NETWORK("SRS_OTP_NO_NETWORK"),
SRS_OTP_SERVER_ERROR("SRS_OTP_SERVER_ERROR"),
SRS_OTP_400("SRS_OTP_400"),
SRS_OTP_401("SRS_OTP_401"),
SRS_OTP_403("SRS_OTP_403"),
SRS_SUB_CLIENT_ERROR("SRS_SUB_CLIENT_ERROR"),
SRS_SUB_NO_NETWORK("SRS_SUB_NO_NETWORK"),
SRS_SUB_SERVER_ERROR("SRS_SUB_SERVER_ERROR"),
SRS_SUB_400("SRS_SUB_400"),
SRS_SUB_403("SRS_SUB_403"),
TIME_SINCE_ONBOARDING_UNVERIFIED("TIME_SINCE_ONBOARDING_UNVERIFIED"),
SUBMISSION_TOO_EARLY("SUBMISSION_TOO_EARLY"),
) : Exception(errorCode.code, cause), HasHumanReadableError {

enum class ErrorCode(val code: String, val textKey: TextKey) {
DEVICE_TIME_INCORRECT("DEVICE_TIME_INCORRECT", TextKey.CHANGE_DEVICE_TIME),
DEVICE_TIME_UNVERIFIED("DEVICE_TIME_UNVERIFIED", TextKey.CHANGE_DEVICE_TIME),
SUBMISSION_TOO_EARLY("SUBMISSION_TOO_EARLY", TextKey.SUBMISSION_TOO_EARLY),
TIME_SINCE_ONBOARDING_UNVERIFIED(
"TIME_SINCE_ONBOARDING_UNVERIFIED",
TextKey.TIME_SINCE_ONBOARDING_UNVERIFIED
),

SRS_OTP_CLIENT_ERROR("SRS_OTP_CLIENT_ERROR", TextKey.CALL_HOTLINE),
SRS_OTP_NO_NETWORK("SRS_OTP_NO_NETWORK", TextKey.NO_NETWORK),
SRS_OTP_SERVER_ERROR("SRS_OTP_SERVER_ERROR", TextKey.TRY_AGAIN_LATER),
SRS_OTP_400("SRS_OTP_400", TextKey.CALL_HOTLINE),
SRS_OTP_401("SRS_OTP_401", TextKey.CALL_HOTLINE),
SRS_OTP_403("SRS_OTP_403", TextKey.CALL_HOTLINE),

SRS_SUB_CLIENT_ERROR("SRS_SUB_CLIENT_ERROR", TextKey.CALL_HOTLINE),
SRS_SUB_NO_NETWORK("SRS_SUB_NO_NETWORK", TextKey.NO_NETWORK),
SRS_SUB_SERVER_ERROR("SRS_SUB_SERVER_ERROR", TextKey.TRY_AGAIN_LATER),
SRS_SUB_400("SRS_SUB_400", TextKey.CALL_HOTLINE),
SRS_SUB_403("SRS_SUB_403", TextKey.CALL_HOTLINE),

// Local
ANDROID_ID_INVALID_LOCAL("ANDROID_ID_INVALID_LOCAL"),
ATTESTATION_FAILED("ATTESTATION_FAILED"),
ATTESTATION_REQUEST_FAILED("ATTESTATION_REQUEST_FAILED"),
PLAY_SERVICES_VERSION_MISMATCH("PLAY_SERVICES_VERSION_MISMATCH"),
NONCE_MISMATCH("NONCE_MISMATCH"),
BASIC_INTEGRITY_REQUIRED("BASIC_INTEGRITY_REQUIRED"),
CTS_PROFILE_MATCH_REQUIRED("CTS_PROFILE_MATCH_REQUIRED"),
EVALUATION_TYPE_BASIC_REQUIRED("EVALUATION_TYPE_BASIC_REQUIRED"),
EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED("EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED"),
ANDROID_ID_INVALID_LOCAL("ANDROID_ID_INVALID_LOCAL", TextKey.DEVICE_NOT_TRUSTED),
ATTESTATION_FAILED("ATTESTATION_FAILED", TextKey.TRY_AGAIN_LATER),
ATTESTATION_REQUEST_FAILED("ATTESTATION_REQUEST_FAILED", TextKey.TRY_AGAIN_LATER),
PLAY_SERVICES_VERSION_MISMATCH("PLAY_SERVICES_VERSION_MISMATCH", TextKey.UPDATE_PLAY_SERVICES),
NONCE_MISMATCH("NONCE_MISMATCH", TextKey.TRY_AGAIN_LATER),
BASIC_INTEGRITY_REQUIRED("BASIC_INTEGRITY_REQUIRED", TextKey.DEVICE_NOT_TRUSTED),
CTS_PROFILE_MATCH_REQUIRED("CTS_PROFILE_MATCH_REQUIRED", TextKey.DEVICE_NOT_TRUSTED),
EVALUATION_TYPE_BASIC_REQUIRED("EVALUATION_TYPE_BASIC_REQUIRED", TextKey.DEVICE_NOT_TRUSTED),
EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED(
"EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED",
TextKey.DEVICE_NOT_TRUSTED
),

// Server
ANDROID_ID_INVALID("ANDROID_ID_INVALID"),
APK_CERTIFICATE_MISMATCH("APK_CERTIFICATE_MISMATCH"),
APK_PACKAGE_NAME_MISMATCH("APK_PACKAGE_NAME_MISMATCH"),
ATTESTATION_EXPIRED("ATTESTATION_EXPIRED"),
DEVICE_QUOTA_EXCEEDED("DEVICE_QUOTA_EXCEEDED"),
JWS_SIGNATURE_VERIFICATION_FAILED("JWS_SIGNATURE_VERIFICATION_FAILED"),
SALT_REDEEMED("SALT_REDEEMED");
ANDROID_ID_INVALID("ANDROID_ID_INVALID", TextKey.DEVICE_NOT_TRUSTED),
APK_CERTIFICATE_MISMATCH("APK_CERTIFICATE_MISMATCH", TextKey.DEVICE_NOT_TRUSTED),
APK_PACKAGE_NAME_MISMATCH("APK_PACKAGE_NAME_MISMATCH", TextKey.DEVICE_NOT_TRUSTED),
ATTESTATION_EXPIRED("ATTESTATION_EXPIRED", TextKey.TRY_AGAIN_LATER),
DEVICE_QUOTA_EXCEEDED("DEVICE_QUOTA_EXCEEDED", TextKey.SUBMISSION_TOO_EARLY),
JWS_SIGNATURE_VERIFICATION_FAILED("JWS_SIGNATURE_VERIFICATION_FAILED", TextKey.TRY_AGAIN_LATER),
SALT_REDEEMED("SALT_REDEEMED", TextKey.TRY_AGAIN_LATER);

enum class TextKey(
@StringRes val title: Int = R.string.srs_error_title,
@StringRes val message: Int,
) {
CALL_HOTLINE(message = R.string.srs_error_call_hotline),
CHANGE_DEVICE_TIME(message = R.string.srs_error_device_time),
DEVICE_NOT_TRUSTED(message = R.string.srs_error_device_not_trusted),
NO_NETWORK(message = R.string.srs_error_no_network),
SUBMISSION_TOO_EARLY(message = R.string.srs_error_submission_too_early),
TIME_SINCE_ONBOARDING_UNVERIFIED(message = R.string.srs_error_time_since_onboarding_unverified),
TRY_AGAIN_LATER(message = R.string.srs_error_try_again_later),
UPDATE_PLAY_SERVICES(message = R.string.srs_error_update_play_services),
}

companion object {
fun fromAuthErrorCode(code: String) = values().find { code == it.code } ?: run {
Expand All @@ -51,4 +79,9 @@ class SrsSubmissionException(
}
}
}

override fun toHumanReadableError(context: Context) = HumanReadableError(
title = context.getString(errorCode.textKey.title),
description = context.getString(errorCode.textKey.message, *errorArgs)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
import javax.inject.Inject
import dagger.Lazy
import dagger.Reusable
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.exception.http.CwaUnknownHostException
import de.rki.coronawarnapp.exception.http.NetworkConnectTimeoutException
import de.rki.coronawarnapp.exception.http.NetworkReadTimeoutException
Expand All @@ -19,6 +20,7 @@ import de.rki.coronawarnapp.srs.core.storage.SrsDevSettings
import de.rki.coronawarnapp.tag
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.serialization.BaseJackson
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.Instant
Expand All @@ -30,6 +32,7 @@ class SrsAuthorizationServer @Inject constructor(
@BaseJackson private val mapper: ObjectMapper,
private val dispatcherProvider: DispatcherProvider,
private val srsDevSettings: SrsDevSettings,
private val appConfigProvider: AppConfigProvider,
) {
private val api = srsAuthorizationApi.get()

Expand Down Expand Up @@ -73,7 +76,13 @@ class SrsAuthorizationServer @Inject constructor(
val bodyResponse = api.authenticate(headers, srsOtpRequest)
val response = bodyResponse.body()?.charStream()?.use { mapper.readValue<SrsAuthorizationResponse>(it) }
return when {
response?.errorCode != null -> throw SrsSubmissionException(ErrorCode.fromAuthErrorCode(response.errorCode))
response?.errorCode != null -> {
val errorCode = ErrorCode.fromAuthErrorCode(response.errorCode)
throw SrsSubmissionException(
errorCode = errorCode,
errorArgs = errorArgs(errorCode)
)
}
response?.expirationDate != null -> OffsetDateTime.parse(response.expirationDate).toInstant()
else -> throw when (bodyResponse.code()) {
400 -> SrsSubmissionException(ErrorCode.SRS_OTP_400)
Expand All @@ -86,6 +95,29 @@ class SrsAuthorizationServer @Inject constructor(
}
}

private suspend fun errorArgs(errorCode: ErrorCode): Array<Any> = when (errorCode) {
ErrorCode.SUBMISSION_TOO_EARLY -> arrayOf(
appConfigProvider
.currentConfig
.first()
.selfReportSubmission
.common
.timeBetweenSubmissionsInDays
.toDays()
)
ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED -> {
val hours = appConfigProvider
.currentConfig
.first()
.selfReportSubmission
.common
.timeSinceOnboardingInHours
.toHours()
arrayOf(hours, hours)
}
else -> emptyArray()
}

companion object {
val TAG = tag<SrsAuthorizationServer>()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ class SubmissionDispatcherFragment : Fragment(R.layout.fragment_submission_dispa
}

viewModel.srsError.observe2(this) {
// TODO to be finished in Error handling task EXPOSUREAPP-14175
displayDialog { setError(it) }
displayDialog {
setError(it)
positiveButton(R.string.nm_faq_label) { openUrl(R.string.srs_faq_url) }
negativeButton(android.R.string.ok)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions Corona-Warn-App/src/main/res/values-de/links.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@
<string name="settings_tracing_status_location_body_url">https://www.coronawarn.app/de/faq/#android_location</string>
<!-- XTXT: Home Menu Button - URL to social media section of the community page -->
<string name="home_menu_social_media_url">"https://www.coronawarn.app/de/community/#socialmedia"</string>
<string name="srs_faq_url">https://www.coronawarn.app/de/faq/results/#warn_without_tan</string>
</resources>
20 changes: 20 additions & 0 deletions Corona-Warn-App/src/main/res/values-de/srs_submission_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,24 @@
<string name="srs_cancel_dialog_positive_button">Warnen fortsetzen</string>
<!-- XTXT: TSRS cancel dialog negative button -->
<string name="srs_cancel_dialog_negative_button">Nicht warnen</string>

<!-- Srs Errors-->
<!-- XTXT: SRS error title -->
<string name="srs_error_title">Andere warnen nicht möglich</string>
<!-- XTXT: SRS CALL_HOTLINE error message -->
<string name="srs_error_call_hotline">Ein Fehler ist aufgetreten. Bitte versuchen Sie die Warnung später erneut zu senden oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.</string>
<!-- XTXT: SRS CHANGE_DEVICE_TIME error message -->
<string name="srs_error_device_time">Die Uhrzeit Ihres Smartphones stimmt nicht mit der aktuellen Zeit überein. Bitte korrigieren Sie die Uhrzeit in den Einstellungen Ihres Smartphones. Nach der Korrektur kann es einige Minuten dauern, bis die App die Uhrzeit erneut überprüft. Danach können Sie erneut versuchen zu warnen.</string>
<!-- XTXT: SRS DEVICE_NOT_TRUSTED error message -->
<string name="srs_error_device_not_trusted">Ihr Gerät erfüllt nicht die notwendigen Sicherheitsanforderungen für diese Art von Warnung. Einen offiziellen Test können Sie jederzeit in der App registrieren und andere damit warnen.</string>
<!-- XTXT: SRS NO_NETWORK error message -->
<string name="srs_error_no_network">Möglicherweise wurde Ihre Internet-Verbindung unterbrochen. Bitte prüfen Sie die Verbindung und versuchen Sie es erneut.</string>
<!-- XTXT: SRS SUBMISSION_TOO_EARLY error message -->
<string name="srs_error_submission_too_early">Sie können momentan andere nicht warnen, da Sie innerhalb der vergangenen %d Tage bereits ein positives Testergebnis gemeldet haben. Dies dient der Vermeidung von Missbrauch. Einen offiziellen Test können Sie jederzeit in der App registrieren und andere damit warnen.</string>
<!-- XTXT: SRS TIME_SINCE_ONBOARDING_UNVERIFIED error message -->
<string name="srs_error_time_since_onboarding_unverified">Aus Sicherheitsgründen können Sie erst %d Stunden, nachdem Sie die App installiert oder aktualisiert haben, diese Art von Warnung senden. Bitte versuchen Sie es in %d Stunden erneut.</string>
<!-- XTXT: SRS TRY_AGAIN_LATER error message -->
<string name="srs_error_try_again_later">Ein Fehler ist aufgetreten. Bitte versuchen Sie die Warnung erneut zu senden.</string>
<!-- XTXT: SRS UPDATE_PLAY_SERVICES error message -->
<string name="srs_error_update_play_services">Die Warnung kann aktuell nicht gesendet werden. Bitte aktualisieren Sie die Google Play Services.</string>
</resources>
1 change: 1 addition & 0 deletions Corona-Warn-App/src/main/res/values/links.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@
<string name="settings_tracing_status_location_body_url">"https://www.coronawarn.app/en/faq/#android_location"</string>
<!-- XTXT: Home Menu Button - URL to social media section of the community page -->
<string name="home_menu_social_media_url">"https://www.coronawarn.app/en/community/#socialmedia"</string>
<string name="srs_faq_url">https://www.coronawarn.app/en/faq/results/#warn_without_tan</string>

</resources>
20 changes: 20 additions & 0 deletions Corona-Warn-App/src/main/res/values/srs_submission_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,24 @@
<!-- XTXT: TSRS cancel dialog negative button -->
<string name="srs_cancel_dialog_negative_button">Nicht warnen</string>

<!-- Srs Errors-->
<!-- XTXT: SRS error title -->
<string name="srs_error_title">Andere warnen nicht möglich</string>
<!-- XTXT: SRS CALL_HOTLINE error message -->
<string name="srs_error_call_hotline">Ein Fehler ist aufgetreten. Bitte versuchen Sie die Warnung später erneut zu senden oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.</string>
<!-- XTXT: SRS CHANGE_DEVICE_TIME error message -->
<string name="srs_error_device_time">Die Uhrzeit Ihres Smartphones stimmt nicht mit der aktuellen Zeit überein. Bitte korrigieren Sie die Uhrzeit in den Einstellungen Ihres Smartphones. Nach der Korrektur kann es einige Minuten dauern, bis die App die Uhrzeit erneut überprüft. Danach können Sie erneut versuchen zu warnen.</string>
<!-- XTXT: SRS DEVICE_NOT_TRUSTED error message -->
<string name="srs_error_device_not_trusted">Ihr Gerät erfüllt nicht die notwendigen Sicherheitsanforderungen für diese Art von Warnung. Einen offiziellen Test können Sie jederzeit in der App registrieren und andere damit warnen.</string>
<!-- XTXT: SRS NO_NETWORK error message -->
<string name="srs_error_no_network">Möglicherweise wurde Ihre Internet-Verbindung unterbrochen. Bitte prüfen Sie die Verbindung und versuchen Sie es erneut.</string>
<!-- XTXT: SRS SUBMISSION_TOO_EARLY error message -->
<string name="srs_error_submission_too_early">Sie können momentan andere nicht warnen, da Sie innerhalb der vergangenen %d Tage bereits ein positives Testergebnis gemeldet haben. Dies dient der Vermeidung von Missbrauch. Einen offiziellen Test können Sie jederzeit in der App registrieren und andere damit warnen.</string>
<!-- XTXT: SRS TIME_SINCE_ONBOARDING_UNVERIFIED error message -->
<string name="srs_error_time_since_onboarding_unverified">Aus Sicherheitsgründen können Sie erst %d Stunden, nachdem Sie die App installiert oder aktualisiert haben, diese Art von Warnung senden. Bitte versuchen Sie es in %d Stunden erneut.</string>
<!-- XTXT: SRS TRY_AGAIN_LATER error message -->
<string name="srs_error_try_again_later">Ein Fehler ist aufgetreten. Bitte versuchen Sie die Warnung erneut zu senden.</string>
<!-- XTXT: SRS UPDATE_PLAY_SERVICES error message -->
<string name="srs_error_update_play_services">Die Warnung kann aktuell nicht gesendet werden. Bitte aktualisieren Sie die Google Play Services.</string>

</resources>
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package de.rki.coronawarnapp.srs.core.server

import com.google.protobuf.ByteString
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.ConfigData
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfigContainer
import de.rki.coronawarnapp.exception.http.CwaUnknownHostException
import de.rki.coronawarnapp.exception.http.NetworkConnectTimeoutException
import de.rki.coronawarnapp.exception.http.NetworkReadTimeoutException
Expand All @@ -16,7 +19,9 @@ import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import okhttp3.ResponseBody.Companion.toResponseBody
import org.junit.jupiter.api.BeforeEach
Expand All @@ -31,6 +36,8 @@ internal class SrsAuthorizationServerTest : BaseTest() {

@MockK lateinit var srsAuthorizationApi: SrsAuthorizationApi
@MockK lateinit var srsDevSettings: SrsDevSettings
@MockK lateinit var appConfigProvider: AppConfigProvider
@MockK lateinit var configData: ConfigData

private val request = SrsAuthorizationRequest(
srsOtp = SrsOtp(),
Expand All @@ -50,6 +57,8 @@ internal class SrsAuthorizationServerTest : BaseTest() {
""".trimIndent().toResponseBody()
)
coEvery { srsDevSettings.forceAndroidIdAcceptance() } returns false
every { configData.selfReportSubmission } returns SelfReportSubmissionConfigContainer.DEFAULT
every { appConfigProvider.currentConfig } returns flowOf(configData)
}

@Test
Expand Down Expand Up @@ -161,5 +170,6 @@ internal class SrsAuthorizationServerTest : BaseTest() {
dispatcherProvider = TestDispatcherProvider(),
mapper = SerializationModule.jacksonBaseMapper,
srsDevSettings = srsDevSettings,
appConfigProvider = appConfigProvider,
)
}