From 886184d5b9d3b648d4ada6ec64fa6b55a21f5f25 Mon Sep 17 00:00:00 2001 From: Kolya Opahle <k.opahle@sap.com> Date: Tue, 13 Apr 2021 17:08:50 +0200 Subject: [PATCH] Verify important QR code attributes on scan (EXPOSUREAPP-6378, EXPOSUREAPP-6399) (#2816) * Implemented TraceLocationVerifier that checks scanned qr codes for required conditions Also added some error strings to support information propagation to the user (took them from ios could not find a design) * Fixed CheckInsViewModelTest * Added Additional checks to TraceLocationVerifier (EXPOSUREAPP-6399) * Fixed linting issues * Fixed linting issues caused by fixing linting issues * Fixed linting issues caused by auto formatting the fixed lint issues * Implemented first set of unit tests and changed the start end time condition to allow start and end to equal Changed AlertDialog Builder to MaterialAltertDialogBuilder Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- .../checkins/qrcode/TraceLocationVerifier.kt | 76 +++++++++ .../attendee/checkins/CheckInEvent.kt | 3 + .../attendee/checkins/CheckInsFragment.kt | 12 +- .../attendee/checkins/CheckInsViewModel.kt | 23 +-- .../values-de/event_registration_strings.xml | 10 ++ .../res/values/event_registration_strings.xml | 10 ++ .../checkins/qrcode/InvalidQRCodeProvider.kt | 148 ++++++++++++++++++ .../qrcode/TraceLocationVerifierTest.kt | 32 ++++ .../checkins/qrcode/ValidQRCodeProvider.kt | 104 ++++++++++++ .../checkins/CheckInsViewModelTest.kt | 5 + 10 files changed, 413 insertions(+), 10 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifier.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeProvider.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifierTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidQRCodeProvider.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifier.kt new file mode 100644 index 000000000..abd313609 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifier.kt @@ -0,0 +1,76 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import androidx.annotation.StringRes +import dagger.Reusable +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import javax.inject.Inject + +@Reusable +class TraceLocationVerifier @Inject constructor() { + @Suppress("ReturnCount") + fun verifyTraceLocation(protoQrCodePayload: TraceLocationOuterClass.QRCodePayload): VerificationResult { + val traceLocation = protoQrCodePayload.traceLocation() + + if (traceLocation.description.isEmpty()) { + return VerificationResult.Invalid.Description + } + + if (traceLocation.description.length > QR_CODE_DESCRIPTION_MAX_LENGTH) { + return VerificationResult.Invalid.Description + } + + if (traceLocation.description.lines().size > 1) { + return VerificationResult.Invalid.Description + } + + if (traceLocation.address.isEmpty()) { + return VerificationResult.Invalid.Address + } + + if (traceLocation.address.length > QR_CODE_ADDRESS_MAX_LENGTH) { + return VerificationResult.Invalid.Address + } + + if (traceLocation.address.lines().size > 1) { + return VerificationResult.Invalid.Address + } + + // If both are 0 do nothing else check start is smaller than end or return error + if (!( + protoQrCodePayload.locationData.startTimestamp == 0L && + protoQrCodePayload.locationData.endTimestamp == 0L + ) + ) { + if (protoQrCodePayload.locationData.startTimestamp > protoQrCodePayload.locationData.endTimestamp) { + return VerificationResult.Invalid.StartEndTime + } + } + + if (traceLocation.cryptographicSeed.size != CROWD_NOTIFIER_CRYPTO_SEED_LENGTH) { + return VerificationResult.Invalid.CryptographicSeed + } + + return VerificationResult.Valid( + VerifiedTraceLocation(protoQrCodePayload) + ) + } + + sealed class VerificationResult { + data class Valid(val verifiedTraceLocation: VerifiedTraceLocation) : VerificationResult() + + sealed class Invalid(@StringRes val errorTextRes: Int) : VerificationResult() { + object Description : Invalid(R.string.trace_location_checkins_qr_code_invalid_description) + object Address : Invalid(R.string.trace_location_checkins_qr_code_invalid_address) + object StartEndTime : Invalid(R.string.trace_location_checkins_qr_code_invalid_times) + object CryptographicSeed : + Invalid(R.string.trace_location_checkins_qr_code_invalid_cryptographic_seed) + } + } + + companion object { + private const val CROWD_NOTIFIER_CRYPTO_SEED_LENGTH = 16 + private const val QR_CODE_DESCRIPTION_MAX_LENGTH = 100 + private const val QR_CODE_ADDRESS_MAX_LENGTH = 100 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt index e39108ce2..a8c0aba51 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins +import androidx.annotation.StringRes import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.qrcode.VerifiedTraceLocation @@ -11,6 +12,8 @@ sealed class CheckInEvent { data class ConfirmCheckIn(val verifiedTraceLocation: VerifiedTraceLocation) : CheckInEvent() + data class InvalidQrCode(@StringRes val errorTextRes: Int) : CheckInEvent() + data class ConfirmCheckInWithoutHistory(val verifiedTraceLocation: VerifiedTraceLocation) : CheckInEvent() data class EditCheckIn(val checkInId: Long, val position: Int) : CheckInEvent() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt index 5b9a042b7..d198e93cc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt @@ -7,6 +7,7 @@ import android.os.Bundle import android.provider.Settings import android.view.View import android.widget.Toast +import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.core.net.toUri @@ -16,6 +17,7 @@ import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DefaultItemAnimator +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.transition.Hold import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.BuildConfig @@ -38,7 +40,6 @@ import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted import timber.log.Timber -import java.lang.Exception import java.net.URLEncoder import javax.inject.Inject @@ -98,6 +99,8 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag ) } + is CheckInEvent.InvalidQrCode -> showInvalidQrCodeInformation(event.errorTextRes) + is CheckInEvent.ConfirmCheckInWithoutHistory -> doNavigate( CheckInsFragmentDirections.actionCheckInsFragmentToConfirmCheckInFragmentCleanHistory( verifiedTraceLocation = event.verifiedTraceLocation @@ -132,6 +135,13 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag } } + private fun showInvalidQrCodeInformation(@StringRes errorTextRes: Int) = + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.errors_generic_headline) + setMessage(errorTextRes) + setPositiveButton(R.string.errors_generic_button_positive) { _, _ -> } + }.show() + private fun updateViews(items: List<CheckInsItem>) { checkInsAdapter.update(items) binding.apply { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt index f80b589f1..94c98114e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt @@ -9,7 +9,7 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeUriParser -import de.rki.coronawarnapp.eventregistration.checkins.qrcode.VerifiedTraceLocation +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationVerifier import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.presencetracing.checkins.checkout.CheckOutHandler @@ -28,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import timber.log.Timber +@Suppress("LongParameterList") class CheckInsViewModel @AssistedInject constructor( @Assisted private val savedState: SavedStateHandle, @Assisted private val deepLink: String?, @@ -37,7 +38,8 @@ class CheckInsViewModel @AssistedInject constructor( private val qrCodeUriParser: QRCodeUriParser, private val checkInsRepository: CheckInRepository, private val checkOutHandler: CheckOutHandler, - private val cameraPermissionProvider: CameraPermissionProvider + private val cameraPermissionProvider: CameraPermissionProvider, + private val traceLocationVerifier: TraceLocationVerifier ) : CWAViewModel(dispatcherProvider) { val events = SingleLiveEvent<CheckInEvent>() @@ -143,13 +145,16 @@ class CheckInsViewModel @AssistedInject constructor( try { Timber.i("uri: $uri") val qrCodePayload = qrCodeUriParser.getQrCodePayload(uri) - val verifiedTraceLocation = VerifiedTraceLocation(qrCodePayload) - events.postValue( - if (cleanHistory) - CheckInEvent.ConfirmCheckInWithoutHistory(verifiedTraceLocation) - else - CheckInEvent.ConfirmCheckIn(verifiedTraceLocation) - ) + when (val verifyResult = traceLocationVerifier.verifyTraceLocation(qrCodePayload)) { + is TraceLocationVerifier.VerificationResult.Valid -> events.postValue( + if (cleanHistory) + CheckInEvent.ConfirmCheckInWithoutHistory(verifyResult.verifiedTraceLocation) + else + CheckInEvent.ConfirmCheckIn(verifyResult.verifiedTraceLocation) + ) + is TraceLocationVerifier.VerificationResult.Invalid -> + events.postValue(CheckInEvent.InvalidQrCode(verifyResult.errorTextRes)) + } } catch (e: Exception) { Timber.d(e, "TraceLocation verification failed") e.report(ExceptionCategory.INTERNAL) diff --git a/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml index 732c3f2c5..758b6ca6e 100644 --- a/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml @@ -259,4 +259,14 @@ <!-- XBUT: Organizer Flow : Title for save as template button --> <string name="trace_location_event_detail_save_as_template_button">"Als Vorlage verwenden"</string> + <!-- Qr Code Validation Messages --> + <!-- XTXT: My check-ins: qr code validation wrong description field --> + <string name="trace_location_checkins_qr_code_invalid_description">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_DESCRIPTION)."</string> + <!-- XTXT: My check-ins: qr code validation wrong address field --> + <string name="trace_location_checkins_qr_code_invalid_address">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_ADDRESS)."</string> + <!-- XTXT: My check-ins: qr code validation wrong start end time field --> + <string name="trace_location_checkins_qr_code_invalid_times">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_TIMESTAMPS)."</string> + <!-- XTXT: My check-ins: qr code validation wrong cryptographic seed field --> + <string name="trace_location_checkins_qr_code_invalid_cryptographic_seed">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_CRYPTO_SEED)."</string> + </resources> diff --git a/Corona-Warn-App/src/main/res/values/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values/event_registration_strings.xml index 453cda224..9896181c8 100644 --- a/Corona-Warn-App/src/main/res/values/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values/event_registration_strings.xml @@ -257,4 +257,14 @@ <!-- XBUT: Organizer Flow : Title for save as template button --> <string name="trace_location_event_detail_save_as_template_button">"Use as Template"</string> + <!-- Qr Code Validation Messages --> + <!-- XTXT: My check-ins: qr code validation wrong description field --> + <string name="trace_location_checkins_qr_code_invalid_description">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_DESCRIPTION)."</string> + <!-- XTXT: My check-ins: qr code validation wrong address field --> + <string name="trace_location_checkins_qr_code_invalid_address">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_ADDRESS)."</string> + <!-- XTXT: My check-ins: qr code validation wrong start end time field --> + <string name="trace_location_checkins_qr_code_invalid_times">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_TIMESTAMPS)."</string> + <!-- XTXT: My check-ins: qr code validation wrong cryptographic seed field --> + <string name="trace_location_checkins_qr_code_invalid_cryptographic_seed">"Ungültiger QR-Code verhindert das Einchecken. Die verantwortliche Stelle muss einen neuen QR-Code generieren (Fehlercode INVALID_CRYPTO_SEED)."</string> + </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeProvider.kt new file mode 100644 index 000000000..6d8df6b97 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeProvider.kt @@ -0,0 +1,148 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import de.rki.coronawarnapp.util.toProtoByteString +import okio.ByteString.Companion.decodeBase64 +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import java.util.stream.Stream + +class InvalidQRCodeProvider : ArgumentsProvider { + private fun baseValidQrCodeBuilder(): TraceLocationOuterClass.QRCodePayload.Builder = + TraceLocationOuterClass.QRCodePayload.newBuilder() + .setVersion(1) + .setCrowdNotifierData( + TraceLocationOuterClass.CrowdNotifierData.newBuilder() + .setCryptographicSeed(CRYPTOGRAPHIC_SEED.decodeBase64()!!.toProtoByteString()) + .setPublicKey(PUB_KEY.decodeBase64()!!.toProtoByteString()) + .setVersion(1) + ) + .setVendorData("CAEQARgK".decodeBase64()!!.toProtoByteString()) + .setLocationData( + TraceLocationOuterClass.TraceLocation.newBuilder() + .setDescription("Icecream Shop") + .setAddress("Main Street 1") + .setVersion(1) + .build() + ) + + private fun baseValidLocationData(): TraceLocationOuterClass.TraceLocation.Builder = + TraceLocationOuterClass.TraceLocation.newBuilder() + .setDescription("Icecream Shop") + .setAddress("Main Street 1") + .setVersion(1) + + override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> { + return Stream.of( + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setStartTimestamp(2687991) + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.StartEndTime + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setStartTimestamp(2687991) + .setEndTimestamp(2387991) + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.StartEndTime + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setDescription("") + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Description + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .clearDescription() + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Description + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setDescription((0..101).joinToString { "a" }) + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Description + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setDescription("A \n B") + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Description + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setAddress("") + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Address + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .clearAddress() + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Address + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setAddress((0..101).joinToString { "a" }) + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Address + ), + Arguments.of( + baseValidQrCodeBuilder() + .setLocationData( + baseValidLocationData() + .setAddress("A \n B") + .build() + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.Address + ), + Arguments.of( + baseValidQrCodeBuilder() + .setCrowdNotifierData( + TraceLocationOuterClass.CrowdNotifierData.newBuilder() + .setCryptographicSeed("WNlQ==".decodeBase64()!!.toProtoByteString()) + .setPublicKey(PUB_KEY.decodeBase64()!!.toProtoByteString()) + .setVersion(1) + ).build(), + TraceLocationVerifier.VerificationResult.Invalid.CryptographicSeed + ) + ) + } + + companion object { + const val CRYPTOGRAPHIC_SEED = "zveDikIfwAXWqI6h4dWNlQ==" + const val PUB_KEY = + "OMTa6eYSiaDv8lW13xdYEvGHOZ1EYTiFSxt51HEoPCD7CNnvCUiIYPhax1MpkN0UfNClCm9ZWYy0JH01CDVD9" + + "eq+voxQ1EcFJQkEIujVwoCNK0MNGuDK1ayjGxeDc4UD" + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifierTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifierTest.kt new file mode 100644 index 000000000..1bc786543 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/TraceLocationVerifierTest.kt @@ -0,0 +1,32 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import io.kotest.matchers.shouldBe +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource +import testhelpers.BaseTest + +class TraceLocationVerifierTest : BaseTest() { + fun createInstance() = TraceLocationVerifier() + + @ParameterizedTest + @ArgumentsSource(ValidQRCodeProvider::class) + fun `Valid QR Codes`( + protoQrCodePayload: TraceLocationOuterClass.QRCodePayload + ) { + val validationResult = createInstance().verifyTraceLocation(protoQrCodePayload) + + (validationResult is TraceLocationVerifier.VerificationResult.Valid) shouldBe true + } + + @ParameterizedTest + @ArgumentsSource(InvalidQRCodeProvider::class) + fun `Invalid QR Codes`( + protoQrCodePayload: TraceLocationOuterClass.QRCodePayload, + expectedFailure: TraceLocationVerifier.VerificationResult.Invalid + ) { + val validationResult = createInstance().verifyTraceLocation(protoQrCodePayload) + + validationResult shouldBe expectedFailure + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidQRCodeProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidQRCodeProvider.kt new file mode 100644 index 000000000..bd08d2846 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidQRCodeProvider.kt @@ -0,0 +1,104 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import de.rki.coronawarnapp.util.toProtoByteString +import okio.ByteString.Companion.decodeBase64 +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import java.util.stream.Stream + +class ValidQRCodeProvider : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> { + return Stream.of( + Arguments.of( + TraceLocationOuterClass.QRCodePayload.newBuilder() + .setVersion(1) + .setCrowdNotifierData( + TraceLocationOuterClass.CrowdNotifierData.newBuilder() + .setCryptographicSeed(CRYPTOGRAPHIC_SEED.decodeBase64()!!.toProtoByteString()) + .setPublicKey(PUB_KEY.decodeBase64()!!.toProtoByteString()) + .setVersion(1) + ) + .setVendorData("CAEQAg==".decodeBase64()!!.toProtoByteString()) + .setLocationData( + TraceLocationOuterClass.TraceLocation.newBuilder() + .setDescription("My Birthday Party") + .setAddress("at my place") + .setStartTimestamp(2687955) + .setEndTimestamp(2687991) + .setVersion(1) + .build() + ) + .build() + ), + Arguments.of( + TraceLocationOuterClass.QRCodePayload.newBuilder() + .setVersion(1) + .setCrowdNotifierData( + TraceLocationOuterClass.CrowdNotifierData.newBuilder() + .setCryptographicSeed(CRYPTOGRAPHIC_SEED.decodeBase64()!!.toProtoByteString()) + .setPublicKey(PUB_KEY.decodeBase64()!!.toProtoByteString()) + .setVersion(1) + ) + .setVendorData("CAEQAg==".decodeBase64()!!.toProtoByteString()) + .setLocationData( + TraceLocationOuterClass.TraceLocation.newBuilder() + .setDescription("My Birthday Party") + .setAddress("at my place") + .setStartTimestamp(2687991) + .setEndTimestamp(2687991) + .setVersion(1) + .build() + ) + .build() + ), + Arguments.of( + TraceLocationOuterClass.QRCodePayload.newBuilder() + .setVersion(1) + .setCrowdNotifierData( + TraceLocationOuterClass.CrowdNotifierData.newBuilder() + .setCryptographicSeed(CRYPTOGRAPHIC_SEED.decodeBase64()!!.toProtoByteString()) + .setPublicKey(PUB_KEY.decodeBase64()!!.toProtoByteString()) + .setVersion(1) + ) + .setVendorData("CAEQAg==".decodeBase64()!!.toProtoByteString()) + .setLocationData( + TraceLocationOuterClass.TraceLocation.newBuilder() + .setDescription("My Birthday Party") + .setAddress("at my place") + .setEndTimestamp(2687991) + .setVersion(1) + .build() + ) + .build() + ), + Arguments.of( + TraceLocationOuterClass.QRCodePayload.newBuilder() + .setVersion(1) + .setCrowdNotifierData( + TraceLocationOuterClass.CrowdNotifierData.newBuilder() + .setCryptographicSeed(CRYPTOGRAPHIC_SEED.decodeBase64()!!.toProtoByteString()) + .setPublicKey(PUB_KEY.decodeBase64()!!.toProtoByteString()) + .setVersion(1) + ) + .setVendorData("CAEQARgK".decodeBase64()!!.toProtoByteString()) + .setLocationData( + TraceLocationOuterClass.TraceLocation.newBuilder() + .setDescription("Icecream Shop") + .setAddress("Main Street 1") + .setVersion(1) + .build() + ) + .build() + ) + ) + } + + companion object { + const val CRYPTOGRAPHIC_SEED = "zveDikIfwAXWqI6h4dWNlQ==" + const val PUB_KEY = + "OMTa6eYSiaDv8lW13xdYEvGHOZ1EYTiFSxt51HEoPCD7CNnvCUiIYPhax1MpkN0UfNClCm9ZWYy0JH01CDVD9" + + "eq+voxQ1EcFJQkEIujVwoCNK0MNGuDK1ayjGxeDc4UD" + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt index 1d0e83df0..7efbed5de 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeUriParser +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationVerifier import de.rki.coronawarnapp.presencetracing.checkins.checkout.CheckOutHandler import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.items.ActiveCheckInVH @@ -41,6 +42,7 @@ class CheckInsViewModelTest : BaseTest() { @MockK lateinit var checkInsRepository: CheckInRepository @MockK lateinit var checkOutHandler: CheckOutHandler @MockK lateinit var cameraPermissionProvider: CameraPermissionProvider + @MockK lateinit var traceLocationVerifier: TraceLocationVerifier @BeforeEach fun setup() { @@ -48,6 +50,8 @@ class CheckInsViewModelTest : BaseTest() { every { savedState.set(any(), any<String>()) } just Runs every { checkInsRepository.checkInsWithinRetention } returns flowOf() every { cameraPermissionProvider.deniedPermanently } returns flowOf(false) + every { traceLocationVerifier.verifyTraceLocation(any()) } returns + TraceLocationVerifier.VerificationResult.Valid(mockk()) } @Test @@ -186,6 +190,7 @@ class CheckInsViewModelTest : BaseTest() { checkInsRepository = checkInsRepository, checkOutHandler = checkOutHandler, cameraPermissionProvider = cameraPermissionProvider, + traceLocationVerifier = traceLocationVerifier, cleanHistory = false ) -- GitLab