mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 10:48:47 +03:00
Add storage for invalid lock attempts (#637)
This commit is contained in:
parent
c428a57ca8
commit
6220670ce3
4 changed files with 109 additions and 0 deletions
|
@ -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].
|
||||
*/
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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].
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue