diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt index b359d034aa..7144b03035 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/shared/TokenAuthenticator.kt @@ -222,7 +222,7 @@ constructor( accounts.find { it.name == username && it.type == authConfiguration.accountType } val currentAccount = - secureSharedPreference.retrieveCredentials()?.let { cred -> + secureSharedPreference.retrieveSessionCredentials()?.let { cred -> accounts.find { it.name == cred.username && it.type == authConfiguration.accountType } } if (currentAccount != null) { @@ -231,8 +231,6 @@ constructor( val currentUserRoles = getRolesList(currentAccessToken) val secondUserRoles = getRolesList(oAuthResponse.accessToken) - // todo: verify requirements (with pm/tpm) on comparing match for user permission roles, - // also which roles to compare // todo: optimise val allMatching = currentUserRoles.all { secondUserRoles.contains(it) } if (!allMatching) throw IllegalAccessException("Unauthorized") @@ -247,7 +245,10 @@ constructor( setAuthToken(newAccount, AUTH_TOKEN_TYPE, oAuthResponse.accessToken) } // Save credentials - secureSharedPreference.saveCredentials(username, password) + secureSharedPreference.apply { + saveMultiCredentials(username, password) + saveSessionUsername(username) + } } } @@ -272,18 +273,16 @@ constructor( } fun validateSavedLoginCredentials(username: String, enteredPassword: CharArray): Boolean { - val credentials = secureSharedPreference.retrieveCredentials() - return if (username.equals(credentials?.username, ignoreCase = true)) { - val generatedHash = - enteredPassword.toPasswordHash(Base64.getDecoder().decode(credentials!!.salt)) - generatedHash == credentials.passwordHash - } else { - false - } + val usernameCredential = secureSharedPreference.retrieveCredentials(username) + return if (usernameCredential != null){ + val usernameGeneratedHash = enteredPassword.toPasswordHash(Base64.getDecoder().decode( + usernameCredential.salt)) + usernameGeneratedHash == usernameCredential.passwordHash + } else false } fun findAccount(): Account? { - val credentials = secureSharedPreference.retrieveCredentials() + val credentials = secureSharedPreference.retrieveSessionCredentials() return accountManager.getAccountsByType(authConfiguration.accountType).find { it.name == credentials?.username } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SecureSharedPreference.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SecureSharedPreference.kt index 73658b4fdf..b224f32a28 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SecureSharedPreference.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SecureSharedPreference.kt @@ -44,7 +44,7 @@ class SecureSharedPreference @Inject constructor(@ApplicationContext val context private fun getMasterKey() = MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build() - fun saveCredentials(username: String, password: CharArray) { + @Deprecated("") private fun saveCredentials(username: String, password: CharArray) { val randomSaltBytes = get256RandomBytes() secureSharedPreferences.edit { @@ -61,17 +61,70 @@ class SecureSharedPreference @Inject constructor(@ApplicationContext val context clearPasswordInMemory(password) } + fun saveMultiCredentials(username: String, password: CharArray){ + val randomSaltBytes = get256RandomBytes() + + secureSharedPreferences.apply { + + edit { + putString( + "${username}_${SharedPreferenceKey.LOGIN_CREDENTIAL_KEY.name}", + AuthCredentials( + username = username, + salt = Base64.getEncoder().encodeToString(randomSaltBytes), + passwordHash = password.toPasswordHash(randomSaltBytes), + ) + .encodeJson(), + ) + + putStringSet(SharedPreferenceKey.LOGIN_USERS.name, retrieveLoggedInUsernames() + username) + } + } + + saveCredentials(username, password) + + clearPasswordInMemory(password) + } + + fun saveSessionUsername(username: String) = secureSharedPreferences.edit { + putString(SharedPreferenceKey.LOGIN_SESSION_USER.name, username) + } + + fun retrieveLoggedInUsernames(): Set = secureSharedPreferences.getStringSet(SharedPreferenceKey.LOGIN_USERS.name, emptySet())!! + + + @Deprecated("") fun deleteCredentials() = secureSharedPreferences.edit { remove(SharedPreferenceKey.LOGIN_CREDENTIAL_KEY.name) } - fun retrieveSessionUsername() = retrieveCredentials()?.username + fun deleteCredentials(username: String) = secureSharedPreferences.apply { + val users = retrieveLoggedInUsernames() + edit { + remove("${username}_${SharedPreferenceKey.LOGIN_CREDENTIAL_KEY.name}") + putStringSet(SharedPreferenceKey.LOGIN_USERS.name, users - username) + } + } - fun retrieveCredentials(): AuthCredentials? = +// fun retrieveSessionUsername() = retrieveCredentials()?.username + + fun retrieveSessionUsername() = secureSharedPreferences.getString(SharedPreferenceKey.LOGIN_SESSION_USER.name, null) + + fun retrieveSessionUsername(username: String) = retrieveCredentials(username)?.username + + @Deprecated("") fun retrieveCredentials(): AuthCredentials? = secureSharedPreferences .getString(SharedPreferenceKey.LOGIN_CREDENTIAL_KEY.name, null) ?.decodeJson() - fun saveSessionPin(pin: CharArray) { + fun retrieveSessionCredentials(): AuthCredentials? = + retrieveSessionUsername()?.let { retrieveCredentials(it) } + + fun retrieveCredentials(username: String): AuthCredentials? = + secureSharedPreferences + .getString("${username}_${SharedPreferenceKey.LOGIN_CREDENTIAL_KEY.name}", null) + ?.decodeJson() + + @Deprecated("") private fun saveSessionPin(pin: CharArray) { val randomSaltBytes = get256RandomBytes() secureSharedPreferences.edit { putString( @@ -82,17 +135,40 @@ class SecureSharedPreference @Inject constructor(@ApplicationContext val context } } + fun saveSessionPin(username: String, pin: CharArray){ + val randomSaltBytes = get256RandomBytes() + val hash = pin.toPasswordHash(randomSaltBytes) + + secureSharedPreferences.edit { + putString("${username}_${SharedPreferenceKey.LOGIN_PIN_SALT.name}", Base64.getEncoder().encodeToString(randomSaltBytes)) + + putString("${username}_${SharedPreferenceKey.LOGIN_PIN_KEY.name}", hash) + } + + saveSessionPin(pin) + } + + fun retrieveSessionUserSalt(username: String) = secureSharedPreferences.getString("${username}_${SharedPreferenceKey.LOGIN_PIN_SALT.name}", null) + + fun retrieveSessionUserPin(username: String) = secureSharedPreferences.getString("${username}_${SharedPreferenceKey.LOGIN_PIN_KEY.name}", null) + @VisibleForTesting fun get256RandomBytes() = 256.getRandomBytesOfSize() - fun retrievePinSalt() = + @Deprecated("") fun retrievePinSalt() = secureSharedPreferences.getString(SharedPreferenceKey.LOGIN_PIN_SALT.name, null) - fun retrieveSessionPin() = + @Deprecated("") fun retrieveSessionPin() = secureSharedPreferences.getString(SharedPreferenceKey.LOGIN_PIN_KEY.name, null) - fun deleteSessionPin() = + @Deprecated("") fun deleteSessionPin() = secureSharedPreferences.edit { remove(SharedPreferenceKey.LOGIN_PIN_KEY.name) } + fun deleteSessionPin(username: String) = + secureSharedPreferences.edit { + remove("${username}_${SharedPreferenceKey.LOGIN_PIN_KEY.name}") + remove("${username}_${SharedPreferenceKey.LOGIN_PIN_SALT.name}") + } + /** This method resets/clears all existing values in the shared preferences synchronously */ fun resetSharedPrefs() = secureSharedPreferences.edit { clear() } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt index a138588f39..d43415a6a9 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/SharedPreferenceKey.kt @@ -28,6 +28,8 @@ enum class SharedPreferenceKey { LOGIN_CREDENTIAL_KEY, LOGIN_PIN_KEY, LOGIN_PIN_SALT, + LOGIN_USERS, + LOGIN_SESSION_USER, LAST_OFFSET, USER_INFO, CARE_TEAM, diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pin/PinViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pin/PinViewModel.kt index 90dfb155a0..6fac385ed7 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pin/PinViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pin/PinViewModel.kt @@ -102,6 +102,13 @@ constructor( } } + fun onPinVerified(username:String, validPin: Boolean){ + viewModelScope.launch(dispatcherProvider.io()) { + secureSharedPreference.saveSessionUsername(username) + onPinVerified(validPin) + } + } + fun onShowPinError(showError: Boolean) { pinUiState.value = pinUiState.value.copy(showProgressBar = false) _showError.postValue(showError) @@ -109,8 +116,10 @@ constructor( fun onSetPin(newPin: CharArray) { viewModelScope.launch(dispatcherProvider.io()) { + val username = secureSharedPreference.retrieveSessionUsername() + pinUiState.value = pinUiState.value.copy(showProgressBar = true) - secureSharedPreference.saveSessionPin(newPin) + secureSharedPreference.saveSessionPin(username!!, newPin) pinUiState.value = pinUiState.value.copy(showProgressBar = false) } @@ -150,4 +159,21 @@ constructor( onPinVerified(validPin) } } + + fun pinLogin(username: String, enteredPin: CharArray, callback: (Boolean) -> Unit) { + viewModelScope.launch(dispatcherProvider.io()) { + pinUiState.value = pinUiState.value.copy(showProgressBar = true) + + val storedPinHash = secureSharedPreference.retrieveSessionUserPin(username) + val salt = secureSharedPreference.retrieveSessionUserSalt(username) + val generatedHash = enteredPin.toPasswordHash(Base64.getDecoder().decode(salt)) + val validPin = generatedHash == storedPinHash + + if (validPin) clearPasswordInMemory(enteredPin) + + callback.invoke(validPin) + + onPinVerified(validPin) + } + } }