1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.authentication.data.repository 18 19 import com.android.internal.widget.LockPatternUtils 20 import com.android.internal.widget.LockPatternView 21 import com.android.internal.widget.LockscreenCredential 22 import com.android.keyguard.KeyguardSecurityModel.SecurityMode 23 import com.android.systemui.authentication.data.model.AuthenticationMethodModel 24 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate 25 import com.android.systemui.authentication.shared.model.AuthenticationResultModel 26 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel 27 import kotlinx.coroutines.flow.MutableStateFlow 28 import kotlinx.coroutines.flow.StateFlow 29 import kotlinx.coroutines.flow.asStateFlow 30 31 class FakeAuthenticationRepository( 32 private val currentTime: () -> Long, 33 ) : AuthenticationRepository { 34 35 private val _isAutoConfirmEnabled = MutableStateFlow(false) 36 override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow() 37 38 private val _isUnlocked = MutableStateFlow(false) 39 override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() 40 41 override val hintedPinLength: Int = HINTING_PIN_LENGTH 42 43 private val _isPatternVisible = MutableStateFlow(true) 44 override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow() 45 46 private val _throttling = MutableStateFlow(AuthenticationThrottlingModel()) 47 override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow() 48 49 private val _authenticationMethod = 50 MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD) 51 override val authenticationMethod: StateFlow<AuthenticationMethodModel> = 52 _authenticationMethod.asStateFlow() 53 54 private var isLockscreenEnabled = true 55 private var failedAttemptCount = 0 56 private var throttlingEndTimestamp = 0L 57 private var credentialOverride: List<Any>? = null 58 private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode() 59 60 override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { 61 return authenticationMethod.value 62 } 63 64 fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { 65 _authenticationMethod.value = authenticationMethod 66 securityMode = authenticationMethod.toSecurityMode() 67 } 68 69 fun overrideCredential(pin: List<Int>) { 70 credentialOverride = pin 71 } 72 73 override suspend fun isLockscreenEnabled(): Boolean { 74 return isLockscreenEnabled 75 } 76 77 override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { 78 failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1 79 _isUnlocked.value = isSuccessful 80 } 81 82 override suspend fun getPinLength(): Int { 83 return (credentialOverride ?: DEFAULT_PIN).size 84 } 85 86 override suspend fun getFailedAuthenticationAttemptCount(): Int { 87 return failedAttemptCount 88 } 89 90 override suspend fun getThrottlingEndTimestamp(): Long { 91 return throttlingEndTimestamp 92 } 93 94 override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) { 95 _throttling.value = throttlingModel 96 } 97 98 fun setUnlocked(isUnlocked: Boolean) { 99 _isUnlocked.value = isUnlocked 100 } 101 102 fun setAutoConfirmEnabled(isEnabled: Boolean) { 103 _isAutoConfirmEnabled.value = isEnabled 104 } 105 106 fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { 107 this.isLockscreenEnabled = isLockscreenEnabled 108 } 109 110 override suspend fun setThrottleDuration(durationMs: Int) { 111 throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 112 } 113 114 override suspend fun checkCredential( 115 credential: LockscreenCredential 116 ): AuthenticationResultModel { 117 val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode) 118 val isSuccessful = 119 when { 120 credential.type != getCurrentCredentialType(securityMode) -> false 121 credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN -> 122 credential.isPin && credential.matches(expectedCredential) 123 credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> 124 credential.isPassword && credential.matches(expectedCredential) 125 credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> 126 credential.isPattern && credential.matches(expectedCredential) 127 else -> error("Unexpected credential type ${credential.type}!") 128 } 129 130 return if ( 131 isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 132 ) { 133 AuthenticationResultModel( 134 isSuccessful = isSuccessful, 135 throttleDurationMs = 0, 136 ) 137 } else { 138 AuthenticationResultModel( 139 isSuccessful = false, 140 throttleDurationMs = THROTTLE_DURATION_MS, 141 ) 142 } 143 } 144 145 private fun getExpectedCredential(securityMode: SecurityMode): List<Any> { 146 return when (val credentialType = getCurrentCredentialType(securityMode)) { 147 LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN 148 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList() 149 LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells() 150 else -> error("Unsupported credential type $credentialType!") 151 } 152 } 153 154 companion object { 155 val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin 156 val PATTERN = 157 listOf( 158 AuthenticationPatternCoordinate(2, 0), 159 AuthenticationPatternCoordinate(2, 1), 160 AuthenticationPatternCoordinate(2, 2), 161 AuthenticationPatternCoordinate(1, 1), 162 AuthenticationPatternCoordinate(0, 0), 163 AuthenticationPatternCoordinate(0, 1), 164 AuthenticationPatternCoordinate(0, 2), 165 ) 166 const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 167 const val THROTTLE_DURATION_MS = 30000 168 const val HINTING_PIN_LENGTH = 6 169 val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } } 170 171 private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode { 172 return when (this) { 173 is AuthenticationMethodModel.Pin -> SecurityMode.PIN 174 is AuthenticationMethodModel.Password -> SecurityMode.Password 175 is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern 176 is AuthenticationMethodModel.None -> SecurityMode.None 177 } 178 } 179 180 @LockPatternUtils.CredentialType 181 private fun getCurrentCredentialType( 182 securityMode: SecurityMode, 183 ): Int { 184 return when (securityMode) { 185 SecurityMode.PIN, 186 SecurityMode.SimPin, 187 SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN 188 SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD 189 SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN 190 SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE 191 else -> error("Unsupported SecurityMode $securityMode!") 192 } 193 } 194 195 private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean { 196 @Suppress("UNCHECKED_CAST") 197 return when { 198 isPin -> 199 credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential 200 isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential 201 isPattern -> 202 credential.contentEquals( 203 LockPatternUtils.patternToByteArray( 204 expectedCredential as List<LockPatternView.Cell> 205 ) 206 ) 207 else -> error("Unsupported credential type $type!") 208 } 209 } 210 211 private fun List<AuthenticationPatternCoordinate>.toCells(): List<LockPatternView.Cell> { 212 return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) } 213 } 214 } 215 } 216