Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
84a0044
WIP [CORE-3421] first steps of adding the external credentials supportˆ
alexandr-simprints Jul 29, 2025
9504059
[WIP] Adding external credentials to the Realm DBˆ
alexandr-simprints Aug 4, 2025
f279637
[CORE-3421] Adding external credential support on Domain, DB (Realm, …
alexandr-simprints Aug 10, 2025
d61fb62
[CORE-3421] Fixing testsˆ
alexandr-simprints Aug 10, 2025
0660608
[CORE-3421] Fixing testsˆ
alexandr-simprints Aug 10, 2025
5317e06
[CORE-3421] Fixing testsˆ
alexandr-simprints Aug 10, 2025
54f7ddf
[CORE-3421] Fixing testsˆ
alexandr-simprints Aug 10, 2025
d806959
[CORE-3421] Fixing testsˆˆ
alexandr-simprints Aug 10, 2025
6aedb12
[CORE-3421] Fixing tests
alexandr-simprints Aug 10, 2025
23a6c01
[CORE-3421] Adding 1->2 Room migrationˆ
alexandr-simprints Aug 12, 2025
7c2c5ff
[CORE-3421] Moving Room DB version to a static field so that it can b…
alexandr-simprints Aug 12, 2025
3036a3a
[CORE-3421] Updating tests
alexandr-simprints Aug 12, 2025
f0d359c
[CORE-3421] Removing unused classes
alexandr-simprints Aug 12, 2025
e445a48
[CORE-3421] External credentials are now stored as list on the Enrolm…
alexandr-simprints Aug 25, 2025
b372abe
[CORE-3421] Moving external credential Realm mapper to its separate file
alexandr-simprints Aug 25, 2025
b715b1b
[CORE-3421] EnrolmentRecordMoveEvent now contains external credentialˆ
alexandr-simprints Aug 25, 2025
16e166a
[CORE-3421] Reverting addition of the external credentials in Subject…
alexandr-simprints Aug 25, 2025
a3ed7f6
[CORE-3421] Addition of the external credentials back in SubjectBiome…
alexandr-simprints Aug 25, 2025
37d8cb4
[CORE-3421] Fixing tests
alexandr-simprints Aug 26, 2025
05d2a00
[CORE-3421] Adding test coverage for ExternalCredentialType
alexandr-simprints Aug 26, 2025
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 @@ -47,11 +47,13 @@ internal class GetEnrolmentCreationEventForSubjectUseCase @Inject constructor(
}

private fun Subject.fromSubjectToEnrolmentCreationEvent() = EnrolmentRecordCreationEvent(
subjectId,
projectId,
moduleId,
attendantId,
EnrolmentRecordCreationEvent.buildBiometricReferences(fingerprintSamples, faceSamples, encoder),
subjectId = subjectId,
projectId = projectId,
moduleId = moduleId,
attendantId = attendantId,
biometricReferences = EnrolmentRecordCreationEvent.buildBiometricReferences(fingerprintSamples, faceSamples, encoder),
// TODO [CORE-3421] Review if EnrolmentRecordCreationEvent should contain List of external credentials, as it currently doesn't make sense
Comment thread
alexandr-simprints marked this conversation as resolved.
externalCredentials = externalCredentials
)

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.simprints.feature.enrollast.screen.usecase

import com.simprints.core.domain.externalcredential.ExternalCredential
import com.simprints.core.domain.face.FaceSample
import com.simprints.core.domain.fingerprint.FingerprintSample
import com.simprints.core.domain.fingerprint.IFingerIdentifier
Expand Down Expand Up @@ -31,8 +32,14 @@ internal class BuildSubjectUseCase @Inject constructor(
faceSamples = getFaceCaptureResult(params.steps)
?.let { result -> result.results.map { faceSample(result.referenceId, it) } }
.orEmpty(),
externalCredentials = getExternalCredentialResult(params.steps)?.let { listOf(it) } ?: emptyList()
)

// TODO [CORE-3421] When an external credential can be extracted from the UI-level steps, extract it here
private fun getExternalCredentialResult(steps: List<EnrolLastBiometricStepResult>): ExternalCredential? {
return null
}

private fun getFingerprintCaptureResult(steps: List<EnrolLastBiometricStepResult>) = steps
.filterIsInstance<EnrolLastBiometricStepResult.FingerprintCaptureResult>()
.firstOrNull()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.simprints.feature.orchestrator.usecases.response

import com.simprints.core.domain.externalcredential.ExternalCredential
import com.simprints.core.domain.response.AppErrorReason
import com.simprints.face.capture.FaceCaptureResult
import com.simprints.fingerprint.capture.FingerprintCaptureResult
Expand All @@ -25,6 +26,8 @@ internal class CreateEnrolResponseUseCase @Inject constructor(
): AppResponse {
val fingerprintCapture = results.filterIsInstance(FingerprintCaptureResult::class.java).lastOrNull()
val faceCapture = results.filterIsInstance(FaceCaptureResult::class.java).lastOrNull()
// TODO [CORE-3421] When an external credential can be extracted from the UI-level steps, extract it here
val externalCredential: ExternalCredential? = null

return try {
val subject = subjectFactory.buildSubjectFromCaptureResults(
Expand All @@ -33,6 +36,7 @@ internal class CreateEnrolResponseUseCase @Inject constructor(
moduleId = request.moduleId,
fingerprintResponse = fingerprintCapture,
faceResponse = faceCapture,
externalCredential = externalCredential,
)
enrolSubject(subject, project)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ internal class CreateEnrolResponseUseCaseTest {
@Test
fun `Converts correct results to response`() = runTest {
every {
subjectFactory.buildSubjectFromCaptureResults(any(), any(), any(), any(), any())
subjectFactory.buildSubjectFromCaptureResults(
projectId = any(),
attendantId = any(),
moduleId = any(),
fingerprintResponse = any(),
faceResponse = any(),
externalCredential = any()
)
} returns mockk { every { subjectId } returns "guid" }

assertThat(
Expand All @@ -68,7 +75,14 @@ internal class CreateEnrolResponseUseCaseTest {
@Test
fun `Returns error if no valid response`() = runTest {
every {
subjectFactory.buildSubjectFromCaptureResults(any(), any(), any(), null, null)
subjectFactory.buildSubjectFromCaptureResults(
projectId = any(),
attendantId = any(),
moduleId = any(),
fingerprintResponse = null,
faceResponse = null,
externalCredential = null
)
} throws MissingCaptureException()

assertThat(useCase(action, emptyList(), project)).isInstanceOf(AppErrorResponse::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ class ProjectAuthenticatorTest {
} returns Token("", "", "", "")

coEvery { configManager.getProjectConfiguration() } returns ProjectConfiguration(
"id",
PROJECT_ID,
"",
id = "id",
projectId = PROJECT_ID,
updatedAt = "",
general = GeneralConfiguration(
modalities = mockk(),
matchingModalities = mockk(),
Expand All @@ -176,12 +176,13 @@ class ProjectAuthenticatorTest {
duplicateBiometricEnrolmentCheck = false,
settingsPassword = mockk(),
),
mockk(),
mockk(),
mockk(),
mockk(),
mockk(),
mockk(),
face = mockk(),
fingerprint = mockk(),
consent = mockk(),
identification = mockk(),
synchronization = mockk(),
multifactorId = mockk(),
custom = mockk(),
)
coEvery { configManager.getPrivacyNotice(any(), any()) } returns emptyFlow()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,16 +227,17 @@ internal class SignerManagerTest {
tokenizationKeys = emptyMap(),
),
ProjectConfiguration(
"id",
DEFAULT_PROJECT_ID,
"",
mockk(),
mockk(),
mockk(),
mockk(),
mockk(),
mockk(),
null,
id = "id",
projectId = DEFAULT_PROJECT_ID,
updatedAt = "",
general = mockk(),
face = mockk(),
fingerprint = mockk(),
consent = mockk(),
identification = mockk(),
synchronization = mockk(),
multifactorId = mockk(),
custom = mockk(),
),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ internal class ConfigLocalDataSourceImpl @Inject constructor(
),
),
custom = null,
multifactorId = null
).toProto()
val defaultDeviceConfiguration: ProtoDeviceConfiguration = DeviceConfiguration(
language = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ internal data class OldProjectConfig(
consent = consentConfiguration(),
identification = identificationConfiguration(),
synchronization = synchronizationConfiguration(),
multifactorId = null,
custom = null,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.simprints.infra.config.store.local.models

import com.simprints.core.domain.externalcredential.ExternalCredentialType
import com.simprints.infra.config.store.exceptions.InvalidProtobufEnumException

internal fun ExternalCredentialType.toProto(): ProtoExternalCredentialType = when(this){
ExternalCredentialType.NHISCard -> ProtoExternalCredentialType.NHIS_CARD
ExternalCredentialType.GhanaIdCard -> ProtoExternalCredentialType.GHANA_ID_CARD
ExternalCredentialType.QRCode -> ProtoExternalCredentialType.QR_CODE
}

internal fun ProtoExternalCredentialType.toDomain(): ExternalCredentialType = when(this){
ProtoExternalCredentialType.UNRECOGNIZED,
ProtoExternalCredentialType.EXTERNAL_CREDENTIAL_TYPE_UNSPECIFIED -> throw InvalidProtobufEnumException("invalid External credential $name")
ProtoExternalCredentialType.NHIS_CARD -> ExternalCredentialType.NHISCard
ProtoExternalCredentialType.GHANA_ID_CARD -> ExternalCredentialType.GhanaIdCard
ProtoExternalCredentialType.QR_CODE -> ExternalCredentialType.QRCode
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.simprints.infra.config.store.local.models

import com.simprints.infra.config.store.models.MultiFactorIdConfiguration

internal fun MultiFactorIdConfiguration.toProto(): ProtoMultiFactorIdConfiguration = ProtoMultiFactorIdConfiguration
.newBuilder()
.addAllAllowedExternalCredentials(allowedExternalCredentials.map { it.toProto() })
.build()

internal fun ProtoMultiFactorIdConfiguration.toDomain(): MultiFactorIdConfiguration = MultiFactorIdConfiguration(
allowedExternalCredentials = allowedExternalCredentialsList.map { it.toDomain() }
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal fun ProjectConfiguration.toProto(): ProtoProjectConfiguration = ProtoPr
.also {
if (face != null) it.face = face.toProto()
if (fingerprint != null) it.fingerprint = fingerprint.toProto()
if (multifactorId != null) it.multiFactorId = multifactorId.toProto()
}.also {
if (custom != null) {
try {
Expand All @@ -28,16 +29,17 @@ internal fun ProjectConfiguration.toProto(): ProtoProjectConfiguration = ProtoPr
}.build()

internal fun ProtoProjectConfiguration.toDomain(): ProjectConfiguration = ProjectConfiguration(
id,
projectId,
updatedAt,
general.toDomain(),
hasFace().let { if (it) face.toDomain() else null },
hasFingerprint().let { if (it) fingerprint.toDomain() else null },
consent.toDomain(),
identification.toDomain(),
synchronization.toDomain(),
customJson?.takeIf { it.isNotBlank() }?.let {
id = id,
projectId = projectId,
updatedAt = updatedAt,
general = general.toDomain(),
face = hasFace().let { if (it) face.toDomain() else null },
fingerprint = hasFingerprint().let { if (it) fingerprint.toDomain() else null },
consent = consent.toDomain(),
identification = identification.toDomain(),
synchronization = synchronization.toDomain(),
multifactorId = multiFactorId?.toDomain(),
custom = customJson?.takeIf { it.isNotBlank() }?.let {
try {
JsonHelper.fromJson(it)
} catch (_: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.simprints.infra.config.store.models

import com.simprints.core.domain.externalcredential.ExternalCredentialType

data class MultiFactorIdConfiguration(
val allowedExternalCredentials: List<ExternalCredentialType>
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ data class Project(
enum class TokenKeyType {
AttendantId,
ModuleId,
ExternalCredential,
Unknown,
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ data class ProjectConfiguration(
val consent: ConsentConfiguration,
val identification: IdentificationConfiguration,
val synchronization: SynchronizationConfiguration,
val multifactorId: MultiFactorIdConfiguration?,
val custom: Map<String, Any>?,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.simprints.infra.config.store.remote.models

import androidx.annotation.Keep
import com.simprints.core.domain.externalcredential.ExternalCredentialType
import com.simprints.infra.config.store.models.MultiFactorIdConfiguration

@Keep
internal data class ApiMultiFactorIdConfiguration(
val allowedExternalCredentials: List<ApiExternalCredentialType>
) {
fun toDomain(): MultiFactorIdConfiguration = MultiFactorIdConfiguration(
allowedExternalCredentials = allowedExternalCredentials.map { it.toDomain() }
)
}

@Keep
enum class ApiExternalCredentialType {
NHISCard, GhanaIdCard, QRCode;

fun toDomain(): ExternalCredentialType = when (this) {
NHISCard -> ExternalCredentialType.NHISCard
GhanaIdCard -> ExternalCredentialType.GhanaIdCard
QRCode -> ExternalCredentialType.QRCode
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ internal data class ApiProjectConfiguration(
val consent: ApiConsentConfiguration,
val identification: ApiIdentificationConfiguration,
val synchronization: ApiSynchronizationConfiguration,
val multiFactorId: ApiMultiFactorIdConfiguration?,
val custom: Map<String, Any>?,
) {
fun toDomain(): ProjectConfiguration = ProjectConfiguration(
id,
projectId,
updatedAt,
general.toDomain(),
face?.toDomain(),
fingerprint?.toDomain(),
consent.toDomain(),
identification.toDomain(),
synchronization.toDomain(),
custom,
id = id,
projectId = projectId,
updatedAt = updatedAt,
general = general.toDomain(),
face = face?.toDomain(),
fingerprint = fingerprint?.toDomain(),
consent = consent.toDomain(),
identification = identification.toDomain(),
synchronization = synchronization.toDomain(),
multifactorId = multiFactorId?.toDomain(),
custom = custom,
)
}
12 changes: 12 additions & 0 deletions infra/config-store/src/main/proto/project_config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ message ProtoProjectConfiguration {
string updated_at = 8;
optional string customJson = 9;
string id = 10;
optional ProtoMultiFactorIdConfiguration multiFactorId = 11;
}

enum ProtoExternalCredentialType {
EXTERNAL_CREDENTIAL_TYPE_UNSPECIFIED = 0;
NHIS_CARD = 1;
GHANA_ID_CARD = 2;
QR_CODE = 3;
}

message ProtoMultiFactorIdConfiguration {
repeated ProtoExternalCredentialType allowed_external_credentials = 1;
}

message ProtoGeneralConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.simprints.infra.config.store.testtools.consentConfiguration
import com.simprints.infra.config.store.testtools.faceConfiguration
import com.simprints.infra.config.store.testtools.generalConfiguration
import com.simprints.infra.config.store.testtools.identificationConfiguration
import com.simprints.infra.config.store.testtools.multiFactorIdConfiguration
import com.simprints.infra.config.store.testtools.project
import com.simprints.infra.config.store.testtools.projectConfiguration
import com.simprints.infra.config.store.testtools.synchronizationConfiguration
Expand Down Expand Up @@ -173,16 +174,17 @@ class ConfigLocalDataSourceImplTest {
fun `should save the project configuration and update the device configuration correctly with an empty list of fingersToCollect if fingerprint config is missing`() =
runTest {
val projectConfigurationToSave = ProjectConfiguration(
"id",
"projectId",
"updatedAt",
generalConfiguration,
faceConfiguration,
null,
consentConfiguration,
identificationConfiguration,
synchronizationConfiguration,
null,
id = "id",
projectId = "projectId",
updatedAt = "updatedAt",
general = generalConfiguration,
face = faceConfiguration,
fingerprint = null,
consent = consentConfiguration,
identification = identificationConfiguration,
synchronization = synchronizationConfiguration,
multifactorId = multiFactorIdConfiguration,
custom = null,
)

configLocalDataSourceImpl.saveProjectConfiguration(projectConfigurationToSave)
Expand Down
Loading