diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt index 83f1ef199236247f35bcf3adb7883c88d27d585d..d24aeab0b212a66a988e556eb1a658ea8f751257 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt @@ -1,109 +1,161 @@ -package de.rki.coronawarnapp.eventregistration.checkins.qrcode - -import de.rki.coronawarnapp.environment.EnvironmentSetup -import de.rki.coronawarnapp.util.security.SignatureValidation -import io.kotest.assertions.throwables.shouldNotThrowAny -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.test.runBlockingTest -import org.joda.time.Instant -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import testhelpers.BaseTestInstrumentation - -@RunWith(JUnit4::class) -@Ignore("FIXME: Provide new encoded signed trace location samples") -class DefaultQRCodeVerifierTest : BaseTestInstrumentation() { - - @MockK lateinit var environmentSetup: EnvironmentSetup - private lateinit var qrCodeVerifier: QRCodeVerifier - - @Before - fun setUp() { - MockKAnnotations.init(this) - every { environmentSetup.appConfigVerificationKey } returns PUB_KEY - qrCodeVerifier = DefaultQRCodeVerifier(SignatureValidation(environmentSetup)) - } - - @Test - fun verifyEventSuccess() = runBlockingTest { - val time = 2687960 * 1_000L - val instant = Instant.ofEpochMilli(time) - shouldNotThrowAny { - val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT) - verifyResult.apply { - singedTraceLocation.location.description shouldBe "CWA Launch Party" - verifyResult.isBeforeStartTime(instant) shouldBe false - verifyResult.isAfterEndTime(instant) shouldBe false - } - } - } - - @Test - fun verifyEventStartTimeWaning() = runBlockingTest { - val time = 2687940 * 1_000L - val instant = Instant.ofEpochMilli(time) - shouldNotThrowAny { - val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT) - verifyResult.apply { - singedTraceLocation.location.description shouldBe "CWA Launch Party" - } - verifyResult.isBeforeStartTime(instant) shouldBe true - verifyResult.isAfterEndTime(instant) shouldBe false - } - } - - @Test - fun verifyEventEndTimeWarning() = runBlockingTest { - val instant = Instant.now() - shouldNotThrowAny { - val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT) - verifyResult.apply { - singedTraceLocation.location.description shouldBe "CWA Launch Party" - } - verifyResult.isBeforeStartTime(instant) shouldBe false - verifyResult.isAfterEndTime(instant) shouldBe true - } - } - - @Test - fun verifyEventWithInvalidKey() = runBlockingTest { - every { environmentSetup.appConfigVerificationKey } returns INVALID_PUB_KEY - shouldThrow<InvalidQRCodeSignatureException> { - qrCodeVerifier.verify(ENCODED_EVENT) - } - } - - @Test - fun eventHasMalformedData() = runBlockingTest { - shouldThrow<InvalidQRCodeDataException> { - qrCodeVerifier.verify(INVALID_ENCODED_EVENT) - } - } - - companion object { - - private const val INVALID_PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg" + - "3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==" - - private const val PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafIKZOiRPuJWjKOUmKv7OTJWTyii" + - "4oCQLcGn3FgYoLQaJIvAM3Pl7anFDPPY/jxfqqrLyGc0f6hWQ9JPR3QjBw==" - - private const val INVALID_ENCODED_EVENT = - "BIPEY33SMVWSA2LQON2W2IDEN5WG64RAONUXIIDBNVSXILBAMNXRBCM4UQARRKM6UQASAHRKCC7CTDWGQ" + - "4JCO7RVZSWVIMQK4UPA.GBCAEIA7TEORBTUA25QHBOCWT26BCA5PORBS2E4FFWMJ3U" + - "U3P6SXOL7SHUBCA7UEZBDDQ2R6VRJH7WBJKVF7GZYJA6YMRN27IPEP7NKGGJSWX3XQ" - - private const val ENCODED_EVENT = - "BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBOJ2" + - "HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGC" + - "PUZ2RQACAYEJ3HQYMAFFBU2SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU" + - "7TYERH23B746RQTABO3CTI=" - } -} +//package de.rki.coronawarnapp.eventregistration.checkins.qrcode +// +//import com.google.protobuf.ByteString +//import de.rki.coronawarnapp.environment.EnvironmentSetup +//import de.rki.coronawarnapp.eventregistration.common.decodeBase32 +//import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +//import de.rki.coronawarnapp.util.security.SignatureValidation +//import io.kotest.assertions.throwables.shouldNotThrowAny +//import io.kotest.assertions.throwables.shouldThrow +//import io.kotest.matchers.shouldBe +//import io.mockk.MockKAnnotations +//import io.mockk.every +//import io.mockk.impl.annotations.MockK +//import kotlinx.coroutines.test.runBlockingTest +//import okio.ByteString.Companion.toByteString +//import org.joda.time.Instant +//import org.junit.Before +//import org.junit.Ignore +//import org.junit.Test +//import org.junit.runner.RunWith +//import org.junit.runners.JUnit4 +//import testhelpers.BaseTestInstrumentation +// +//@RunWith(JUnit4::class) +//@Ignore("Until format is clarified") +//class DefaultQRCodeVerifierTest : BaseTestInstrumentation() { +// +// @MockK lateinit var environmentSetup: EnvironmentSetup +// private lateinit var qrCodeVerifier: QRCodeVerifier +// +// @Before +// fun setUp() { +// MockKAnnotations.init(this) +// every { environmentSetup.appConfigVerificationKey } returns PUB_KEY +// qrCodeVerifier = DefaultQRCodeVerifier(SignatureValidation(environmentSetup)) +// } +// +// @Test +// fun verifyEventSuccess() = runBlockingTest { +// val time = 2687960 * 1_000L +// val instant = Instant.ofEpochMilli(time) +// shouldNotThrowAny { +// val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT.decodeBase32().toByteArray()) +// verifyResult.apply { +// singedTraceLocation.location.description shouldBe "CWA Launch Party" +// verifyResult.isBeforeStartTime(instant) shouldBe false +// verifyResult.isAfterEndTime(instant) shouldBe false +// } +// } +// } +// +// @Test +// fun verifyEventStartTimeWaning() = runBlockingTest { +// val time = 2687940 * 1_000L +// val instant = Instant.ofEpochMilli(time) +// shouldNotThrowAny { +// val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT.decodeBase32().toByteArray()) +// verifyResult.apply { +// singedTraceLocation.location.description shouldBe "CWA Launch Party" +// } +// verifyResult.isBeforeStartTime(instant) shouldBe true +// verifyResult.isAfterEndTime(instant) shouldBe false +// } +// } +// +// @Test +// fun verifyEventEndTimeWarning() = runBlockingTest { +// val instant = Instant.now() +// shouldNotThrowAny { +// val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT.decodeBase32().toByteArray()) +// verifyResult.apply { +// singedTraceLocation.location.description shouldBe "CWA Launch Party" +// } +// verifyResult.isBeforeStartTime(instant) shouldBe false +// verifyResult.isAfterEndTime(instant) shouldBe true +// } +// } +// +// @Test +// fun verifyEventWithInvalidKey() = runBlockingTest { +// every { environmentSetup.appConfigVerificationKey } returns INVALID_PUB_KEY +// shouldThrow<InvalidQRCodeSignatureException> { +// qrCodeVerifier.verify(ENCODED_EVENT.decodeBase32().toByteArray()) +// } +// } +// +// @Test +// fun eventHasMalformedData() = runBlockingTest { +// shouldThrow<InvalidQRCodeDataException> { +// qrCodeVerifier.verify(INVALID_ENCODED_EVENT.decodeBase32().toByteArray()) +// } +// } +// +// @Test +// fun decodingTest1() = runBlockingTest { +// val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.newBuilder().apply { +// signature = ByteString.copyFromUtf8( +// "MEYCIQCNSNL6E/XyCaemkM6//CIBo+goZKJi/URimqcvwIKzCgIhAOfZPRAfZBRmwpq4sbxrLs3EhY3i914aO4lJ59XCFhwk" +// ) +// location = TraceLocationOuterClass.TraceLocation.parseFrom( +// "BISDGMBVGUZTGMLDFUZDGMBWFU2DGZRTFU4TONBSFU3GIODGMFRDKNDFHA2DQEABDABCEEKNPEQEE2LSORUGIYLZEBIGC4TUPEVAWYLUEBWXSIDQNRQWGZJQ2OD2IAJY66D2IAKAAA" +// .decodeBase32().toByteArray() +// ) +// }.build() +// +// val base32 = signedTraceLocation.toByteArray().toByteString().base32() +// +// shouldNotThrowAny { +// val verifyResult = qrCodeVerifier.verify(base32.decodeBase32().toByteArray()) +// +// verifyResult.apply { +// singedTraceLocation.location.description shouldBe "My Birthday Party" +// signedTraceLocation.signature shouldBe "MEYCIQCNSNL6E/XyCaemkM6//CIBo+goZKJi/URimqcvwIKzCgIhAOfZPRAfZBRmwpq4sbxrLs3EhY3i914aO4lJ59XCFhwk" +// } +// } +// } +// +// @Test +// fun decodingTest2() = runBlockingTest { +// val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.newBuilder().apply { +// signature = ByteString.copyFromUtf8( +// "MEUCIFpHvUqYAIP0Mq86R7kNO4EgRSvGJHbOlDraauKZvkgbAiEAh93bBDYviEtym4q5Oqzd7j6Dp1MLCP7YwCKlVcU2DHc=" +// ) +// location = TraceLocationOuterClass.TraceLocation.parseFrom( +// "BISGMY3BHA2GEMZXFU3DCYZQFU2GCN3DFVRDEZRYFU4DENLDMFSGINJQGZRWMEABDAASEDKJMNSWG4TFMFWSAU3IN5YCUDKNMFUW4ICTORZGKZLUEAYTAABYABAAU" +// .decodeBase32().toByteArray() +// ) +// }.build() +// +// val base32 = signedTraceLocation.toByteArray().toByteString().base32() +// +// shouldNotThrowAny { +// val verifyResult = qrCodeVerifier.verify(base32.decodeBase32().toByteArray()) +// +// verifyResult.apply { +// singedTraceLocation.location.description shouldBe "Icecream Shop" +// signedTraceLocation.signature shouldBe "MEUCIFpHvUqYAIP0Mq86R7kNO4EgRSvGJHbOlDraauKZvkgbAiEAh93bBDYviEtym4q5Oqzd7j6Dp1MLCP7YwCKlVcU2DHc=" +// } +// } +// } +// +// companion object { +// +// private const val INVALID_PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg" + +// "3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==" +// +// private const val PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafIKZOiRPuJWjKOUmKv7OTJWTyii" + +// "4oCQLcGn3FgYoLQaJIvAM3Pl7anFDPPY/jxfqqrLyGc0f6hWQ9JPR3QjBw==" +// +// private const val INVALID_ENCODED_EVENT = +// "BIPEY33SMVWSA2LQON2W2IDEN5WG64RAONUXIIDBNVSXILBAMNXRBCM4UQARRKM6UQASAHRKCC7CTDWGQ" + +// "4JCO7RVZSWVIMQK4UPA.GBCAEIA7TEORBTUA25QHBOCWT26BCA5PORBS2E4FFWMJ3U" + +// "U3P6SXOL7SHUBCA7UEZBDDQ2R6VRJH7WBJKVF7GZYJA6YMRN27IPEP7NKGGJSWX3XQ" +// +// private const val ENCODED_EVENT = +// "BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBOJ2" + +// "HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGC" + +// "PUZ2RQACAYEJ3HQYMAFFBU2SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU" + +// "7TYERH23B746RQTABO3CTI=" +// } +//} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocationKtTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/VerifiedTraceLocationKtTest.kt similarity index 99% rename from Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocationKtTest.kt rename to Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/VerifiedTraceLocationKtTest.kt index 9935f9eed60890c9939c8cc860ee6cf9f603e5ef..7484ff51ef4b6fa7b90741eb759a65d7486cce25 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocationKtTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/VerifiedTraceLocationKtTest.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin +package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult import de.rki.coronawarnapp.eventregistration.common.decodeBase32 diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt index db0a9559c20c59b0671a9bddb49cbd4dc1eac113..3a18b4fdb774d9311a629df5b0337736842d4a3b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt @@ -1,8 +1,8 @@ package de.rki.coronawarnapp.eventregistration.checkins.qrcode -import de.rki.coronawarnapp.eventregistration.common.decodeBase32 import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass import de.rki.coronawarnapp.util.security.SignatureValidation +import okio.ByteString.Companion.decodeBase64 import timber.log.Timber import javax.inject.Inject @@ -10,11 +10,11 @@ class DefaultQRCodeVerifier @Inject constructor( private val signatureValidation: SignatureValidation ) : QRCodeVerifier { - override suspend fun verify(encodedTraceLocation: String): QRCodeVerifyResult { - Timber.tag(TAG).v("Verifying: %s", encodedTraceLocation) + override suspend fun verify(rawTraceLocation: ByteArray): QRCodeVerifyResult { + Timber.tag(TAG).v("Verifying: %s", rawTraceLocation) val signedTraceLocation = try { - TraceLocationOuterClass.SignedTraceLocation.parseFrom(encodedTraceLocation.decodeBase32().toByteArray()) + TraceLocationOuterClass.SignedTraceLocation.parseFrom(rawTraceLocation) } catch (e: Exception) { throw InvalidQRCodeDataException(cause = e, message = "QR-code data could not be parsed.") } @@ -23,7 +23,7 @@ class DefaultQRCodeVerifier @Inject constructor( val isValid = try { signatureValidation.hasValidSignature( signedTraceLocation.location.toByteArray(), - sequenceOf(signedTraceLocation.signature.toByteArray()) + sequenceOf(signedTraceLocation.signature.toStringUtf8().decodeBase64()!!.toByteArray()) ) } catch (e: Exception) { throw InvalidQRCodeDataException(cause = e, message = "Verification failed.") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeUriParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeUriParser.kt new file mode 100644 index 0000000000000000000000000000000000000000..094761cf245ffd2e5ed92b1daaf93ddcc6e8eae2 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeUriParser.kt @@ -0,0 +1,46 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import dagger.Reusable +import de.rki.coronawarnapp.eventregistration.common.decodeBase32 +import okio.ByteString +import timber.log.Timber +import java.net.URI +import javax.inject.Inject + +@Reusable +class QRCodeUriParser @Inject constructor() { + + /** + * Validate that QRCode scanned uri matches the following formulas: + * https://e.coronawarn.app/c1/SIGNED_TRACE_LOCATION_BASE32 + * HTTPS://E.CORONAWARN.APP/C1/SIGNED_TRACE_LOCATION_BASE32 + */ + fun getSignedTraceLocation(maybeUri: String): ByteString? = URI.create(maybeUri).run { + if (!scheme.equals(SCHEME, true)) return@run null + if (!authority.equals(AUTHORITY, true)) return@run null + + if (!path.substringBeforeLast("/").equals(PATH_PREFIX, true)) return@run null + + val rawData = path.substringAfterLast("/") + val paddingDiff = 8 - (rawData.length % 8) + val maybeBase32 = rawData + createPadding(paddingDiff) + + if (!maybeBase32.matches(BASE32_REGEX)) return@run null + + return@run try { + maybeBase32.decodeBase32() + } catch (e: Exception) { + Timber.w(e, "Data wasn't base32: %s", maybeBase32) + null + } + } + + companion object { + private fun createPadding(length: Int) = (0 until length).joinToString(separator = "") { "=" } + + private const val SCHEME = "https" + private const val AUTHORITY = "e.coronawarn.app" + private const val PATH_PREFIX = "/c1" + private val BASE32_REGEX = "^([A-Z2-7=]{8})+$".toRegex(RegexOption.IGNORE_CASE) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt index 8e7e2452d58b283c699c89cc3864c3e517457636..c4a1059e0089f06bb81bd6d27684da0cd6da4728 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt @@ -2,5 +2,5 @@ package de.rki.coronawarnapp.eventregistration.checkins.qrcode interface QRCodeVerifier { - suspend fun verify(encodedEvent: String): QRCodeVerifyResult + suspend fun verify(rawTraceLocation: ByteArray): QRCodeVerifyResult } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidator.kt deleted file mode 100644 index 48af21b14167f849d326fdcd9e1ccc5060dfacdb..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package de.rki.coronawarnapp.eventregistration.checkins.qrcode - -import java.net.URI - -private const val SCHEME = "https" -private const val AUTHORITY = "e.coronawarn.app" -private const val PATH_PREFIX = "/c1" -private const val SIGNED_TRACE_LOCATION_BASE_32_REGEX = - "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?\$" - -/** - * Validate that QRCode scanned uri matches the following formulas: - * https://e.coronawarn.app/c1/SIGNED_TRACE_LOCATION_BASE32 - * HTTPS://E.CORONAWARN.APP/C1/SIGNED_TRACE_LOCATION_BASE32 - */ -fun String.isValidQRCodeUri(): Boolean = - URI.create(this).run { - scheme.equals(SCHEME, true) && - authority.equals(AUTHORITY, true) && - path.substringBeforeLast("/") - .equals(PATH_PREFIX, true) && - path.substringAfterLast("/") - .matches(Regex(SIGNED_TRACE_LOCATION_BASE_32_REGEX)) - } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt index a87ab34f421700514e26c8d5ce57a7357e9f973a..d3f7c17ba2dae360a155f691928aab50e5f52e97 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.ui.eventregistration import dagger.Module import dagger.android.ContributesAndroidInjector -import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment -import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsModule +import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment +import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsModule import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInFragment import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInModule import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsFragment.kt deleted file mode 100644 index 2ab0eb9fa9a49a81953fbe7624ecbfdad17917db..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin - -import android.net.Uri -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.View -import androidx.core.net.toUri -import androidx.navigation.fragment.FragmentNavigatorExtras -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import com.google.android.material.transition.Hold -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.FragmentCheckInsBinding -import de.rki.coronawarnapp.util.di.AutoInject -import de.rki.coronawarnapp.util.ui.doNavigate -import de.rki.coronawarnapp.util.ui.observe2 -import de.rki.coronawarnapp.util.ui.viewBindingLazy -import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider -import de.rki.coronawarnapp.util.viewmodel.cwaViewModels -import javax.inject.Inject - -class CheckInsFragment : Fragment(R.layout.fragment_check_ins), AutoInject { - - @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val viewModel: CheckInsViewModel by cwaViewModels { viewModelFactory } - private val binding: FragmentCheckInsBinding by viewBindingLazy() - - // Encoded uri is a one-time use data and then cleared - private val uri: String? - get() = navArgs<CheckInsFragmentArgs>().value - .uri - .also { arguments?.clear() } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - exitTransition = Hold() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - with(binding.scanCheckinQrcodeFab) { - setOnClickListener { - findNavController().navigate( - R.id.action_checkInsFragment_to_scanCheckInQrCodeFragment, - null, - null, - FragmentNavigatorExtras(this to transitionName) - ) - } - } - - uri?.let { viewModel.verifyUri(it) } - viewModel.verifyResult.observe2(this) { - doNavigate( - CheckInsFragmentDirections - .actionCheckInsFragmentToConfirmCheckInFragment(it.toVerifiedTraceLocation()) - ) - } - } - - companion object { - fun uri(rootUri: String): Uri = "coronawarnapp://check-ins/$rootUri".toUri() - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsViewModel.kt deleted file mode 100644 index c72f034e320f2f289cdbbbb1f9e4889145e91d85..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsViewModel.kt +++ /dev/null @@ -1,43 +0,0 @@ -package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier -import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult -import de.rki.coronawarnapp.eventregistration.checkins.qrcode.isValidQRCodeUri -import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.util.coroutine.DispatcherProvider -import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -import timber.log.Timber - -class CheckInsViewModel @AssistedInject constructor( - dispatcherProvider: DispatcherProvider, - private val qrCodeVerifier: QRCodeVerifier -) : CWAViewModel(dispatcherProvider) { - - private val verifyResultData = MutableLiveData<QRCodeVerifyResult>() - val verifyResult: LiveData<QRCodeVerifyResult> = verifyResultData - - fun verifyUri(uri: String) = launch { - try { - Timber.i("uri: $uri") - if (!uri.isValidQRCodeUri()) - throw IllegalArgumentException("Invalid uri: $uri") - - val encodedEvent = uri.substringAfterLast("/") - val verifyResult = qrCodeVerifier.verify(encodedEvent) - Timber.i("verifyResult: $verifyResult") - verifyResultData.postValue(verifyResult) - } catch (e: Exception) { - Timber.d(e, "TraceLocation verification failed") - e.report(ExceptionCategory.INTERNAL) - } - } - - @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<CheckInsViewModel> -} 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 new file mode 100644 index 0000000000000000000000000000000000000000..ccc7664277a19ee4eefb775365b2dbeb8f25e5bc --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt @@ -0,0 +1,98 @@ +package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins + +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.appcompat.widget.Toolbar +import androidx.core.net.toUri +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import com.google.android.material.transition.Hold +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsFragmentBinding +import de.rki.coronawarnapp.util.CWADebug +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.doNavigate +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted +import javax.inject.Inject + +class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_fragment), AutoInject { + + private val navArgs by navArgs<CheckInsFragmentArgs>() + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel: CheckInsViewModel by cwaViewModelsAssisted( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, savedState -> + factory as CheckInsViewModel.Factory + factory.create(savedState = savedState, deepLink = navArgs.uri) + } + ) + private val binding: TraceLocationAttendeeCheckinsFragmentBinding by viewBindingLazy() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + exitTransition = Hold() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupMenu(binding.toolbar) + + binding.scanCheckinQrcodeFab.apply { + setOnClickListener { + findNavController().navigate( + R.id.action_checkInsFragment_to_scanCheckInQrCodeFragment, + null, + null, + FragmentNavigatorExtras(this to transitionName) + ) + } + if (CWADebug.isDeviceForTestersBuild) { + setOnLongClickListener { + findNavController().navigate(createCheckInUri(DEBUG_CHECKINS.random())) + true + } + } + } + + viewModel.verifyResult.observe2(this) { + doNavigate( + CheckInsFragmentDirections + .actionCheckInsFragmentToConfirmCheckInFragment(it.toVerifiedTraceLocation()) + ) + } + } + + private fun setupMenu(toolbar: Toolbar) = toolbar.apply { + inflateMenu(R.menu.menu_trace_location_my_check_ins) + setOnMenuItemClickListener { + when (it.itemId) { + R.id.menu_information -> { + Toast.makeText(requireContext(), "Information // TODO", Toast.LENGTH_SHORT).show() + true + } + R.id.menu_remove_all -> { + Toast.makeText(requireContext(), "Remove all // TODO", Toast.LENGTH_SHORT).show() + true + } + else -> onOptionsItemSelected(it) + } + } + } + + companion object { + fun createCheckInUri(rootUri: String): Uri = "coronawarnapp://check-ins/$rootUri".toUri() + + @Suppress("MaxLineLength") + private val DEBUG_CHECKINS = listOf( + "https://e.coronawarn.app/c1/BJLAUJBTGA2TKMZTGFRS2MRTGA3C2NBTMYZS2OJXGQZC2NTEHBTGCYRVGRSTQNBYCAARQARCCFGXSICCNFZHI2DEMF4SAUDBOJ2HSKQLMF2CA3LZEBYGYYLDMUYNHB5EAE4PPB5EAFAAAESIGBDAEIIARVENF6QT6XZATJ5GSDHL77BCAGR6QKDEUJRP2RDCTKTS7QECWMFAEIIA47MT2EA7MQKGNQU2XCY3Y2ZOZXCILDPC65PBUO4JJHT5LQQWDQSA" + ) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsModule.kt similarity index 99% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsModule.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsModule.kt index d4b68e13ea370f9e4ddfbee26a9deab8b45b392f..3777ed0daf59ed0c16f474f0843692827e4fbf19 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsModule.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin +package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins import dagger.Binds import dagger.Module 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 new file mode 100644 index 0000000000000000000000000000000000000000..c14b531ffd10b38e67c777536b7bf6681e20792a --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt @@ -0,0 +1,68 @@ +package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.SavedStateHandle +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeUriParser +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import timber.log.Timber + +class CheckInsViewModel @AssistedInject constructor( + @Assisted private val savedState: SavedStateHandle, + @Assisted private val deepLink: String?, + dispatcherProvider: DispatcherProvider, + private val qrCodeVerifier: QRCodeVerifier, + private val qrCodeUriParser: QRCodeUriParser +) : CWAViewModel(dispatcherProvider) { + + private val verifyResultData = MutableLiveData<QRCodeVerifyResult>() + val verifyResult: LiveData<QRCodeVerifyResult> = verifyResultData + + init { + deepLink?.let { + if (deepLink != savedState.get(SKEY_LAST_DEEPLINK)) { + Timber.i("New deeplink: %s", deepLink) + verifyUri(it) + } else { + Timber.d("Already consumed this deeplink: %s", deepLink) + } + } + savedState.set(SKEY_LAST_DEEPLINK, deepLink) + } + + private fun verifyUri(uri: String) = launch { + try { + Timber.i("uri: $uri") + val signedTraceLocation = qrCodeUriParser.getSignedTraceLocation(uri) + ?: throw IllegalArgumentException("Invalid uri: $uri") + + val verifyResult = qrCodeVerifier.verify(signedTraceLocation.toByteArray()) + Timber.i("verifyResult: $verifyResult") + verifyResultData.postValue(verifyResult) + } catch (e: Exception) { + Timber.d(e, "TraceLocation verification failed") + e.report(ExceptionCategory.INTERNAL) + } + } + + companion object { + private const val SKEY_LAST_DEEPLINK = "deeplink.last" + } + + @AssistedFactory + interface Factory : CWAViewModelFactory<CheckInsViewModel> { + fun create( + savedState: SavedStateHandle, + deepLink: String? + ): CheckInsViewModel + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/VerifiedTraceLocation.kt similarity index 99% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocation.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/VerifiedTraceLocation.kt index b8c61317dba996501964090f76d8caa5774aeb2d..e0621dc572dcebfda0a0733e1f53e4e7f6680a98 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/VerifiedTraceLocation.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin +package de.rki.coronawarnapp.ui.eventregistration.attendee.checkins import android.os.Parcelable import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt index c8d757c662b984b9c7eaf5636b934071740f2111..8e1ee327a9277d53b804aa87199b51fb150d9484 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt @@ -13,7 +13,7 @@ import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.DefaultDecoderFactory import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentScanCheckInQrCodeBinding -import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment +import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment import de.rki.coronawarnapp.util.CameraPermissionHelper import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject @@ -61,7 +61,7 @@ class ScanCheckInQrCodeFragment : is ScanCheckInQrCodeNavigation.ScanResultNavigation -> { Timber.i(navEvent.uri) findNavController().navigate( - CheckInsFragment.uri(navEvent.uri), + CheckInsFragment.createCheckInUri(navEvent.uri), NavOptions.Builder() .setPopUpTo(R.id.checkInsFragment, true) .build() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index 12ba28358a75be28d1cfa0d45252d25ffa996e32..adaf9fb30b63aa91507a36c0a8899ebc0c45d5b1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -24,7 +24,7 @@ import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsS import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.ui.base.startActivitySafely -import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment +import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment import de.rki.coronawarnapp.ui.setupWithNavController2 import de.rki.coronawarnapp.util.AppShortcuts import de.rki.coronawarnapp.util.CWADebug @@ -157,7 +157,7 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { private fun navigateByIntentUri(intent: Intent?) { val uri = intent?.data ?: return Timber.i("Uri:$uri") - navController.navigate(CheckInsFragment.uri(uri.toString())) + navController.navigate(CheckInsFragment.createCheckInUri(uri.toString())) } /** diff --git a/Corona-Warn-App/src/main/res/drawable-night/trace_location_my_check_ins_empty_illustration.xml b/Corona-Warn-App/src/main/res/drawable-night/trace_location_my_check_ins_empty_illustration.xml new file mode 100644 index 0000000000000000000000000000000000000000..aafb8ab4bbb76e3567e4c46274fb822668f60e51 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable-night/trace_location_my_check_ins_empty_illustration.xml @@ -0,0 +1,91 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="200dp" + android:height="200dp" + android:viewportWidth="200" + android:viewportHeight="200"> + <group> + <clip-path android:pathData="M100,100m-100,0a100,100 0,1 1,200 0a100,100 0,1 1,-200 0" /> + <path + android:pathData="M-9.091,-6.818h224.242v257.576h-224.242z" + android:fillColor="#333337" /> + <path + android:pathData="M-92.752,138.323C-141.112,138.323 -162.18,109.327 -207.332,114.816C-218.065,116.121 -231.693,120.198 -247,131.631V204.198L258,206V142.28C255.197,140.676 252.401,139.305 249.756,138.302C249.756,138.302 198.868,118.625 142.512,135.462C94.242,149.897 38.639,112.115 12.001,111.135C9.514,111.043 7.113,111 4.807,111C-52.745,110.996 -55.497,138.323 -92.752,138.323Z" + android:fillColor="#2D2D2F" + android:fillType="evenOdd" /> + <path + android:pathData="M48,70.667H74.667V44H48V70.667ZM54.667,50.667H68V64H54.667V50.667Z" + android:fillColor="#747576" /> + <path + android:pathData="M48,103.999H74.667V77.332H48V103.999ZM54.667,83.999H68V97.332H54.667V83.999Z" + android:fillColor="#747576" /> + <path + android:pathData="M81.333,44V70.667H108V44H81.333ZM101.333,64H88V50.667H101.333V64Z" + android:fillColor="#747576" /> + <path + android:pathData="M108,97.332H101.333V103.999H108V97.332Z" + android:fillColor="#747576" /> + <path + android:pathData="M88,77.332H81.333V83.999H88V77.332Z" + android:fillColor="#747576" /> + <path + android:pathData="M94.667,84H88V90.667H94.667V84Z" + android:fillColor="#747576" /> + <path + android:pathData="M88,90.668H81.333V97.335H88V90.668Z" + android:fillColor="#747576" /> + <path + android:pathData="M94.667,97.332H88V103.999H94.667V97.332Z" + android:fillColor="#747576" /> + <path + android:pathData="M101.334,90.668H94.667V97.335H101.334V90.668Z" + android:fillColor="#747576" /> + <path + android:pathData="M101.334,77.332H94.667V83.999H101.334V77.332Z" + android:fillColor="#747576" /> + <path + android:pathData="M108,84H101.333V90.667H108V84Z" + android:fillColor="#747576" /> + <path + android:pathData="M93.84,101.923V113.694H109.61V102.234L109.76,84.723H97.48L93.84,101.923Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M108.82,102.094V113.694H121.67V102.234L121.83,84.723H109.25L108.82,102.094Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M121,84.723L121.13,102.234V113.694H135.56V101.923L132.23,84.723H121Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M148,76.822L81.208,76.822L81.208,85.001H148V76.822Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M148,113H81V150H148V113Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M99.5,84H81.5L77,102V114H95V102L99.5,84Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M93,112H79V102L83,86H97L93,102V112Z" + android:fillColor="#1E1E1F" /> + <path + android:pathData="M130,84H148L152.5,102V114H134.5V102L130,84Z" + android:fillColor="#3F3F43" /> + <path + android:pathData="M136.5,112H150.5V102L146.5,86H132.5L136.5,102V112Z" + android:fillColor="#1E1E1F" /> + <path + android:pathData="M107,112H95V102L99,86H107V102V112Z" + android:fillColor="#2D2D2F" /> + <path + android:pathData="M120.5,112H109V101.793V86H120.5V94V102V112Z" + android:fillColor="#1E1E1F" /> + <path + android:pathData="M93,150V122H114V150" + android:fillColor="#333337" /> + <path + android:pathData="M116,150V122H137V150" + android:fillColor="#333337" /> + <path + android:pathData="M122.5,112H134.5V102L130.5,86H122.5V102V112Z" + android:fillColor="#2D2D2F" /> + </group> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_qrcode_checkins_fab.xml b/Corona-Warn-App/src/main/res/drawable/ic_qrcode_checkins_fab.xml new file mode 100644 index 0000000000000000000000000000000000000000..387c3a6e544cc3e384807656bbf2a2512eb22a76 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_qrcode_checkins_fab.xml @@ -0,0 +1,43 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <group + android:translateX="4" + android:translateY="2"> + <path + android:fillColor="#ffffff" + android:pathData="M0.001,8.8892H8.8902V0H0.001V8.8892ZM2.2233,2.2223H6.6679V6.6669H2.2233V2.2223Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M0,19.9986H8.8892V11.1094H0V19.9986ZM2.2223,13.3317H6.6669V17.7763H2.2223V13.3317Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M11.1113,0V8.8892H20.0006V0H11.1113ZM17.7783,6.6669H13.3336V2.2223H17.7783V6.6669Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M20.0006,17.7773H17.7783V19.9997H20.0006V17.7773Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M13.3336,11.1094H11.1113V13.3317H13.3336V11.1094Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M15.5553,13.332H13.333V15.5543H15.5553V13.332Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M13.3336,15.5547H11.1113V17.777H13.3336V15.5547Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M15.5553,17.7773H13.333V19.9997H15.5553V17.7773Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M17.779,15.5547H15.5566V17.777H17.779V15.5547Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M17.779,11.1094H15.5566V13.3317H17.779V11.1094Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M20.0006,13.332H17.7783V15.5543H20.0006V13.332Z" /> + </group> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/trace_location_my_check_ins_empty_illustration.xml b/Corona-Warn-App/src/main/res/drawable/trace_location_my_check_ins_empty_illustration.xml new file mode 100644 index 0000000000000000000000000000000000000000..54d3ea0c7a0b7a6b60063770f44c742bf468af2f --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/trace_location_my_check_ins_empty_illustration.xml @@ -0,0 +1,91 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="200dp" + android:height="200dp" + android:viewportWidth="200" + android:viewportHeight="200"> + <group> + <clip-path android:pathData="M100,100m-100,0a100,100 0,1 1,200 0a100,100 0,1 1,-200 0" /> + <path + android:fillColor="#E8F5FF" + android:pathData="M-9.091,-6.818h224.242v257.576h-224.242z" /> + <path + android:fillColor="#D8ECF9" + android:fillType="evenOdd" + android:pathData="M-92.752,138.323C-141.112,138.323 -162.18,109.327 -207.332,114.816C-218.065,116.121 -231.693,120.198 -247,131.631V204.198L258,206V142.28C255.197,140.676 252.401,139.305 249.756,138.302C249.756,138.302 198.868,118.625 142.512,135.462C94.242,149.897 38.639,112.115 12.001,111.135C9.514,111.043 7.113,111 4.807,111C-52.745,110.996 -55.497,138.323 -92.752,138.323Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M48,70.667H74.667V44H48V70.667ZM54.667,50.667H68V64H54.667V50.667Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M48,103.999H74.667V77.332H48V103.999ZM54.667,83.999H68V97.332H54.667V83.999Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M81.333,44V70.667H108V44H81.333ZM101.333,64H88V50.667H101.333V64Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M108,97.332H101.333V103.999H108V97.332Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M88,77.332H81.333V83.999H88V77.332Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M94.667,84H88V90.667H94.667V84Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M88,90.668H81.333V97.335H88V90.668Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M94.667,97.332H88V103.999H94.667V97.332Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M101.334,90.668H94.667V97.335H101.334V90.668Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M101.334,77.332H94.667V83.999H101.334V77.332Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M108,84H101.333V90.667H108V84Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M93.84,101.923V113.694H109.61V102.234L109.76,84.723H97.48L93.84,101.923Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M108.82,102.094V113.694H121.67V102.234L121.83,84.723H109.25L108.82,102.094Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M121,84.723L121.13,102.234V113.694H135.56V101.923L132.23,84.723H121Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M148,76.822L81.208,76.822L81.208,85.001H148V76.822Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M148,113H81V150H148V113Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M99.5,84H81.5L77,102V114H95V102L99.5,84Z" /> + <path + android:fillColor="#B8E0FA" + android:pathData="M93,112H79V102L83,86H97L93,102V112Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M130,84H148L152.5,102V114H134.5V102L130,84Z" /> + <path + android:fillColor="#B8E0FA" + android:pathData="M136.5,112H150.5V102L146.5,86H132.5L136.5,102V112Z" /> + <path + android:fillColor="#E8F5FF" + android:pathData="M107,112H95V102L99,86H107V102V112Z" /> + <path + android:fillColor="#B8E0FA" + android:pathData="M120.5,112H109V101.793V86H120.5V94V102V112Z" /> + <path + android:fillColor="#E8F5FF" + android:pathData="M93,150V122H114V150" /> + <path + android:fillColor="#E8F5FF" + android:pathData="M116,150V122H137V150" /> + <path + android:fillColor="#E8F5FF" + android:pathData="M122.5,112H134.5V102L130.5,86H122.5V102V112Z" /> + </group> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_check_ins.xml b/Corona-Warn-App/src/main/res/layout/fragment_check_ins.xml deleted file mode 100644 index 1053da359f440f76e1861823d2946dea460a8370..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/res/layout/fragment_check_ins.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/content_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <com.google.android.material.appbar.MaterialToolbar - android:id="@+id/toolbar" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/check_ins_recycler" - android:layout_width="match_parent" - android:layout_height="match_parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" /> - - <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton - android:id="@+id/scan_checkin_qrcode_fab" - style="@style/Widget.App.ExtendedFloatingActionButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_normal" - android:text="@string/scan_check_in_qr_code" - android:transitionName="shared_element_container" - app:icon="@drawable/ic_nav_check_in" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml new file mode 100644 index 0000000000000000000000000000000000000000..e597f214780d7fe0d6e7e90039495d543cbf6eb9 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/content_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + style="@style/CWAToolbar" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:title="@string/trace_location_checkins_title" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/check_ins_list" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="48dp" + android:gravity="center" + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/trace_location_checkins_empty_illustration_accessibility" + android:src="@drawable/trace_location_my_check_ins_empty_illustration" /> + + <TextView + style="@style/headline6" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="36dp" + android:layout_gravity="center" + android:text="@string/trace_location_checkins_empty_label" /> + <TextView + style="@style/subtitleMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:gravity="center" + android:text="@string/trace_location_checkins_empty_description" /> + </LinearLayout> + + <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + android:id="@+id/scan_checkin_qrcode_fab" + style="@style/Widget.App.ExtendedFloatingActionButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_normal" + android:text="@string/trace_location_checkins_action_scan_qrcode" + android:transitionName="shared_element_container" + app:icon="@drawable/ic_qrcode_checkins_fab" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/menu/menu_trace_location_my_check_ins.xml b/Corona-Warn-App/src/main/res/menu/menu_trace_location_my_check_ins.xml new file mode 100644 index 0000000000000000000000000000000000000000..b624e4f56508d03b09ac833c03361bc050b06c74 --- /dev/null +++ b/Corona-Warn-App/src/main/res/menu/menu_trace_location_my_check_ins.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/menu_information" + android:title="@string/trace_location_checkins_menu_information" /> + <item + android:id="@+id/menu_remove_all" + android:title="@string/trace_location_checkins_menu_remove_all" /> +</menu> diff --git a/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml index 062c8064c0103be7b5f6b29c49ba31bcd12702f3..05270f678f14ca9dfb0f59ffc55e5cbce2141dcc 100644 --- a/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml @@ -11,7 +11,7 @@ tools:layout="@layout/fragment_confirm_check_in"> <argument android:name="traceLocation" - app:argType="de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.VerifiedTraceLocation" /> + app:argType="de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.VerifiedTraceLocation" /> </fragment> <fragment android:id="@+id/scanCheckInQrCodeFragment" @@ -21,9 +21,9 @@ <fragment android:id="@+id/checkInsFragment" - android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment" + android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment" android:label="CheckInsFragment" - tools:layout="@layout/fragment_check_ins"> + tools:layout="@layout/trace_location_attendee_checkins_fragment"> <deepLink app:uri="coronawarnapp://check-ins/{uri}" /> <action android:id="@+id/action_checkInsFragment_to_scanCheckInQrCodeFragment" 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 33739d62a4c0e4aa463e1ce545ca28c8868d0c98..15ac22e978165febddb8090d2be80649e19c0e66 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 @@ -3,4 +3,35 @@ <!-- #################################### Event Registration ###################################### --> + + <!-- XHED: My check-ins: screen title --> + <string name="trace_location_checkins_title">Meine Check-ins</string> + <!-- XHED: My check-ins: Title for list explanation when there are no check-ins --> + <string name="trace_location_checkins_empty_label">Noch keine Einträge vorhanden</string> + <!-- XTXT: My check-ins: Description for list explanation when there are no check-ins --> + <string name="trace_location_checkins_empty_description">Wenn Sie einen QR-Code scannen, um sich für ein Event oder einen Ort einzuchecken, wird automatisch ein Eintrag angelegt.</string> + <!-- XTXT: My check-ins: Accessibility description for empty check-ins illustration --> + <string name="trace_location_checkins_empty_illustration_accessibility">Gebäude mit QR-Code-Symbol im Hintergrund.</string> + + <!-- XBUT: My check-ins: Scan QR Code button --> + <string name="trace_location_checkins_action_scan_qrcode">QR-Code scannen</string> + + <!-- XBUT: My check-ins: menu entry to remove all --> + <string name="trace_location_checkins_menu_remove_all">Alle entfernen</string> + <!-- XBUT: My check-ins: menu entry to show information --> + <string name="trace_location_checkins_menu_information">Informationen</string> + + <!-- XHED: My checkin-ins: card title if camera permissions were permanently declined. --> + <string name="trace_location_checkins_camera_permission_title">Zugriff auf Kamera erlauben</string> + <!-- XTXT: My checkin-ins: card description if camera permissions were permanently declined. --> + <string name="trace_location_checkins_camera_permission_description">Um die Kamera für das Scannen des QR-Codes zu nutzen, müssen Sie die Verwendung der Kamera in den Geräte-Einstellungen zur App zulassen.</string> + <!-- XBUT: My checkin-ins: card description if camera permissions were permanently declined. --> + <string name="trace_location_checkins_camera_permission_action">Einstellungen
 öffnen</string> + + <!-- XHED: My checkin-ins: Confirmation dialog title when removing all items --> + <string name="trace_location_checkins_remove_all_title">Wollen Sie wirklich alle Einträge entfernen?</string> + <!-- XHED: My checkin-ins: Confirmation dialog title when removing a single item --> + <string name="trace_location_checkins_remove_single_title">Wollen Sie diesen Eintrag wirklich entfernen?</string> + <!-- XTXT: My checkin-ins: Confirmation dialog description when removing all/single items --> + <string name="trace_location_checkins_remove_message">Sie können dann Personen, die zur selben Zeit für diesen Ort oder Event eingecheckt waren, bei Bedarf nicht mehr warnen oder von ihnen gewarnt werden.</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml index 2285da38b5227a2efaafe21ad36783185e3e2148..1e6f63eff10dff1c32d440472b8e229fe4752d21 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -1925,4 +1925,12 @@ <!-- Scan check in QR Code--> <!-- XTXT: Scan check in QR-Code FAB text--> <string name="scan_check_in_qr_code">QR-Code scannen</string> + + <!-- #################################### + Generic + ###################################### --> + <!-- XACT: Button/Dialog label to cancel something--> + <string name="generic_action_abort">Abbrechen</string> + <!-- XACT: Button/Dialog label to remove something--> + <string name="generic_action_remove">Entfernen</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 33739d62a4c0e4aa463e1ce545ca28c8868d0c98..15ac22e978165febddb8090d2be80649e19c0e66 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 @@ -3,4 +3,35 @@ <!-- #################################### Event Registration ###################################### --> + + <!-- XHED: My check-ins: screen title --> + <string name="trace_location_checkins_title">Meine Check-ins</string> + <!-- XHED: My check-ins: Title for list explanation when there are no check-ins --> + <string name="trace_location_checkins_empty_label">Noch keine Einträge vorhanden</string> + <!-- XTXT: My check-ins: Description for list explanation when there are no check-ins --> + <string name="trace_location_checkins_empty_description">Wenn Sie einen QR-Code scannen, um sich für ein Event oder einen Ort einzuchecken, wird automatisch ein Eintrag angelegt.</string> + <!-- XTXT: My check-ins: Accessibility description for empty check-ins illustration --> + <string name="trace_location_checkins_empty_illustration_accessibility">Gebäude mit QR-Code-Symbol im Hintergrund.</string> + + <!-- XBUT: My check-ins: Scan QR Code button --> + <string name="trace_location_checkins_action_scan_qrcode">QR-Code scannen</string> + + <!-- XBUT: My check-ins: menu entry to remove all --> + <string name="trace_location_checkins_menu_remove_all">Alle entfernen</string> + <!-- XBUT: My check-ins: menu entry to show information --> + <string name="trace_location_checkins_menu_information">Informationen</string> + + <!-- XHED: My checkin-ins: card title if camera permissions were permanently declined. --> + <string name="trace_location_checkins_camera_permission_title">Zugriff auf Kamera erlauben</string> + <!-- XTXT: My checkin-ins: card description if camera permissions were permanently declined. --> + <string name="trace_location_checkins_camera_permission_description">Um die Kamera für das Scannen des QR-Codes zu nutzen, müssen Sie die Verwendung der Kamera in den Geräte-Einstellungen zur App zulassen.</string> + <!-- XBUT: My checkin-ins: card description if camera permissions were permanently declined. --> + <string name="trace_location_checkins_camera_permission_action">Einstellungen
 öffnen</string> + + <!-- XHED: My checkin-ins: Confirmation dialog title when removing all items --> + <string name="trace_location_checkins_remove_all_title">Wollen Sie wirklich alle Einträge entfernen?</string> + <!-- XHED: My checkin-ins: Confirmation dialog title when removing a single item --> + <string name="trace_location_checkins_remove_single_title">Wollen Sie diesen Eintrag wirklich entfernen?</string> + <!-- XTXT: My checkin-ins: Confirmation dialog description when removing all/single items --> + <string name="trace_location_checkins_remove_message">Sie können dann Personen, die zur selben Zeit für diesen Ort oder Event eingecheckt waren, bei Bedarf nicht mehr warnen oder von ihnen gewarnt werden.</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 75d293fde8e1638f48c051b11733c1cbbc02263a..4257ddb1e225a23cf55ab960b7c0b8291e511369 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -312,7 +312,7 @@ <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active in times during which you encountered other people, your risk of infection can be calculated for this period."</string> - <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> + <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment">"Exposure logging covers the past 14 days. During this time, the logging feature on your smartphone was active for %1$s days. The app automatically deletes older logs, as these are no longer relevant for infection prevention."</string> <!-- XTXT: risk details - infection period days logged/14 --> <string name="risk_details_information_active_tracing_days_circle_progress">"%s/14"</string> @@ -1934,4 +1934,12 @@ <!-- Scan check in QR Code--> <!-- XTXT: Scan check in QR-Code FAB text--> <string name="scan_check_in_qr_code">Scan QR-Code</string> + + <!-- #################################### + Generic + ###################################### --> + <!-- XACT: Button/Dialog label to cancel something--> + <string name="generic_action_abort">Cancel</string> + <!-- XACT: Button/Dialog label to remove something--> + <string name="generic_action_remove">Remove</string> </resources> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest2.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest2.kt new file mode 100644 index 0000000000000000000000000000000000000000..62e20f4784d817a7796079269db356fd5a7b57c6 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest2.kt @@ -0,0 +1,76 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import com.google.protobuf.ByteString +import de.rki.coronawarnapp.eventregistration.common.decodeBase32 +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import io.kotest.matchers.shouldBe +import okio.ByteString.Companion.toByteString +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +@Disabled("Renable after encoding clarification") +class DefaultQRCodeVerifierTest2 : BaseTest() { + + @Test + fun `protobuf decoding 1`() { + val signedTraceLocation = + TraceLocationOuterClass.SignedTraceLocation.newBuilder(TraceLocationOuterClass.SignedTraceLocation.getDefaultInstance()) + .apply { + signature = ByteString.copyFromUtf8( + "MEYCIQCNSNL6E/XyCaemkM6//CIBo+goZKJi/URimqcvwIKzCgIhAOfZPRAfZBRmwpq4sbxrLs3EhY3i914aO4lJ59XCFhwk" + ) + location = TraceLocationOuterClass.TraceLocation.parseFrom( + "BISDGMBVGUZTGMLDFUZDGMBWFU2DGZRTFU4TONBSFU3GIODGMFRDKNDFHA2DQEABDABCEEKNPEQEE2LSORUGIYLZEBIGC4TUPEVAWYLUEBWXSIDQNRQWGZJQ2OD2IAJY66D2IAKAAA".decodeBase32() + .toByteArray() + ) + }.build() + + signedTraceLocation.apply { + location.apply { + guid shouldBe "3055331c-2306-43f3-9742-6d8fab54e848" + version shouldBe 1 + typeValue shouldBe 2 + description shouldBe "My Birthday Party" + address shouldBe "at my place" + startTimestamp shouldBe 2687955 + endTimestamp shouldBe 2687991 + defaultCheckInLengthInMinutes shouldBe 0 + } + signature.toStringUtf8() shouldBe "MEYCIQCNSNL6E/XyCaemkM6//CIBo+goZKJi/URimqcvwIKzCgIhAOfZPRAfZBRmwpq4sbxrLs3EhY3i914aO4lJ59XCFhwk" + } + + signedTraceLocation.location.toByteArray().toByteString() + .base64() shouldBe "CiQzMDU1MzMxYy0yMzA2LTQzZjMtOTc0Mi02ZDhmYWI1NGU4NDgQARgCIhFNeSBCaXJ0aGRheSBQYXJ0eSoLYXQgbXkgcGxhY2Uw04ekATj3h6QBQAA=" + } + + @Test + fun `protobuf decoding 2`() { + val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.newBuilder().apply { + signature = ByteString.copyFromUtf8( + "MEUCIFpHvUqYAIP0Mq86R7kNO4EgRSvGJHbOlDraauKZvkgbAiEAh93bBDYviEtym4q5Oqzd7j6Dp1MLCP7YwCKlVcU2DHc=" + ) + location = TraceLocationOuterClass.TraceLocation.parseFrom( + "BISGMY3BHA2GEMZXFU3DCYZQFU2GCN3DFVRDEZRYFU4DENLDMFSGINJQGZRWMEABDAASEDKJMNSWG4TFMFWSAU3IN5YCUDKNMFUW4ICTORZGKZLUEAYTAABYABAAU".decodeBase32() + .toByteArray() + ) + }.build() + + signedTraceLocation.apply { + location.apply { + guid shouldBe "fca84b37-61c0-4a7c-b2f8-825cadd506cf" + version shouldBe 1 + typeValue shouldBe 1 + description shouldBe "Icecream Shop" + address shouldBe "Main Street 1" + startTimestamp shouldBe 0 + endTimestamp shouldBe 0 + defaultCheckInLengthInMinutes shouldBe 10 + } + signature.toStringUtf8() shouldBe "MEUCIFpHvUqYAIP0Mq86R7kNO4EgRSvGJHbOlDraauKZvkgbAiEAh93bBDYviEtym4q5Oqzd7j6Dp1MLCP7YwCKlVcU2DHc=" + } + + signedTraceLocation.location.toByteArray().toByteString() + .base64() shouldBe "CiRmY2E4NGIzNy02MWMwLTRhN2MtYjJmOC04MjVjYWRkNTA2Y2YQARgBIg1JY2VjcmVhbSBTaG9wKg1NYWluIFN0cmVldCAxMAA4AEAK" + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt index 62e33419136ee48800c5d1ecd84315d289b4ed36..d7e3d30e51b86fa877323ef4f79746009624d4f9 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt @@ -22,7 +22,7 @@ class InvalidUrlProvider : ArgumentsProvider { "7SHUBCA7UEZBDDQ2R6VRJH7WBJKVF7GZYJA6YMRN27IPEP7NKGGJSWX3XQ" ), Arguments.of( - "HTTPS://E.CORONAWARN.APP/C1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" + + "HTTP://E.CORONAWARN.APP/C1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" + "J2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2" + "SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI" ), diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeUriParserTest.kt similarity index 56% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidatorTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeUriParserTest.kt index d0f30a69b74d3aba0aa7db9aae6fc6ebdb95de49..27a0d286f206191f06d00681e03a92c4ee06982d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidatorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeUriParserTest.kt @@ -1,29 +1,24 @@ package de.rki.coronawarnapp.eventregistration.checkins.qrcode -import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe -import org.junit.Test +import io.kotest.matchers.shouldNotBe import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ArgumentsSource +import testhelpers.BaseTest -class UriValidatorTest { +class QRCodeUriParserTest : BaseTest() { + + fun createInstance() = QRCodeUriParser() @ParameterizedTest @ArgumentsSource(ValidUrlProvider::class) fun `Valid URLs`(input: String) { - input.isValidQRCodeUri() shouldBe true + createInstance().getSignedTraceLocation(input) shouldNotBe null } @ParameterizedTest @ArgumentsSource(InvalidUrlProvider::class) fun `Invalid URLs`(input: String) { - input.isValidQRCodeUri() shouldBe false - } - - @Test - fun `Invalid URL string`() { - shouldThrow<IllegalArgumentException> { - "Hello World!".isValidQRCodeUri() - } + createInstance().getSignedTraceLocation(input) shouldBe null } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt index 6b8b83fbe79725ba9382671da81673ba7dd729c6..f7e0bf42fae6a0c6d362ad86c1ffdb14bf97fc9f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt @@ -17,7 +17,22 @@ class ValidUrlProvider : ArgumentsProvider { "https://e.coronawarn.app/c1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" + "J2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2" + "SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI=" - ) + ), + Arguments.of( + "https://e.coronawarn.app/c1/BJLAUJBTGA2TKMZTGFRS2MRTGA3C2NBTMYZS2OJXGQZC2NTEHBTGCYRVG" + + "RSTQNBYCAARQARCCFGXSICCNFZHI2DEMF4SAUDBOJ2HSKQLMF2CA3LZEBYGYYLDMUYNHB5EAE4PPB5EAF" + + "AAAESIGBDAEIIARVENF6QT6XZATJ5GSDHL77BCAGR6QKDEUJRP2RDCTKTS7QECWMFAEIIA47MT2EA7MQK" + + "GNQU2XCY3Y2ZOZXCILDPC65PBUO4JJHT5LQQWDQSA" + ), + Arguments.of( + "https://e.coronawarn.app/c1/BJHAUJDGMNQTQNDCGM3S2NRRMMYC2NDBG5RS2YRSMY4C2OBSGVRWCZDEG" + + "UYDMY3GCAARQAJCBVEWGZLDOJSWC3JAKNUG64BKBVGWC2LOEBJXI4TFMV2CAMJQAA4AAQAKCJDTARICEB" + + "NEPPKKTAAIH5BSV45EPOINHOASARJLYYSHNTUUHLNGVYUZXZEBWARBACD53WYEGYXYQS3STOFLSOVM3XX" + + "D5A5HKMFQR7WYYARKKVOFGYGHO" + ), + Arguments.of("https://e.coronawarn.app/c1/JBSWY3DPEBLW64TMMQQQ===="), + Arguments.of("https://e.coronawarn.app/c1/JBSWY3DPEBLW64TMMQQQ"), + Arguments.of("https://e.coronawarn.app/c1/JBSWY3DPEBLW64TMMQQQ========"), ) } }