Skip to content
Snippets Groups Projects
Unverified Commit 0ab2b010 authored by Rituraj Sambherao's avatar Rituraj Sambherao Committed by GitHub
Browse files

QR Code stricter validation (EXPOSUREAPP-2570) (#1127)


* test for OnboardingFragment

* QR code stricter validations

* regex pattern improvement

* removed Obsolete test

* lint changes to fix pipeline issue

* logic refinement and test coverage improvement

* pipeline related fixes

* piepline related fixes : unnecessary test removed

* pipeline fixes and increased tests coverage

* QR code validation logic improvement

* detekt pipeline issue resolved

* pipeline issue Fix

* logic ovehaul and improvement for QRCode scans / validations

1, Data object for ScanResults
2. new Test for ScanResults
3. logic simplified

* removing unused imports from tests

* Small refactoring together with Rituraj.

* Adjust detekt, early abort via return is GOOD!

Co-authored-by: default avatarMatthias Urhahn <darken@darken.eu>
Co-authored-by: default avatarMatthias Urhahn <matthias.urhahn@sap.com>
parent 86f793a1
No related branches found
No related tags found
No related merge requests found
Showing
with 102 additions and 47 deletions
...@@ -572,7 +572,7 @@ style: ...@@ -572,7 +572,7 @@ style:
excludedFunctions: 'equals' excludedFunctions: 'equals'
excludeLabeled: false excludeLabeled: false
excludeReturnFromLambda: true excludeReturnFromLambda: true
excludeGuardClauses: false excludeGuardClauses: true
SafeCast: SafeCast:
active: true active: true
SerialVersionUIDInSerializableClass: SerialVersionUIDInSerializableClass:
......
package de.rki.coronawarnapp.service.submission
import java.util.regex.Pattern
data class QRScanResult(val rawResult: String) {
val isValid: Boolean
get() = guid != null
val guid: String? by lazy { extractGUID(rawResult) }
private fun extractGUID(rawResult: String): String? {
if (rawResult.length > MAX_QR_CODE_LENGTH) return null
if (rawResult.count { it == GUID_SEPARATOR } != 1) return null
if (!QR_CODE_REGEX.toRegex().matches(rawResult)) return null
val potentialGUID = rawResult.substringAfterLast(GUID_SEPARATOR, "")
if (potentialGUID.isBlank() || potentialGUID.length > MAX_GUID_LENGTH) return null
return potentialGUID
}
companion object {
// regex pattern for scanned QR code URL
val QR_CODE_REGEX: Pattern = Pattern.compile(
"^((^https:\\/{2}localhost)(\\/\\?)[A-Fa-f0-9]{6}" +
"[-][A-Fa-f0-9]{8}[-][A-Fa-f0-9]{4}[-][A-Fa-f0-9]{4}[-][A-Fa-f0-9]{4}[-][A-Fa-f0-9]{12})\$"
)
const val GUID_SEPARATOR = '?'
const val MAX_QR_CODE_LENGTH = 150
const val MAX_GUID_LENGTH = 80
}
}
...@@ -14,10 +14,6 @@ object SubmissionConstants { ...@@ -14,10 +14,6 @@ object SubmissionConstants {
val TEST_RESULT_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TEST_RESULT" val TEST_RESULT_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TEST_RESULT"
val TAN_REQUEST_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TAN" val TAN_REQUEST_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TAN"
const val MAX_QR_CODE_LENGTH = 150
const val MAX_GUID_LENGTH = 80
const val GUID_SEPARATOR = '?'
const val SERVER_ERROR_CODE_400 = 400 const val SERVER_ERROR_CODE_400 = 400
const val EMPTY_HEADER = "" const val EMPTY_HEADER = ""
......
...@@ -65,19 +65,10 @@ object SubmissionService { ...@@ -65,19 +65,10 @@ object SubmissionService {
} }
fun containsValidGUID(scanResult: String): Boolean { fun containsValidGUID(scanResult: String): Boolean {
if (scanResult.length > SubmissionConstants.MAX_QR_CODE_LENGTH || val scanResult = QRScanResult(scanResult)
scanResult.count { it == SubmissionConstants.GUID_SEPARATOR } != 1 return scanResult.isValid
)
return false
val potentialGUID = extractGUID(scanResult)
return !(potentialGUID.isEmpty() || potentialGUID.length > SubmissionConstants.MAX_GUID_LENGTH)
} }
fun extractGUID(scanResult: String): String =
scanResult.substringAfterLast(SubmissionConstants.GUID_SEPARATOR, "")
fun storeTestGUID(guid: String) = LocalData.testGUID(guid) fun storeTestGUID(guid: String) = LocalData.testGUID(guid)
fun deleteTestGUID() { fun deleteTestGUID() {
......
...@@ -9,6 +9,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory ...@@ -9,6 +9,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory
import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.TransactionException
import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.http.CwaWebException
import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.exception.reporting.report
import de.rki.coronawarnapp.service.submission.QRScanResult
import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.service.submission.SubmissionService
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.SubmissionRepository
...@@ -98,10 +99,10 @@ class SubmissionViewModel : ViewModel() { ...@@ -98,10 +99,10 @@ class SubmissionViewModel : ViewModel() {
_uiStateError _uiStateError
) )
fun validateAndStoreTestGUID(scanResult: String) { fun validateAndStoreTestGUID(rawResult: String) {
if (SubmissionService.containsValidGUID(scanResult)) { val scanResult = QRScanResult(rawResult)
val guid = SubmissionService.extractGUID(scanResult) if (scanResult.isValid) {
SubmissionService.storeTestGUID(guid) SubmissionService.storeTestGUID(scanResult.guid!!)
_scanStatus.value = Event(ScanStatus.SUCCESS) _scanStatus.value = Event(ScanStatus.SUCCESS)
} else { } else {
_scanStatus.value = Event(ScanStatus.INVALID) _scanStatus.value = Event(ScanStatus.INVALID)
......
package de.rki.coronawarnapp.service.submission
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockkObject
import org.junit.Before
import org.junit.Test
class ScanResultTest {
private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064"
@MockK
private lateinit var scanResult: QRScanResult
@Before
fun setUp() {
MockKAnnotations.init(this)
mockkObject(scanResult)
every { scanResult.isValid } returns false
}
@Test
fun containsValidGUID() {
//valid test
scanResult = QRScanResult("https://localhost/?$guid")
scanResult.isValid shouldBe true
// more invalid tests checks
scanResult = QRScanResult("http://localhost/?$guid")
scanResult.isValid shouldBe false
scanResult = QRScanResult("https://localhost/?")
scanResult.isValid shouldBe false
scanResult = QRScanResult("htps://wrongformat.com")
scanResult.isValid shouldBe false
scanResult =
QRScanResult("https://localhost/%20?3D6D08-3567F3F2-4DCF-43A3-8737-4CD1F87D6FDA")
scanResult.isValid shouldBe false
scanResult =
QRScanResult("https://some-host.com/?3D6D08-3567F3F2-4DCF-43A3-8737-4CD1F87D6FDA")
scanResult.isValid shouldBe false
scanResult = QRScanResult("https://localhost/?3567F3F2-4DCF-43A3-8737-4CD1F87D6FDA")
scanResult.isValid shouldBe false
scanResult = QRScanResult("https://localhost/?4CD1F87D6FDA")
scanResult.isValid shouldBe false
}
@Test
fun extractGUID() {
QRScanResult("https://localhost/?$guid").guid shouldBe guid
}
}
...@@ -11,13 +11,16 @@ class SubmissionConstantsTest { ...@@ -11,13 +11,16 @@ class SubmissionConstantsTest {
Assert.assertEquals(KeyType.GUID.name, "GUID") Assert.assertEquals(KeyType.GUID.name, "GUID")
Assert.assertEquals(KeyType.TELETAN.name, "TELETAN") Assert.assertEquals(KeyType.TELETAN.name, "TELETAN")
Assert.assertEquals(SubmissionConstants.REGISTRATION_TOKEN_URL, "version/v1/registrationToken") Assert.assertEquals(
SubmissionConstants.REGISTRATION_TOKEN_URL,
"version/v1/registrationToken"
)
Assert.assertEquals(SubmissionConstants.TEST_RESULT_URL, "version/v1/testresult") Assert.assertEquals(SubmissionConstants.TEST_RESULT_URL, "version/v1/testresult")
Assert.assertEquals(SubmissionConstants.TAN_REQUEST_URL, "version/v1/tan") Assert.assertEquals(SubmissionConstants.TAN_REQUEST_URL, "version/v1/tan")
Assert.assertEquals(SubmissionConstants.MAX_QR_CODE_LENGTH, 150) Assert.assertEquals(QRScanResult.MAX_QR_CODE_LENGTH, 150)
Assert.assertEquals(SubmissionConstants.MAX_GUID_LENGTH, 80) Assert.assertEquals(QRScanResult.MAX_GUID_LENGTH, 80)
Assert.assertEquals(SubmissionConstants.GUID_SEPARATOR, '?') Assert.assertEquals(QRScanResult.GUID_SEPARATOR, '?')
Assert.assertEquals(SubmissionConstants.SERVER_ERROR_CODE_400, 400) Assert.assertEquals(SubmissionConstants.SERVER_ERROR_CODE_400, 400)
......
...@@ -163,26 +163,4 @@ class SubmissionServiceTest { ...@@ -163,26 +163,4 @@ class SubmissionServiceTest {
} }
} }
@Test
fun containsValidGUID() {
// valid
assertThat(
SubmissionService.containsValidGUID("https://bs-sd.de/covid-19/?$guid"),
equalTo(true)
)
// invalid
assertThat(
SubmissionService.containsValidGUID("https://no-guid-here"),
equalTo(false)
)
}
@Test
fun extractGUID() {
assertThat(
SubmissionService.extractGUID("https://bs-sd.de/covid-19/?$guid"),
equalTo(guid)
)
}
} }
...@@ -51,7 +51,7 @@ class SubmissionViewModelTest { ...@@ -51,7 +51,7 @@ class SubmissionViewModelTest {
// valid guid // valid guid
val guid = "123456-12345678-1234-4DA7-B166-B86D85475064" val guid = "123456-12345678-1234-4DA7-B166-B86D85475064"
viewModel.validateAndStoreTestGUID("https://bs-sd.de/covid-19/?$guid") viewModel.validateAndStoreTestGUID("https://localhost/?$guid")
viewModel.scanStatus.value?.getContent().let { Assert.assertEquals(ScanStatus.SUCCESS, it) } viewModel.scanStatus.value?.getContent().let { Assert.assertEquals(ScanStatus.SUCCESS, it) }
// invalid guid // invalid guid
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment