diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCase.kt index 69f2839a59..a181a66195 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCase.kt @@ -29,13 +29,13 @@ internal class AppResponseBuilderUseCase @Inject constructor( request = request, results = results, project = project, - enrolmentSubjectId = enrolmentSubjectId + enrolmentSubjectId = enrolmentSubjectId, ) } else { - handleIdentify(projectConfiguration, results) + handleIdentify(projectConfiguration, project, results) } - is ActionRequest.IdentifyActionRequest -> handleIdentify(projectConfiguration, results) + is ActionRequest.IdentifyActionRequest -> handleIdentify(projectConfiguration, project, results) is ActionRequest.VerifyActionRequest -> handleVerify(projectConfiguration, results) is ActionRequest.ConfirmIdentityActionRequest -> handleConfirmIdentity(results) is ActionRequest.EnrolLastBiometricActionRequest -> handleEnrolLastBiometric(results) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt index 8dbe30fba5..03fefb53f3 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt @@ -1,9 +1,13 @@ package com.simprints.feature.orchestrator.usecases.response +import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.feature.externalcredential.ExternalCredentialSearchResult import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential import com.simprints.infra.config.store.models.DecisionPolicy +import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.ProjectConfiguration +import com.simprints.infra.config.store.models.TokenKeyType +import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.events.session.SessionEventRepository import com.simprints.infra.matching.FaceMatchResult import com.simprints.infra.matching.FingerprintMatchResult @@ -17,9 +21,11 @@ import javax.inject.Inject internal class CreateIdentifyResponseUseCase @Inject constructor( private val eventRepository: SessionEventRepository, + private val tokenizationProcessor: TokenizationProcessor, ) { suspend operator fun invoke( projectConfiguration: ProjectConfiguration, + project: Project?, results: List, ): AppResponse { val isMultiFactorIdEnabled = projectConfiguration.multifactorId?.allowedExternalCredentials?.isNotEmpty() ?: false @@ -51,7 +57,7 @@ internal class CreateIdentifyResponseUseCase @Inject constructor( .filterIsInstance(ExternalCredentialSearchResult::class.java) .lastOrNull() ?.scannedCredential - .toAppExternalCredential() + ?.toAppExternalCredential(tokenizationProcessor, project) return AppIdentifyResponse( sessionId = currentSessionId, @@ -61,11 +67,20 @@ internal class CreateIdentifyResponseUseCase @Inject constructor( ) } - private fun ScannedCredential?.toAppExternalCredential(): AppExternalCredential? = this?.let { scannedCredential -> - AppExternalCredential( - id = scannedCredential.credentialScanId, - value = scannedCredential.scannedValue, - type = scannedCredential.credentialType, + private fun ScannedCredential.toAppExternalCredential( + tokenizationProcessor: TokenizationProcessor, + project: Project?, + ): AppExternalCredential? { + if (project == null) return null + val decryptedValue = tokenizationProcessor.decrypt( + encrypted = credential, + tokenKeyType = TokenKeyType.ExternalCredential, + project = project, + ) as? TokenizableString.Raw ?: return null + return AppExternalCredential( + id = credentialScanId, + value = decryptedValue, + type = credentialType, ) } diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCaseTest.kt index 1ff982ba2e..7a09848279 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/AppResponseBuilderUseCaseTest.kt @@ -40,7 +40,7 @@ internal class AppResponseBuilderUseCaseTest { MockKAnnotations.init(this, relaxUnitFun = true) coEvery { handleEnrolment.invoke(any(), any(), any(), any()) } returns mockk() - coEvery { handleIdentify.invoke(any(), any()) } returns mockk() + coEvery { handleIdentify.invoke(any(), any(), any()) } returns mockk() every { handleVerify.invoke(any(), any()) } returns mockk() every { handleConfirmIdentity.invoke(any()) } returns mockk() every { handleEnrolLastBiometric.invoke(any()) } returns mockk() @@ -67,13 +67,13 @@ internal class AppResponseBuilderUseCaseTest { fun `Handles as identification for enrolment action with existing item`() = runTest { every { isNewEnrolment(any(), any()) } returns false useCase(mockk(), mockk(), mockk(), mockk(), enrolmentSubjectId) - coVerify { handleIdentify.invoke(any(), any()) } + coVerify { handleIdentify.invoke(any(), any(), any()) } } @Test fun `Handles as identification for identification action`() = runTest { useCase(mockk(), mockk(), mockk(), mockk(), enrolmentSubjectId) - coVerify { handleIdentify.invoke(any(), any()) } + coVerify { handleIdentify.invoke(any(), any(), any()) } } @Test diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt index 1d11320e17..39945cdb1b 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt @@ -1,11 +1,15 @@ package com.simprints.feature.orchestrator.usecases.response import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.feature.externalcredential.ExternalCredentialSearchResult import com.simprints.feature.externalcredential.model.CredentialMatch import com.simprints.infra.config.store.models.DecisionPolicy import com.simprints.infra.config.store.models.FaceConfiguration import com.simprints.infra.config.store.models.FingerprintConfiguration +import com.simprints.infra.config.store.models.Project +import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.events.session.SessionEventRepository import com.simprints.infra.matching.FaceMatchResult import com.simprints.infra.matching.FingerprintMatchResult @@ -24,6 +28,12 @@ class CreateIdentifyResponseUseCaseTest { @MockK lateinit var eventRepository: SessionEventRepository + @MockK + lateinit var tokenizationProcessor: TokenizationProcessor + + @MockK + lateinit var project: Project + private lateinit var useCase: CreateIdentifyResponseUseCase @Before @@ -32,7 +42,7 @@ class CreateIdentifyResponseUseCaseTest { coEvery { eventRepository.getCurrentSessionScope().id } returns "sessionId" - useCase = CreateIdentifyResponseUseCase(eventRepository) + useCase = CreateIdentifyResponseUseCase(eventRepository, tokenizationProcessor) } @Test @@ -45,6 +55,7 @@ class CreateIdentifyResponseUseCaseTest { every { fingerprint?.getSdkConfiguration((any()))?.decisionPolicy } returns null }, results = listOf(createFaceMatchResult(10f, 20f, 30f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isEmpty() @@ -60,6 +71,7 @@ class CreateIdentifyResponseUseCaseTest { every { fingerprint?.getSdkConfiguration((any()))?.decisionPolicy } returns null }, results = listOf(createFaceMatchResult(10f, 20f, 30f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -76,6 +88,7 @@ class CreateIdentifyResponseUseCaseTest { every { fingerprint?.getSdkConfiguration((any()))?.decisionPolicy } returns null }, results = listOf(createFaceMatchResult(20f, 25f, 30f, 40f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -92,6 +105,7 @@ class CreateIdentifyResponseUseCaseTest { every { fingerprint?.getSdkConfiguration((any()))?.decisionPolicy } returns null }, results = listOf(createFaceMatchResult(15f, 30f, 100f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -112,6 +126,7 @@ class CreateIdentifyResponseUseCaseTest { ) }, results = listOf(createFingerprintMatchResult(10f, 20f, 30f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -132,6 +147,7 @@ class CreateIdentifyResponseUseCaseTest { ) }, results = listOf(createFingerprintMatchResult(20f, 25f, 30f, 40f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -152,6 +168,7 @@ class CreateIdentifyResponseUseCaseTest { ) }, results = listOf(createFingerprintMatchResult(15f, 30f, 100f)), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -175,6 +192,7 @@ class CreateIdentifyResponseUseCaseTest { createFaceMatchResult(15f, 30f, 100f), createFingerprintMatchResult(15f, 30f, 105f), ), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -198,6 +216,7 @@ class CreateIdentifyResponseUseCaseTest { createFaceMatchResult(15f, 30f, 105f), createFingerprintMatchResult(15f, 30f, 100f), ), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -262,6 +281,7 @@ class CreateIdentifyResponseUseCaseTest { every { scannedCredential } returns null }, ), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).isNotEmpty() @@ -314,6 +334,7 @@ class CreateIdentifyResponseUseCaseTest { FaceConfiguration.BioSdk.RANK_ONE, ), ), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).hasSize(1) @@ -366,6 +387,7 @@ class CreateIdentifyResponseUseCaseTest { FingerprintConfiguration.BioSdk.SECUGEN_SIM_MATCHER, ), ), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).hasSize(1) @@ -437,6 +459,7 @@ class CreateIdentifyResponseUseCaseTest { }, faceMatchResults, ), + project = project, ) assertThat((result as AppIdentifyResponse).identifications).hasSize(maxNbOfReturnedCandidates) @@ -454,6 +477,40 @@ class CreateIdentifyResponseUseCaseTest { ) } + @Test + fun `Returns scanned credential when decryption succeeds`() = runTest { + val id = "id" + val type = ExternalCredentialType.NHISCard + val expectedDecrypted = "expectedDecrypted".asTokenizableRaw() + + every { tokenizationProcessor.decrypt(any(), any(), any()) } returns expectedDecrypted + + val result = useCase( + mockk { + every { multifactorId?.allowedExternalCredentials } returns null + every { identification.maxNbOfReturnedCandidates } returns 2 + every { face?.getSdkConfiguration(any())?.decisionPolicy } returns null + every { fingerprint?.getSdkConfiguration(any())?.decisionPolicy } returns null + }, + results = listOf( + mockk { + every { matchResults } returns emptyList() + every { scannedCredential } returns mockk { + every { credentialScanId } returns id + every { credentialType } returns type + every { credential } returns mockk() + } + }, + ), + project = project, + ) + + assertThat((result as AppIdentifyResponse).scannedCredential).isNotNull() + assertThat(result.scannedCredential?.id).isEqualTo(id) + assertThat(result.scannedCredential?.type).isEqualTo(type) + assertThat(result.scannedCredential?.value).isEqualTo(expectedDecrypted) + } + private fun createFaceMatchResult(vararg confidences: Float): Serializable = FaceMatchResult( confidences.mapIndexed { i, confidence -> FaceMatchResult.Item(subjectId = "$i", confidence = confidence) }, FaceConfiguration.BioSdk.RANK_ONE,