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