Add storage for invalid lock attempts (#637)

This commit is contained in:
Brian Yencho 2024-01-16 11:10:26 -06:00 committed by Álison Fernandes
parent c428a57ca8
commit 6220670ce3
4 changed files with 109 additions and 0 deletions

View file

@ -57,6 +57,19 @@ interface AuthDiskSource {
lastActiveTimeMillis: Long?,
)
/**
* Retrieves the number of consecutive invalid lock attempts for the given [userId].
*/
fun getInvalidUnlockAttempts(userId: String): Int?
/**
* Stores the number of consecutive invalid lock attempts for the given [userId].
*/
fun storeInvalidUnlockAttempts(
userId: String,
invalidUnlockAttempts: Int?,
)
/**
* Retrieves a user key using a [userId].
*/

View file

@ -20,6 +20,7 @@ private const val UNIQUE_APP_ID_KEY = "$BASE_KEY:appId"
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail"
private const val STATE_KEY = "$BASE_KEY:state"
private const val LAST_ACTIVE_TIME_KEY = "$BASE_KEY:lastActiveTime"
private const val INVALID_UNLOCK_ATTEMPTS_KEY = "$BASE_KEY:invalidUnlockAttempts"
private const val MASTER_KEY_ENCRYPTION_USER_KEY = "$BASE_KEY:masterKeyEncryptedUserKey"
private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "$BASE_KEY:encPrivateKey"
private const val PIN_PROTECTED_USER_KEY_KEY = "$BASE_KEY:pinKeyEncryptedUserKey"
@ -74,6 +75,7 @@ class AuthDiskSourceImpl(
override fun clearData(userId: String) {
storeLastActiveTimeMillis(userId = userId, lastActiveTimeMillis = null)
storeInvalidUnlockAttempts(userId = userId, invalidUnlockAttempts = null)
storeUserKey(userId = userId, userKey = null)
storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null)
storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null)
@ -96,6 +98,19 @@ class AuthDiskSourceImpl(
)
}
override fun getInvalidUnlockAttempts(userId: String): Int? =
getInt(key = "${INVALID_UNLOCK_ATTEMPTS_KEY}_$userId")
override fun storeInvalidUnlockAttempts(
userId: String,
invalidUnlockAttempts: Int?,
) {
putInt(
key = "${INVALID_UNLOCK_ATTEMPTS_KEY}_$userId",
value = invalidUnlockAttempts,
)
}
override fun getUserKey(userId: String): String? =
getString(key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId")

View file

@ -127,6 +127,10 @@ class AuthDiskSourceTest {
userId = userId,
lastActiveTimeMillis = 123456789L,
)
authDiskSource.storeInvalidUnlockAttempts(
userId = userId,
invalidUnlockAttempts = 1,
)
authDiskSource.storeUserKey(userId = userId, userKey = "userKey")
authDiskSource.storeUserAutoUnlockKey(
userId = userId,
@ -145,6 +149,7 @@ class AuthDiskSourceTest {
authDiskSource.clearData(userId = userId)
assertNull(authDiskSource.getLastActiveTimeMillis(userId = userId))
assertNull(authDiskSource.getInvalidUnlockAttempts(userId = userId))
assertNull(authDiskSource.getUserKey(userId = userId))
assertNull(authDiskSource.getUserAutoUnlockKey(userId = userId))
assertNull(authDiskSource.getPrivateKey(userId = userId))
@ -209,6 +214,63 @@ class AuthDiskSourceTest {
assertFalse(fakeSharedPreferences.contains(lastActiveTimeKey))
}
@Test
fun `getInvalidUnlockAttempts should pull from SharedPreferences`() {
val lastActiveTimeBaseKey = "bwPreferencesStorage:invalidUnlockAttempts"
val mockUserId = "mockUserId"
val mockInvalidUnlockAttempts = 4
fakeSharedPreferences
.edit {
putInt(
"${lastActiveTimeBaseKey}_$mockUserId",
mockInvalidUnlockAttempts,
)
}
val actual = authDiskSource.getInvalidUnlockAttempts(userId = mockUserId)
assertEquals(
mockInvalidUnlockAttempts,
actual,
)
}
@Test
fun `storeInvalidUnlockAttempts for non-null values should update SharedPreferences`() {
val invalidUnlockAttemptsBaseKey = "bwPreferencesStorage:invalidUnlockAttempts"
val mockUserId = "mockUserId"
val mockInvalidUnlockAttempts = 4
authDiskSource.storeInvalidUnlockAttempts(
userId = mockUserId,
invalidUnlockAttempts = mockInvalidUnlockAttempts,
)
val actual = fakeSharedPreferences
.getInt(
"${invalidUnlockAttemptsBaseKey}_$mockUserId",
0,
)
assertEquals(
mockInvalidUnlockAttempts,
actual,
)
}
@Test
fun `storeInvalidUnlockAttempts for null values should clear SharedPreferences`() {
val invalidUnlockAttemptsBaseKey = "bwPreferencesStorage:invalidUnlockAttempts"
val mockUserId = "mockUserId"
val mockInvalidUnlockAttempts = 4
val invalidUnlockAttemptsKey = "${invalidUnlockAttemptsBaseKey}_$mockUserId"
fakeSharedPreferences
.edit {
putInt(invalidUnlockAttemptsKey, mockInvalidUnlockAttempts)
}
assertTrue(fakeSharedPreferences.contains(invalidUnlockAttemptsKey))
authDiskSource.storeInvalidUnlockAttempts(
userId = mockUserId,
invalidUnlockAttempts = null,
)
assertFalse(fakeSharedPreferences.contains(invalidUnlockAttemptsKey))
}
@Test
fun `getUserKey should pull from SharedPreferences`() {
val userKeyBaseKey = "bwPreferencesStorage:masterKeyEncryptedUserKey"

View file

@ -20,6 +20,7 @@ class FakeAuthDiskSource : AuthDiskSource {
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
private val storedLastActiveTimeMillis = mutableMapOf<String, Long?>()
private val storedInvalidUnlockAttempts = mutableMapOf<String, Int?>()
private val storedUserKeys = mutableMapOf<String, String?>()
private val storedPrivateKeys = mutableMapOf<String, String?>()
private val storedUserAutoUnlockKeys = mutableMapOf<String, String?>()
@ -40,6 +41,7 @@ class FakeAuthDiskSource : AuthDiskSource {
override fun clearData(userId: String) {
storedLastActiveTimeMillis.remove(userId)
storedInvalidUnlockAttempts.remove(userId)
storedUserKeys.remove(userId)
storedPrivateKeys.remove(userId)
storedUserAutoUnlockKeys.remove(userId)
@ -61,6 +63,16 @@ class FakeAuthDiskSource : AuthDiskSource {
storedLastActiveTimeMillis[userId] = lastActiveTimeMillis
}
override fun getInvalidUnlockAttempts(userId: String): Int? =
storedInvalidUnlockAttempts[userId]
override fun storeInvalidUnlockAttempts(
userId: String,
invalidUnlockAttempts: Int?,
) {
storedInvalidUnlockAttempts[userId] = invalidUnlockAttempts
}
override fun getUserKey(userId: String): String? = storedUserKeys[userId]
override fun storeUserKey(userId: String, userKey: String?) {
@ -140,6 +152,13 @@ class FakeAuthDiskSource : AuthDiskSource {
assertEquals(lastActiveTimeMillis, storedLastActiveTimeMillis[userId])
}
/**
* Assert that the [invalidUnlockAttempts] was stored successfully using the [userId].
*/
fun assertInvalidUnlockAttempts(userId: String, invalidUnlockAttempts: Int?) {
assertEquals(invalidUnlockAttempts, storedInvalidUnlockAttempts[userId])
}
/**
* Assert that the [userKey] was stored successfully using the [userId].
*/