Pin : move some classes around and introduce PinCodeManager

This commit is contained in:
ganfra
2023-10-17 18:28:39 +02:00
parent cec489793b
commit 65bbe72ecc
20 changed files with 188 additions and 33 deletions

View File

@@ -39,6 +39,7 @@ dependencies {
implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.cryptography.api)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.pin.impl.pin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.pin.impl.pin.storage.PinCodeStore
import io.element.android.libraries.cryptography.api.CryptoService
import io.element.android.libraries.cryptography.api.EncryptionResult
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
private const val SECRET_KEY_ALIAS = "SECRET_KEY_ALIAS_PIN_CODE"
@ContributesBinding(AppScope::class)
class DefaultPinCodeManager @Inject constructor(
private val cryptoService: CryptoService,
private val pinCodeStore: PinCodeStore,
) : PinCodeManager {
override suspend fun isPinCodeAvailable(): Boolean {
return pinCodeStore.hasPinCode()
}
override suspend fun createPinCode(pinCode: String) {
val secretKey = cryptoService.getOrCreateSecretKey(SECRET_KEY_ALIAS)
val encryptedPinCode = cryptoService.encrypt(secretKey, pinCode.toByteArray()).toBase64()
pinCodeStore.saveEncryptedPinCode(encryptedPinCode)
}
override suspend fun verifyPinCode(pinCode: String): Boolean {
val encryptedPinCode = pinCodeStore.getEncryptedCode() ?: return false
return try {
val secretKey = cryptoService.getOrCreateSecretKey(SECRET_KEY_ALIAS)
val decryptedPinCode = cryptoService.decrypt(secretKey, EncryptionResult.fromBase64(encryptedPinCode))
decryptedPinCode.contentEquals(pinCode.toByteArray())
} catch (failure: Throwable) {
false
}
}
override suspend fun deletePinCode() {
pinCodeStore.deleteEncryptedPinCode()
}
override suspend fun getRemainingPinCodeAttemptsNumber(): Int {
return pinCodeStore.getRemainingPinCodeAttemptsNumber()
}
override suspend fun onWrongPin(): Int {
return pinCodeStore.onWrongPin()
}
override suspend fun resetCounter() {
pinCodeStore.resetCounter()
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.pin.impl.pin
/**
* This interface is the main interface to manage the pin code.
* Implementation should take care of encrypting the pin code and storing it.
*/
interface PinCodeManager {
/**
* @return true if a pin code is available.
*/
suspend fun isPinCodeAvailable(): Boolean
/**
* creates a new encrypted pin code.
* @param pinCode the clear pin code to create
*/
suspend fun createPinCode(pinCode: String)
/**
* @return true if the pin code is correct
*/
suspend fun verifyPinCode(pinCode: String): Boolean
/**
* deletes the previously created pin code
*/
suspend fun deletePinCode()
/**
* @return the number of remaining attempts before the pin code is blocked
*/
suspend fun getRemainingPinCodeAttemptsNumber(): Int
/**
* @return the number of remaining attempts before the pin code is blocked
*/
suspend fun onWrongPin(): Int
/**
* Resets the counter of attempts for PIN code.
*/
suspend fun resetCounter()
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.storage
package io.element.android.features.pin.impl.pin.storage
/**
* Should be implemented by any class that provides access to the encrypted PIN code.
@@ -24,17 +24,17 @@ interface EncryptedPinCodeStorage {
/**
* Returns the encrypted PIN code.
*/
suspend fun getPinCode(): String?
suspend fun getEncryptedCode(): String?
/**
* Saves the encrypted PIN code to some persistable storage.
*/
suspend fun savePinCode(pinCode: String)
suspend fun saveEncryptedPinCode(pinCode: String)
/**
* Deletes the PIN code from some persistable storage.
*/
suspend fun deletePinCode()
suspend fun deleteEncryptedPinCode()
/**
* Returns whether the PIN code is stored or not.

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.storage
package io.element.android.features.pin.impl.pin.storage
interface PinCodeStore : EncryptedPinCodeStorage {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.storage
package io.element.android.features.pin.impl.pin.storage
import android.content.SharedPreferences
import androidx.core.content.edit
@@ -26,6 +26,10 @@ import kotlinx.coroutines.withContext
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
private const val ENCODED_PIN_CODE_KEY = "ENCODED_PIN_CODE_KEY"
private const val REMAINING_PIN_CODE_ATTEMPTS_KEY = "REMAINING_PIN_CODE_ATTEMPTS_KEY"
private const val MAX_PIN_CODE_ATTEMPTS_NUMBER_BEFORE_LOGOUT = 3
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class SharedPreferencesPinCodeStore @Inject constructor(
@@ -35,11 +39,11 @@ class SharedPreferencesPinCodeStore @Inject constructor(
private val listeners = CopyOnWriteArrayList<PinCodeStore.Listener>()
override suspend fun getPinCode(): String? = withContext(dispatchers.io) {
override suspend fun getEncryptedCode(): String? = withContext(dispatchers.io) {
sharedPreferences.getString(ENCODED_PIN_CODE_KEY, null)
}
override suspend fun savePinCode(pinCode: String) = withContext(dispatchers.io) {
override suspend fun saveEncryptedPinCode(pinCode: String) = withContext(dispatchers.io) {
sharedPreferences.edit {
putString(ENCODED_PIN_CODE_KEY, pinCode)
}
@@ -48,7 +52,7 @@ class SharedPreferencesPinCodeStore @Inject constructor(
}
}
override suspend fun deletePinCode() = withContext(dispatchers.io) {
override suspend fun deleteEncryptedPinCode() = withContext(dispatchers.io) {
// Also reset the counters
resetCounter()
sharedPreferences.edit {
@@ -78,7 +82,6 @@ class SharedPreferencesPinCodeStore @Inject constructor(
override suspend fun resetCounter() = withContext(dispatchers.io) {
sharedPreferences.edit {
remove(REMAINING_PIN_CODE_ATTEMPTS_KEY)
remove(REMAINING_BIOMETRICS_ATTEMPTS_KEY)
}
}
@@ -89,12 +92,4 @@ class SharedPreferencesPinCodeStore @Inject constructor(
override fun removeListener(listener: PinCodeStore.Listener) {
listeners.remove(listener)
}
companion object {
private const val ENCODED_PIN_CODE_KEY = "ENCODED_PIN_CODE_KEY"
private const val REMAINING_PIN_CODE_ATTEMPTS_KEY = "REMAINING_PIN_CODE_ATTEMPTS_KEY"
private const val REMAINING_BIOMETRICS_ATTEMPTS_KEY = "REMAINING_BIOMETRICS_ATTEMPTS_KEY"
private const val MAX_PIN_CODE_ATTEMPTS_NUMBER_BEFORE_LOGOUT = 3
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl
package io.element.android.features.pin.impl.presentation
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl
package io.element.android.features.pin.impl.presentation
import android.os.Parcelable
import androidx.compose.runtime.Composable
@@ -27,8 +27,8 @@ import com.bumble.appyx.navmodel.backstack.BackStack
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.pin.impl.auth.PinAuthenticationNode
import io.element.android.features.pin.impl.create.CreatePinNode
import io.element.android.features.pin.impl.presentation.auth.PinAuthenticationNode
import io.element.android.features.pin.impl.presentation.create.CreatePinNode
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.auth
package io.element.android.features.pin.impl.presentation.auth
sealed interface PinAuthenticationEvents {
data object Unlock : PinAuthenticationEvents

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.auth
package io.element.android.features.pin.impl.presentation.auth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.auth
package io.element.android.features.pin.impl.presentation.auth
import androidx.compose.runtime.Composable
import io.element.android.features.pin.api.PinStateService

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.auth
package io.element.android.features.pin.impl.presentation.auth
data class PinAuthenticationState(
val eventSink: (PinAuthenticationEvents) -> Unit

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.pin.impl.presentation.auth
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class PinAuthenticationStateProvider : PreviewParameterProvider<PinAuthenticationState> {
override val values: Sequence<PinAuthenticationState>
get() = sequenceOf(
aPinAuthenticationState(),
)
}
fun aPinAuthenticationState() = PinAuthenticationState(
eventSink = {}
)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.auth
package io.element.android.features.pin.impl.presentation.auth
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.create
package io.element.android.features.pin.impl.presentation.create
sealed interface CreatePinEvents {
object MyEvent : CreatePinEvents

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.create
package io.element.android.features.pin.impl.presentation.create
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.create
package io.element.android.features.pin.impl.presentation.create
import androidx.compose.runtime.Composable
import io.element.android.libraries.architecture.Presenter

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.create
package io.element.android.features.pin.impl.presentation.create
data class CreatePinState(
val eventSink: (CreatePinEvents) -> Unit

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.create
package io.element.android.features.pin.impl.presentation.create
import androidx.compose.ui.tooling.preview.PreviewParameterProvider

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.pin.impl.create
package io.element.android.features.pin.impl.presentation.create
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme