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 @@ -10,6 +10,7 @@ import com.simprints.core.livedata.send
import com.simprints.feature.login.LoginError
import com.simprints.feature.login.LoginResult
import com.simprints.feature.logincheck.usecases.AddAuthorizationEventUseCase
import com.simprints.feature.logincheck.usecases.EnsureActionFieldsTokenizedUseCase
import com.simprints.feature.logincheck.usecases.ExtractCrashKeysUseCase
import com.simprints.feature.logincheck.usecases.ExtractParametersForAnalyticsUseCase
import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase
Expand Down Expand Up @@ -52,6 +53,7 @@ class LoginCheckViewModel @Inject internal constructor(
private val updateProjectInCurrentSession: UpdateProjectInCurrentSessionUseCase,
private val updateStoredUserId: UpdateStoredUserIdUseCase,
private val realmToRoomMigrationScheduler: RealmToRoomMigrationScheduler,
private val ensureActionFieldsTokenizedUseCase: EnsureActionFieldsTokenizedUseCase,
) : ViewModel() {
private var cachedRequest: ActionRequest? = null
private val loginAlreadyTried: AtomicBoolean = AtomicBoolean(false)
Expand Down Expand Up @@ -138,7 +140,7 @@ class LoginCheckViewModel @Inject internal constructor(
null, ProjectState.PROJECT_ENDING -> _showAlert.send(LoginCheckError.PROJECT_ENDING)
ProjectState.PROJECT_PAUSED -> _showAlert.send(LoginCheckError.PROJECT_PAUSED)
ProjectState.PROJECT_ENDED -> startSignInAttempt(actionRequest)
ProjectState.RUNNING -> proceedWithAction(actionRequest)
ProjectState.RUNNING -> proceedWithAction(ensureActionFieldsTokenizedUseCase(actionRequest))
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.simprints.feature.logincheck.usecases

import com.simprints.infra.config.store.models.TokenKeyType
import com.simprints.infra.config.store.tokenization.TokenizationProcessor
import com.simprints.infra.config.sync.ConfigManager
import com.simprints.infra.orchestration.data.ActionRequest
import javax.inject.Inject

class EnsureActionFieldsTokenizedUseCase @Inject constructor(
private val configManager: ConfigManager,
private val tokenizationProcessor: TokenizationProcessor,
) {
suspend operator fun invoke(action: ActionRequest): ActionRequest {
val project = configManager.getProject() ?: return action

// There is no automatic `.copy()` on the the interfaces, so we have to enumerate all sub-classes separately
return when (action) {
is ActionRequest.EnrolActionRequest -> action.copy(
userId = tokenizationProcessor.tokenizeIfNecessary(action.userId, TokenKeyType.AttendantId, project),
moduleId = tokenizationProcessor.tokenizeIfNecessary(action.moduleId, TokenKeyType.ModuleId, project),
)

is ActionRequest.IdentifyActionRequest -> action.copy(
userId = tokenizationProcessor.tokenizeIfNecessary(action.userId, TokenKeyType.AttendantId, project),
moduleId = tokenizationProcessor.tokenizeIfNecessary(action.moduleId, TokenKeyType.ModuleId, project),
)

is ActionRequest.VerifyActionRequest -> action.copy(
userId = tokenizationProcessor.tokenizeIfNecessary(action.userId, TokenKeyType.AttendantId, project),
moduleId = tokenizationProcessor.tokenizeIfNecessary(action.moduleId, TokenKeyType.ModuleId, project),
)

is ActionRequest.EnrolLastBiometricActionRequest -> action.copy(
userId = tokenizationProcessor.tokenizeIfNecessary(action.userId, TokenKeyType.AttendantId, project),
moduleId = tokenizationProcessor.tokenizeIfNecessary(action.moduleId, TokenKeyType.ModuleId, project),
)

is ActionRequest.ConfirmIdentityActionRequest -> action.copy(
userId = tokenizationProcessor.tokenizeIfNecessary(action.userId, TokenKeyType.AttendantId, project),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.simprints.feature.login.LoginError
import com.simprints.feature.login.LoginResult
import com.simprints.feature.logincheck.usecases.ActionFactory
import com.simprints.feature.logincheck.usecases.AddAuthorizationEventUseCase
import com.simprints.feature.logincheck.usecases.EnsureActionFieldsTokenizedUseCase
import com.simprints.feature.logincheck.usecases.ExtractCrashKeysUseCase
import com.simprints.feature.logincheck.usecases.ExtractParametersForAnalyticsUseCase
import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase
Expand Down Expand Up @@ -75,27 +76,33 @@ internal class LoginCheckViewModelTest {
@MockK
lateinit var realmToRoomMigrationScheduler: RealmToRoomMigrationScheduler

@MockK
lateinit var ensureActionFieldsTokenizedUseCase: EnsureActionFieldsTokenizedUseCase

private lateinit var viewModel: LoginCheckViewModel

@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)

viewModel = LoginCheckViewModel(
rootMatchers,
reportActionRequestEventsUseCase,
extractParametersForAnalyticsUseCase,
extractCrashKeysUseCase,
addAuthorizationEventUseCase,
isUserSignedInUseCase,
configManager,
startBackgroundSync,
syncOrchestrator,
updateSessionScopePayloadUseCase,
updateProjectStateUseCase,
updateStoredUserIdUseCase,
realmToRoomMigrationScheduler,
rootManager = rootMatchers,
reportActionRequestEvents = reportActionRequestEventsUseCase,
extractParametersForAnalytics = extractParametersForAnalyticsUseCase,
extractParametersForCrashReport = extractCrashKeysUseCase,
addAuthorizationEvent = addAuthorizationEventUseCase,
isUserSignedIn = isUserSignedInUseCase,
configManager = configManager,
startBackgroundSync = startBackgroundSync,
syncOrchestrator = syncOrchestrator,
updateDatabaseCountsInCurrentSession = updateSessionScopePayloadUseCase,
updateProjectInCurrentSession = updateProjectStateUseCase,
updateStoredUserId = updateStoredUserIdUseCase,
realmToRoomMigrationScheduler = realmToRoomMigrationScheduler,
ensureActionFieldsTokenizedUseCase = ensureActionFieldsTokenizedUseCase,
)

coEvery { ensureActionFieldsTokenizedUseCase.invoke(any()) } answers { firstArg() }
}

@Test
Expand Down Expand Up @@ -337,6 +344,7 @@ internal class LoginCheckViewModelTest {
addAuthorizationEventUseCase.invoke(any(), eq(true))
extractCrashKeysUseCase.invoke(any())
startBackgroundSync.invoke()
ensureActionFieldsTokenizedUseCase.invoke(any())
}

viewModel.proceedWithAction
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.simprints.feature.logincheck.usecases

import com.google.common.truth.Truth.*
import com.simprints.core.domain.tokenization.asTokenizableEncrypted
import com.simprints.core.domain.tokenization.asTokenizableRaw
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.config.sync.ConfigManager
import com.simprints.infra.orchestration.data.ActionRequest
import io.mockk.*
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test

class EnsureActionFieldsTokenisedUseCaseTest {
@MockK
private lateinit var configManager: ConfigManager

@MockK
private lateinit var tokenizationProcessor: TokenizationProcessor

@MockK
private lateinit var project: Project

private lateinit var useCase: EnsureActionFieldsTokenizedUseCase

@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
useCase = EnsureActionFieldsTokenizedUseCase(configManager, tokenizationProcessor)

every { tokenizationProcessor.tokenizeIfNecessary(any(), any(), any()) } returns "value".asTokenizableEncrypted()
}

@Test
fun `when project is null, return original action`() = runTest {
coEvery { configManager.getProject() } returns null

val expected = mockk<ActionRequest>()
val result = useCase(expected)

assertThat(result).isSameInstanceAs(expected)
}

@Test
fun `when invoking with EnrolActionRequest, attempt tokenizing fields`() = runTest {
coEvery { configManager.getProject() } returns project

val action = mockk<ActionRequest.EnrolActionRequest> {
every { userId } returns "user".asTokenizableRaw()
every { moduleId } returns "module".asTokenizableRaw()
}
every { action.copy(userId = any(), moduleId = any()) } returns action
val result = useCase(action)

assertThat(result).isInstanceOf(ActionRequest.EnrolActionRequest::class.java)
verify {
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.ModuleId, any())
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.AttendantId, any())
}
}

@Test
fun `when invoking with VerifyActionRequest, attempt tokenizing fields`() = runTest {
coEvery { configManager.getProject() } returns project

val action = mockk<ActionRequest.VerifyActionRequest> {
every { userId } returns "user".asTokenizableRaw()
every { moduleId } returns "module".asTokenizableRaw()
}
every { action.copy(userId = any(), moduleId = any()) } returns action
val result = useCase(action)

assertThat(result).isInstanceOf(ActionRequest.VerifyActionRequest::class.java)
verify {
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.ModuleId, any())
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.AttendantId, any())
}
}

@Test
fun `when invoking with IdentifyActionRequest, attempt tokenizing fields`() = runTest {
coEvery { configManager.getProject() } returns project

val action = mockk<ActionRequest.IdentifyActionRequest> {
every { userId } returns "user".asTokenizableRaw()
every { moduleId } returns "module".asTokenizableRaw()
}
every { action.copy(userId = any(), moduleId = any()) } returns action
val result = useCase(action)

assertThat(result).isInstanceOf(ActionRequest.IdentifyActionRequest::class.java)
verify {
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.ModuleId, any())
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.AttendantId, any())
}
}

@Test
fun `when invoking with ConfirmIdentityActionRequest, attempt tokenizing fields`() = runTest {
coEvery { configManager.getProject() } returns project

val action = mockk<ActionRequest.ConfirmIdentityActionRequest> {
every { userId } returns "user".asTokenizableRaw()
}
every { action.copy(userId = any()) } returns action
val result = useCase(action)

assertThat(result).isInstanceOf(ActionRequest.ConfirmIdentityActionRequest::class.java)
verify {
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.AttendantId, any())
}
}

@Test
fun `when invoking with EnrolLastBiometricActionRequest, attempt tokenizing fields`() = runTest {
coEvery { configManager.getProject() } returns project

val action = mockk<ActionRequest.EnrolLastBiometricActionRequest> {
every { userId } returns "user".asTokenizableRaw()
every { moduleId } returns "module".asTokenizableRaw()
}
every { action.copy(userId = any(), moduleId = any()) } returns action
val result = useCase(action)

assertThat(result).isInstanceOf(ActionRequest.EnrolLastBiometricActionRequest::class.java)
verify {
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.ModuleId, any())
tokenizationProcessor.tokenizeIfNecessary(any(), TokenKeyType.AttendantId, any())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.simprints.infra.config.store.models.PrivacyNoticeResult
import com.simprints.infra.config.store.models.Project
import com.simprints.infra.config.store.models.ProjectConfiguration
import com.simprints.infra.config.store.models.ProjectWithConfig
import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -19,7 +18,6 @@ import javax.inject.Singleton
@Singleton
class ConfigManager @Inject constructor(
Comment thread
BurningAXE marked this conversation as resolved.
private val configRepository: ConfigRepository,
private val enrolmentRecordRepository: EnrolmentRecordRepository,
private val configSyncCache: ConfigSyncCache,
private val authStore: AuthStore,
) {
Expand All @@ -29,7 +27,6 @@ class ConfigManager @Inject constructor(
isProjectRefreshingFlow.tryEmit(true)
try {
return configRepository.refreshProject(projectId).also {
enrolmentRecordRepository.tokenizeExistingRecords(it.project)
configSyncCache.saveUpdateTime()
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.simprints.infra.config.store.models.DeviceConfiguration
import com.simprints.infra.config.store.models.Project
import com.simprints.infra.config.store.models.ProjectConfiguration
import com.simprints.infra.config.store.models.ProjectWithConfig
import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository
import io.mockk.*
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -32,9 +31,6 @@ class ConfigManagerTest {
@MockK
private lateinit var configRepository: ConfigRepository

@MockK
private lateinit var enrolmentRecordRepository: EnrolmentRecordRepository

@MockK
private lateinit var configSyncCache: ConfigSyncCache

Expand All @@ -58,7 +54,6 @@ class ConfigManagerTest {
MockKAnnotations.init(this, relaxed = true)
configManager = ConfigManager(
configRepository = configRepository,
enrolmentRecordRepository = enrolmentRecordRepository,
configSyncCache = configSyncCache,
authStore = authStore,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.simprints.infra.enrolment.records.repository

import androidx.core.content.edit
import com.simprints.core.DispatcherIO
import com.simprints.core.domain.tokenization.TokenizableString
import com.simprints.infra.config.store.models.Project
import com.simprints.infra.config.store.models.TokenKeyType
import com.simprints.infra.config.store.tokenization.TokenizationProcessor
Expand Down Expand Up @@ -63,23 +62,23 @@ internal class EnrolmentRecordRepositoryImpl @Inject constructor(
override suspend fun tokenizeExistingRecords(project: Project) {
try {
val query = EnrolmentRecordQuery(projectId = project.id, hasUntokenizedFields = true)
val tokenizedSubjectsCreateAction = selectEnrolmentRecordLocalDataSource()
val tokenizedRecordsCreateAction = selectEnrolmentRecordLocalDataSource()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be untokenizedRecordsCreateAction?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the end of the call chain the final value is indeed tokenized.

.load(query)
.mapNotNull { subject ->
if (subject.projectId != project.id) return@mapNotNull null
val moduleId = tokenizeIfNecessary(
value = subject.moduleId,
.mapNotNull { record ->
if (record.projectId != project.id) return@mapNotNull null
val moduleId = tokenizationProcessor.tokenizeIfNecessary(
tokenizableString = record.moduleId,
tokenKeyType = TokenKeyType.ModuleId,
project = project,
)
val attendantId = tokenizeIfNecessary(
value = subject.attendantId,
val attendantId = tokenizationProcessor.tokenizeIfNecessary(
tokenizableString = record.attendantId,
tokenKeyType = TokenKeyType.AttendantId,
project = project,
)
return@mapNotNull subject.copy(moduleId = moduleId, attendantId = attendantId)
record.copy(moduleId = moduleId, attendantId = attendantId)
}.map(EnrolmentRecordAction::Creation)
selectEnrolmentRecordLocalDataSource().performActions(tokenizedSubjectsCreateAction, project)
selectEnrolmentRecordLocalDataSource().performActions(tokenizedRecordsCreateAction, project)
} catch (e: Exception) {
when (e) {
is RealmUninitialisedException -> Unit
Expand All @@ -90,20 +89,6 @@ internal class EnrolmentRecordRepositoryImpl @Inject constructor(
}
}

private fun tokenizeIfNecessary(
value: TokenizableString,
tokenKeyType: TokenKeyType,
project: Project,
) = when (value) {
is TokenizableString.Tokenized -> value

is TokenizableString.Raw -> tokenizationProcessor.encrypt(
decrypted = value,
tokenKeyType = tokenKeyType,
project = project,
)
}

override suspend fun count(
query: EnrolmentRecordQuery,
dataSource: BiometricDataSource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,15 @@ class EnrolmentRecordRepositoryImplTest {
every { project.id } returns projectId
coEvery { localDataSource.load(any()) } returns listOf(enrolmentRecord)
every {
tokenizationProcessor.encrypt(
decrypted = attendantIdRaw,
tokenizationProcessor.tokenizeIfNecessary(
tokenizableString = attendantIdRaw,
tokenKeyType = TokenKeyType.AttendantId,
project = project,
)
} returns attendantIdTokenized
every {
tokenizationProcessor.encrypt(
decrypted = moduleIdRaw,
tokenizationProcessor.tokenizeIfNecessary(
tokenizableString = moduleIdRaw,
tokenKeyType = TokenKeyType.ModuleId,
project = project,
)
Expand Down
Loading