Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.simprints.core.domain.externalcredential.ExternalCredential
import com.simprints.core.domain.tokenization.TokenizableString
import com.simprints.core.livedata.LiveDataEvent
import com.simprints.core.livedata.LiveDataEventWithContent
import com.simprints.core.livedata.send
Expand All @@ -24,9 +22,6 @@ import com.simprints.feature.clientapi.usecases.GetEnrolmentCreationEventForReco
import com.simprints.feature.clientapi.usecases.IsFlowCompletedWithErrorUseCase
import com.simprints.feature.clientapi.usecases.SimpleEventReporter
import com.simprints.infra.config.store.ConfigRepository
import com.simprints.infra.config.store.models.Project
import com.simprints.infra.config.store.models.TokenKeyType
import com.simprints.infra.config.store.tokenization.TokenizationProcessor
import com.simprints.infra.logging.LoggingConstants.CrashReportTag.ORCHESTRATION
import com.simprints.infra.logging.Simber
import com.simprints.infra.orchestration.data.ActionRequest
Expand All @@ -35,7 +30,6 @@ import com.simprints.infra.orchestration.data.ActionResponse
import com.simprints.infra.orchestration.data.responses.AppConfirmationResponse
import com.simprints.infra.orchestration.data.responses.AppEnrolResponse
import com.simprints.infra.orchestration.data.responses.AppErrorResponse
import com.simprints.infra.orchestration.data.responses.AppExternalCredential
import com.simprints.infra.orchestration.data.responses.AppIdentifyResponse
import com.simprints.infra.orchestration.data.responses.AppRefusalResponse
import com.simprints.infra.orchestration.data.responses.AppVerifyResponse
Expand All @@ -58,7 +52,6 @@ class ClientApiViewModel @Inject internal constructor(
private val configRepository: ConfigRepository,
private val timeHelper: TimeHelper,
private val persistentLogger: PersistentLogger,
private val tokenizationProcessor: TokenizationProcessor,
) : ViewModel() {
val returnResponse: LiveData<LiveDataEventWithContent<Bundle>>
get() = _returnResponse
Expand Down Expand Up @@ -121,7 +114,7 @@ class ClientApiViewModel @Inject internal constructor(
sessionId = currentSessionId,
enrolledGuid = enrolResponse.guid,
subjectActions = coSyncEnrolmentRecords,
externalCredential = enrolResponse.externalCredential?.toAppExternalCredential(tokenizationProcessor, getProject()),
externalCredential = enrolResponse.externalCredential,
),
),
)
Expand Down Expand Up @@ -170,7 +163,7 @@ class ClientApiViewModel @Inject internal constructor(
actionIdentifier = action.actionIdentifier,
sessionId = currentSessionId,
confirmed = confirmResponse.identificationOutcome,
externalCredential = confirmResponse.externalCredential?.toAppExternalCredential(tokenizationProcessor, getProject()),
externalCredential = confirmResponse.externalCredential,
),
),
)
Expand Down Expand Up @@ -272,21 +265,4 @@ class ClientApiViewModel @Inject internal constructor(
body = "${action.actionIdentifier}\n$response",
)
}

private fun ExternalCredential.toAppExternalCredential(
tokenizationProcessor: TokenizationProcessor,
project: Project?,
): AppExternalCredential? {
if (project == null) return null
val decryptedValue = tokenizationProcessor.decrypt(
encrypted = value,
tokenKeyType = TokenKeyType.ExternalCredential,
project = project,
) as? TokenizableString.Raw ?: return null
return AppExternalCredential(
id = id,
value = decryptedValue,
type = type,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.simprints.libsimprints.contracts.data.Identification.Companion.toJson
import com.simprints.libsimprints.contracts.data.RefusalForm
import com.simprints.libsimprints.contracts.data.ScannedCredential
import com.simprints.libsimprints.contracts.data.Verification
import org.json.JSONObject
import javax.inject.Inject
import com.simprints.libsimprints.Identification as LegacyIdentification
import com.simprints.libsimprints.RefusalForm as LegacyRefusalForm
Expand Down Expand Up @@ -165,10 +166,14 @@ internal class LibSimprintsResponseMapper @Inject constructor(

private fun Bundle.appendExternalCredential(credential: AppExternalCredential?) = apply {
credential?.let {
putString(
Constants.SIMPRINTS_SCANNED_CREDENTIAL,
ScannedCredential(it.type.name, it.value.value).toJson(),
)
val credentialJson = JSONObject(ScannedCredential(it.type.name, it.value.value).toJson())
.apply {
Comment thread
alexandr-simprints marked this conversation as resolved.
val documentFields = JSONObject().apply {
it.nonCredentialFields.forEach { (key, value) -> put(key, value) }
}
put(SCANNED_CREDENTIAL_DOCUMENT_FIELDS, documentFields)
}.toString()
putString(Constants.SIMPRINTS_SCANNED_CREDENTIAL, credentialJson)
}
}

Expand Down Expand Up @@ -208,5 +213,6 @@ internal class LibSimprintsResponseMapper @Inject constructor(

companion object {
internal const val RESULT_CODE_OVERRIDE = "result_code_override"
internal const val SCANNED_CREDENTIAL_DOCUMENT_FIELDS = "documentFields"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import android.os.Bundle
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.*
import com.google.common.truth.Truth.assertThat
import com.jraska.livedata.test
import com.simprints.core.domain.externalcredential.ExternalCredential
import com.simprints.core.domain.externalcredential.ExternalCredentialType
import com.simprints.core.domain.tokenization.TokenizableString
import com.simprints.core.domain.tokenization.asTokenizableRaw
import com.simprints.core.tools.time.TimeHelper
import com.simprints.core.tools.time.Timestamp
import com.simprints.feature.clientapi.exceptions.InvalidRequestException
Expand All @@ -21,13 +19,12 @@ import com.simprints.feature.clientapi.usecases.GetEnrolmentCreationEventForReco
import com.simprints.feature.clientapi.usecases.IsFlowCompletedWithErrorUseCase
import com.simprints.feature.clientapi.usecases.SimpleEventReporter
import com.simprints.infra.config.store.ConfigRepository
import com.simprints.infra.config.store.models.Project
import com.simprints.infra.config.store.models.TokenKeyType
import com.simprints.infra.config.store.tokenization.TokenizationProcessor
import com.simprints.infra.orchestration.data.ActionRequest
import com.simprints.infra.orchestration.data.ActionRequestIdentifier
import com.simprints.infra.orchestration.data.ActionResponse
import com.simprints.infra.orchestration.data.responses.AppEnrolResponse
import com.simprints.infra.orchestration.data.responses.AppExternalCredential
import com.simprints.logging.persistent.PersistentLogger
import com.simprints.testtools.common.coroutines.TestCoroutineRule
import io.mockk.*
Expand Down Expand Up @@ -109,7 +106,6 @@ internal class ClientApiViewModelTest {
configRepository = configRepository,
timeHelper = timeHelper,
persistentLogger = persistentLogger,
tokenizationProcessor = tokenizationProcessor,
)
}

Expand Down Expand Up @@ -276,49 +272,24 @@ internal class ClientApiViewModelTest {
}

@Test
fun `handleEnrolResponse with externalCredential decrypts and includes it in response`() = runTest {
fun `handleEnrolResponse with externalCredential includes it in response`() = runTest {
val mockGuid = "mockGuid"
val expectedCredentialId = "credentialId"
val expectedType = ExternalCredentialType.NHISCard
val credential = mockExternalCredential(expectedCredentialId, expectedType)
val project = mockk<Project>(relaxed = true)
setupDecryption(project, "decrypted-value".asTokenizableRaw())
val slot = slot<ActionResponse>()
every { resultMapper.invoke(capture(slot)) } returns mockk()

viewModel.handleEnrolResponse(mockRequest(), mockEnrolResponseWithCredential(mockGuid, credential))

verify {
resultMapper.invoke(
match<ActionResponse.EnrolActionResponse> {
it.externalCredential?.id == expectedCredentialId &&
it.externalCredential?.type == expectedType
},
)
}
}

@Test
fun `handleEnrolResponse with externalCredential but encrypted decryption returns null credential`() = runTest {
val mockGuid = "mockGuid"
val expectedCredentialId = "credentialId"
val expectedType = ExternalCredentialType.NHISCard
val credential = mockExternalCredential(expectedCredentialId, expectedType)
val project = mockk<Project>(relaxed = true)
setupDecryption(project, mockk<TokenizableString.Tokenized>())

viewModel.handleEnrolResponse(mockRequest(), mockEnrolResponseWithCredential(mockGuid, credential))

verify {
resultMapper.invoke(
match<ActionResponse.EnrolActionResponse> {
it.externalCredential == null
},
)
}
val captured = slot.captured as ActionResponse.EnrolActionResponse
assertThat(captured.externalCredential?.id).isEqualTo(expectedCredentialId)
assertThat(captured.externalCredential?.type).isEqualTo(expectedType)
}

private fun mockEnrolResponseWithCredential(
mockGuid: String,
credential: ExternalCredential?,
credential: AppExternalCredential?,
): AppEnrolResponse = mockk {
every { guid } returns mockGuid
every { externalCredential } returns credential
Expand All @@ -327,26 +298,12 @@ internal class ClientApiViewModelTest {
private fun mockExternalCredential(
mockId: String,
mockType: ExternalCredentialType,
): ExternalCredential = mockk {
): AppExternalCredential = mockk {
every { id } returns mockId
every { value } returns mockk()
every { type } returns mockType
}

private fun setupDecryption(
project: Project,
returnValue: TokenizableString,
) {
coEvery { configRepository.getProject() } returns project
every {
tokenizationProcessor.decrypt(
encrypted = any(),
tokenKeyType = TokenKeyType.ExternalCredential,
project = project,
)
} returns returnValue
}

private fun mockRequest(): ActionRequest = mockk {
every { projectId } returns "projectId"
every { actionIdentifier } returns ActionRequestIdentifier("action", "package", "", 1, 0L)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.simprints.feature.clientapi.mappers.request.requestFactories.EnrolAct
import com.simprints.feature.clientapi.mappers.request.requestFactories.EnrolLastBiometricsActionFactory
import com.simprints.feature.clientapi.mappers.request.requestFactories.IdentifyRequestActionFactory
import com.simprints.feature.clientapi.mappers.request.requestFactories.VerifyActionFactory
import com.simprints.feature.clientapi.mappers.response.LibSimprintsResponseMapper.Companion.SCANNED_CREDENTIAL_DOCUMENT_FIELDS
import com.simprints.infra.orchestration.data.ActionResponse
import com.simprints.infra.orchestration.data.responses.AppExternalCredential
import com.simprints.infra.orchestration.data.responses.AppMatchResult
Expand Down Expand Up @@ -139,7 +140,7 @@ class LibSimprintsResponseMapperTest {
fun `correctly maps confirm response`() {
val expectedValue = "expectedValue".asTokenizableRaw()
val expectedType = ExternalCredentialType.NHISCard
val expectedJson = """{"type":"$expectedType","value":"$expectedValue"}"""
val expectedJson = """{"type":"$expectedType","value":"$expectedValue","$SCANNED_CREDENTIAL_DOCUMENT_FIELDS":{}}"""
val extras = mapper(
ActionResponse.ConfirmActionResponse(
actionIdentifier = ConfirmIdentityActionFactory.getIdentifier(),
Expand All @@ -148,6 +149,7 @@ class LibSimprintsResponseMapperTest {
externalCredential = mockk {
every { value } returns expectedValue
every { type } returns expectedType
every { nonCredentialFields } returns emptyMap()
},
),
)
Expand Down Expand Up @@ -406,7 +408,7 @@ class LibSimprintsResponseMapperTest {
fun `correctly maps enrol response with external credential`() {
val expectedValue = "expectedValue".asTokenizableRaw()
val expectedType = ExternalCredentialType.NHISCard
val expectedJson = """{"type":"$expectedType","value":"$expectedValue"}"""
val expectedJson = """{"type":"$expectedType","value":"$expectedValue","$SCANNED_CREDENTIAL_DOCUMENT_FIELDS":{}}"""

val extras = mapper(
ActionResponse.EnrolActionResponse(
Expand All @@ -417,6 +419,7 @@ class LibSimprintsResponseMapperTest {
externalCredential = mockk {
every { value } returns expectedValue
every { type } returns expectedType
every { nonCredentialFields } returns emptyMap()
},
),
)
Expand Down Expand Up @@ -538,14 +541,65 @@ class LibSimprintsResponseMapperTest {
)
}

@Test
fun `correctly maps external credential with non-credential fields`() {
val expectedValue = "expectedValue".asTokenizableRaw()
val expectedType = ExternalCredentialType.NHISCard
val field1Key = "field1Key"
val field1Value = "field1Value"
val field2Key = "field2Key"
val field2Value = "field2Value"
val expectedJson = """{"type":"$expectedType","value":"$expectedValue","$SCANNED_CREDENTIAL_DOCUMENT_FIELDS":{"$field1Key":"$field1Value","$field2Key":"$field2Value"}}"""

val extras = mapper(
ActionResponse.EnrolActionResponse(
actionIdentifier = EnrolActionFactory.getIdentifier(),
sessionId = "sessionId",
enrolledGuid = "guid",
subjectActions = "subjects",
externalCredential = mockk {
every { value } returns expectedValue
every { type } returns expectedType
every { nonCredentialFields } returns mapOf(field1Key to field1Value, field2Key to field2Value)
},
),
)

assertThat(extras.getString(Constants.SIMPRINTS_SCANNED_CREDENTIAL)).isEqualTo(expectedJson)
}

@Test
fun `correctly maps external credential with empty non-credential fields`() {
val expectedValue = "expectedValue".asTokenizableRaw()
val expectedType = ExternalCredentialType.NHISCard
val expectedJson = """{"type":"$expectedType","value":"$expectedValue","$SCANNED_CREDENTIAL_DOCUMENT_FIELDS":{}}"""

val extras = mapper(
ActionResponse.EnrolActionResponse(
actionIdentifier = EnrolActionFactory.getIdentifier(),
sessionId = "sessionId",
enrolledGuid = "guid",
subjectActions = "subjects",
externalCredential = mockk {
every { value } returns expectedValue
every { type } returns expectedType
every { nonCredentialFields } returns emptyMap()
},
),
)

assertThat(extras.getString(Constants.SIMPRINTS_SCANNED_CREDENTIAL)).isEqualTo(expectedJson)
}

@Test
fun `when MFID is enabled, identify response contains scanned credential`() {
val expectedValue = "expectedValue".asTokenizableRaw()
val expectedType = ExternalCredentialType.NHISCard
val expectedJson = """{"type":"$expectedType","value":"$expectedValue"}"""
val expectedJson = """{"type":"$expectedType","value":"$expectedValue","$SCANNED_CREDENTIAL_DOCUMENT_FIELDS":{}}"""
val scannedCredential = mockk<AppExternalCredential> {
every { value } returns expectedValue
every { type } returns expectedType
every { nonCredentialFields } returns emptyMap()
}

val extras = mapper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.simprints.feature.enrollast

import com.simprints.core.ExcludedFromGeneratedTestCoverageReports
import com.simprints.core.domain.tokenization.TokenizableString
import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential
import com.simprints.feature.externalcredential.ExternalCredentialSearchResult

@ExcludedFromGeneratedTestCoverageReports("Data class")
object EnrolLastBiometricContract {
Expand All @@ -13,12 +13,12 @@ object EnrolLastBiometricContract {
userId: TokenizableString,
moduleId: TokenizableString,
steps: List<EnrolLastBiometricStepResult>,
scannedCredential: ScannedCredential?,
credentialSearchResult: ExternalCredentialSearchResult.Complete?,
) = EnrolLastBiometricParams(
projectId = projectId,
userId = userId,
moduleId = moduleId,
steps = steps,
scannedCredential = scannedCredential,
credentialSearchResult = credentialSearchResult,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.simprints.core.domain.capture.BiometricReferenceCapture
import com.simprints.core.domain.comparison.ComparisonResult
import com.simprints.core.domain.step.StepParams
import com.simprints.core.domain.tokenization.TokenizableString
import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential
import com.simprints.feature.externalcredential.ExternalCredentialSearchResult
import com.simprints.infra.config.store.models.ModalitySdkType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand All @@ -19,7 +19,7 @@ data class EnrolLastBiometricParams(
val userId: TokenizableString,
val moduleId: TokenizableString,
val steps: List<EnrolLastBiometricStepResult>,
val scannedCredential: ScannedCredential?,
val credentialSearchResult: ExternalCredentialSearchResult.Complete?,
) : StepParams

@Serializable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.simprints.feature.enrollast

import androidx.annotation.Keep
import com.simprints.core.domain.externalcredential.ExternalCredential
import com.simprints.core.domain.step.StepResult
import com.simprints.feature.externalcredential.ExternalCredentialSearchResult
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand All @@ -11,5 +11,5 @@ import kotlinx.serialization.Serializable
@SerialName("EnrolLastBiometricResult")
data class EnrolLastBiometricResult(
val newSubjectId: String?,
val externalCredential: ExternalCredential?,
val credentialSearchResult: ExternalCredentialSearchResult.Complete?,
) : StepResult
Loading
Loading