Skip to content

Commit

Permalink
Support login for multiple users
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Aug 11, 2023
1 parent f3f10d0 commit 9be6b93
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ data class ApplicationConfiguration(
val languages: List<String> = listOf("en"),
val useDarkTheme: Boolean = false,
val syncInterval: Long = 15,
val syncStrategies: List<String> = listOf(),
val syncStrategy: List<String> = listOf(),
val loginConfig: LoginConfig = LoginConfig(),
val deviceToDeviceSync: DeviceToDeviceSyncConfig? = null,
val snackBarTheme: SnackBarThemeConfig = SnackBarThemeConfig(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import javax.inject.Singleton
import kotlinx.serialization.SerializationException
import org.smartregister.fhircore.engine.util.extension.decodeJson
import org.smartregister.fhircore.engine.util.extension.encodeJson
import org.smartregister.model.practitioner.PractitionerDetails
import timber.log.Timber

@Singleton
Expand Down Expand Up @@ -93,7 +94,14 @@ constructor(@ApplicationContext val context: Context, val gson: Gson) {
/** Write any object by saving it as JSON */
inline fun <reified T> write(key: String, value: T?, encodeWithGson: Boolean = true) {
with(prefs.edit()) {
putString(key, if (encodeWithGson) gson.toJson(value) else value.encodeJson())
putString(
key,
(if (encodeWithGson) gson.toJson(value) else value.encodeJson()).also {
if (value is PractitionerDetails) {
Timber.e(it)
}
},
)
commit()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ enum class LoginErrorState {
INVALID_OFFLINE_STATE,
MULTI_USER_LOGIN_ATTEMPT,
UNKNOWN_HOST,
// TODO: Add login error state that will track when second user is not from 'syncStrategy' team
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.Bundle as FhirR4ModelBundle
import org.hl7.fhir.r4.model.ResourceType
import org.smartregister.fhircore.engine.auth.AuthCredentials
import org.smartregister.fhircore.engine.configuration.ConfigType
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.configuration.app.ApplicationConfiguration
Expand All @@ -60,6 +61,7 @@ import org.smartregister.model.location.LocationHierarchy
import org.smartregister.model.practitioner.PractitionerDetails
import retrofit2.HttpException
import timber.log.Timber
import java.security.AccessControlException

@HiltViewModel
class LoginViewModel
Expand Down Expand Up @@ -139,14 +141,19 @@ constructor(
onFetchPractitioner = { bundleResult, userInfo ->
if (bundleResult.isSuccess) {
val bundle = bundleResult.getOrDefault(FhirR4ModelBundle())
savePractitionerDetails(bundle, userInfo) {

savePractitionerDetails(
bundle,
userInfo
) { // TODO: Throw an error if practitioner details don't match sync strategy
_showProgressBar.postValue(false)
updateNavigateHome(true)
}
} else {
_showProgressBar.postValue(false)
Timber.e(bundleResult.exceptionOrNull())
Timber.e(bundleResult.getOrNull().valueToString())
// TODO: Post login error for second user when not matching sync strategy
_loginErrorState.postValue(LoginErrorState.ERROR_FETCHING_USER)
}
},
Expand Down Expand Up @@ -207,36 +214,38 @@ constructor(
key = SharedPreferenceKey.PRACTITIONER_DETAILS.name,
decodeWithGson = true,
)
if (tokenAuthenticator.sessionActive() && practitionerDetails != null) {
_showProgressBar.postValue(false)
updateNavigateHome(true)
} else {
// Prevent user from logging in with different credentials
val existingCredentials = secureSharedPreference.retrieveCredentials()
if (existingCredentials != null && !username.equals(existingCredentials.username, true)) {
// Get credentials for the current user
val currentCredentials = secureSharedPreference.retrieveCredentials()
if (currentCredentials != null
&& practitionerDetails != null
&& username.equals(currentCredentials.username, ignoreCase = true)
&& tokenAuthenticator.sessionActive()){
// Allow login access
_showProgressBar.postValue(false)
_loginErrorState.postValue(LoginErrorState.MULTI_USER_LOGIN_ATTEMPT)
} else {
tokenAuthenticator
.fetchAccessToken(username, password)
.onSuccess { fetchPractitioner(onFetchUserInfo, onFetchPractitioner) }
.onFailure {
_showProgressBar.postValue(false)
var errorState = LoginErrorState.ERROR_FETCHING_USER
updateNavigateHome(true)
return
}

if (it is HttpException) {
when (it.code()) {
401 -> errorState = LoginErrorState.INVALID_CREDENTIALS
}
} else if (it is UnknownHostException) {
errorState = LoginErrorState.UNKNOWN_HOST
}
tokenAuthenticator
.fetchAccessToken(username, password)
.onSuccess {
fetchPractitioner(onFetchUserInfo, onFetchPractitioner)
}
.onFailure {
_showProgressBar.postValue(false)
var errorState = LoginErrorState.ERROR_FETCHING_USER

_loginErrorState.postValue(errorState)
Timber.e(it)
if (it is HttpException) {
when (it.code()) {
401 -> errorState = LoginErrorState.INVALID_CREDENTIALS
}
} else if (it is UnknownHostException) {
errorState = LoginErrorState.UNKNOWN_HOST
}

_loginErrorState.postValue(errorState)
Timber.e(it)
}
}
}

suspend fun fetchPractitioner(
Expand Down Expand Up @@ -284,6 +293,23 @@ constructor(
}
}

fun checkAccessAllowed(practitionerDetails: PractitionerDetails): Boolean{
val appSyncStrategy = applicationConfiguration.syncStrategy
val existingPractitionerDetails = sharedPreferences.read<PractitionerDetails>(
key = SharedPreferenceKey.PRACTITIONER_DETAILS.name,
decodeWithGson = true,
)
val existingAppCredentials = secureSharedPreference.retrieveCredentials()
if (existingAppCredentials == null || existingAppCredentials.username.equals(username.value!!.trim(), ignoreCase = true)) return true
else if (appSyncStrategy.isEmpty()) return false
else{
// TODO: Compare data in [existingPractitionerDetails] and [practitionerDetails]
}


return false
}

fun savePractitionerDetails(
bundle: FhirR4ModelBundle,
userInfo: UserInfo?,
Expand All @@ -293,6 +319,9 @@ constructor(
viewModelScope.launch {
bundle.entry.forEach { entry ->
val practitionerDetails = entry.resource as PractitionerDetails
if (!checkAccessAllowed(practitionerDetails)){
throw AccessControlException("access not allowed")
}

val careTeams = practitionerDetails.fhirPractitionerDetails?.careTeams ?: listOf()
val organizations = practitionerDetails.fhirPractitionerDetails?.organizations ?: listOf()
Expand All @@ -303,6 +332,11 @@ constructor(
val locationHierarchies =
practitionerDetails.fhirPractitionerDetails?.locationHierarchyList ?: listOf()

// TODO: Get if another user logged in previously
// TODO: Get applicationConfiguration from configurationRegistry
// Todo: Check practitionerDetails matching with current user considering
// applicationConfiguration.syncStrategy
// Todo: If not matching throw error, else continue to save
val careTeamIds =
withContext(dispatcherProvider.io()) {
defaultRepository.createRemote(false, *careTeams.toTypedArray()).run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ constructor(
menuConfig.actions?.find { actionConfig ->
actionConfig.trigger == ActionTrigger.ON_COUNT
}
registerCountMap[countAction?.id ?: menuConfig.id] =
registerRepository.countRegisterData(menuConfig.id)
// registerCountMap[countAction?.id ?: menuConfig.id] =
// registerRepository.countRegisterData(menuConfig.id)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ internal class LoginViewModelTest : RobolectricTest() {
@Test
fun testSuccessfulOnlineLoginWithActiveSessionWithSavedPractitionerDetails() {
updateCredentials()
secureSharedPreference.saveCredentials(thisUsername, thisPassword.toCharArray())
sharedPreferencesHelper.write(
SharedPreferenceKey.PRACTITIONER_DETAILS.name,
PractitionerDetails(),
Expand Down

0 comments on commit 9be6b93

Please sign in to comment.