From a5f28360ec0933d9007704db7605d070699e8fb4 Mon Sep 17 00:00:00 2001 From: Taras Smakula <tarassmakula@gmail.com> Date: Tue, 12 Sep 2023 16:01:57 +0300 Subject: [PATCH] Add possibility to decrypt with raw key --- .../feature/log_in/EnterPassPhraseDialog.kt | 71 ----------- .../log_in/recovery/EnterPassPhraseDialog.kt | 80 ++++++++++++ .../recovery/EnterPassPhraseDialogListener.kt | 12 ++ .../log_in/stages/LogInStagesFragment.kt | 12 +- .../restore/RestoreBackupDataSource.kt | 11 ++ .../res/layout/dialog_enter_passphrase.xml | 115 ++++++++---------- auth/src/main/res/values/strings.xml | 5 +- 7 files changed, 164 insertions(+), 142 deletions(-) delete mode 100644 auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt create mode 100644 auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt create mode 100644 auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt deleted file mode 100644 index e7f5f0514..000000000 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.futo.circles.auth.feature.log_in - -import android.content.Context -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import androidx.appcompat.app.AppCompatDialog -import androidx.core.widget.doAfterTextChanged -import org.futo.circles.auth.databinding.DialogEnterPassphraseBinding -import org.futo.circles.core.extensions.getFilename -import org.futo.circles.core.extensions.getText -import org.futo.circles.core.extensions.gone -import org.futo.circles.core.extensions.visible - -interface EnterPassPhraseDialogListener { - fun onRestoreBackup(passphrase: String) - fun onRestoreBackup(uri: Uri) - fun onDoNotRestore() - fun onSelectFileClicked() -} - -class EnterPassPhraseDialog(context: Context, private val listener: EnterPassPhraseDialogListener) : - AppCompatDialog(context) { - - private val binding = DialogEnterPassphraseBinding.inflate(LayoutInflater.from(context)) - - private var selectedFileUri: Uri? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(binding.root) - setCancelable(false) - - with(binding) { - fileNameGroup.gone() - btnCancel.setOnClickListener { - listener.onDoNotRestore() - dismiss() - } - btnRestore.setOnClickListener { - selectedFileUri?.let { - listener.onRestoreBackup(it) - } ?: listener.onRestoreBackup(tilPassphrase.getText()) - dismiss() - } - tilPassphrase.editText?.doAfterTextChanged { handleRestoreButtonEnabled() } - ivRemoveFile.setOnClickListener { - selectedFileUri = null - binding.passPhraseGroup.visible() - binding.fileNameGroup.gone() - handleRestoreButtonEnabled() - } - btnUploadFile.setOnClickListener { - listener.onSelectFileClicked() - } - } - } - - fun selectFile(uri: Uri) { - selectedFileUri = uri - binding.tvFileName.text = uri.getFilename(context) ?: uri.toString() - binding.passPhraseGroup.gone() - binding.fileNameGroup.visible() - handleRestoreButtonEnabled() - } - - private fun handleRestoreButtonEnabled() { - binding.btnRestore.isEnabled = - binding.tilPassphrase.getText().isNotEmpty() || selectedFileUri != null - } -} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt new file mode 100644 index 000000000..25f0035fe --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt @@ -0,0 +1,80 @@ +package org.futo.circles.auth.feature.log_in.recovery + +import android.app.ActionBar +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.InsetDrawable +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import androidx.appcompat.app.AppCompatDialog +import androidx.core.widget.doAfterTextChanged +import org.futo.circles.auth.databinding.DialogEnterPassphraseBinding +import org.futo.circles.core.extensions.getText +import org.futo.circles.core.extensions.setIsVisible + +class EnterPassPhraseDialog(context: Context, private val listener: EnterPassPhraseDialogListener) : + AppCompatDialog(context) { + + private val binding = DialogEnterPassphraseBinding.inflate(LayoutInflater.from(context)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + setCancelable(false) + window?.apply { + setBackgroundDrawable(InsetDrawable(ColorDrawable(Color.TRANSPARENT), 20)) + setLayout(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT) + } + with(binding) { + recoveryTypeGroup.setOnCheckedChangeListener { _, checkedId -> + tilPassphrase.setIsVisible(checkedId == btnPassphrase.id) + vRecoveryKey.setIsVisible(checkedId == btnRecoveryKey.id) + handleRestoreButtonEnabled() + } + vRecoveryKey.setup( + onUploadFileListener = { listener.onSelectFileClicked() }, + onInputChanged = { handleRestoreButtonEnabled() } + ) + btnCancel.setOnClickListener { + listener.onDoNotRestore() + dismiss() + } + btnRestore.setOnClickListener { + restoreBackup() + dismiss() + } + tilPassphrase.editText?.doAfterTextChanged { handleRestoreButtonEnabled() } + } + } + + fun selectFile(uri: Uri) { + binding.vRecoveryKey.selectFile(uri) + } + + private fun restoreBackup() { + if (isPassphraseTypeSelected()) { + listener.onRestoreBackupWithPassphrase(binding.tilPassphrase.getText()) + } else { + val uri = binding.vRecoveryKey.getFileUri() + uri?.let { + listener.onRestoreBackup(it) + } ?: listener.onRestoreBackupWithRawKey(binding.vRecoveryKey.getRawKey() ?: "") + } + } + + private fun isPassphraseTypeSelected() = + binding.recoveryTypeGroup.checkedRadioButtonId == binding.btnPassphrase.id + + private fun handleRestoreButtonEnabled() { + binding.btnRestore.isEnabled = if (isPassphraseTypeSelected()) { + binding.tilPassphrase.getText().isNotEmpty() + } else { + val isRawKeyEntered = binding.vRecoveryKey.getRawKey()?.isNotEmpty() == true + val isFileKeyEntered = binding.vRecoveryKey.getFileUri() != null + + isRawKeyEntered || isFileKeyEntered + } + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt new file mode 100644 index 000000000..9c750f912 --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt @@ -0,0 +1,12 @@ +package org.futo.circles.auth.feature.log_in.recovery + +import android.net.Uri + +interface EnterPassPhraseDialogListener { + fun onRestoreBackupWithPassphrase(passphrase: String) + + fun onRestoreBackupWithRawKey(key: String) + fun onRestoreBackup(uri: Uri) + fun onDoNotRestore() + fun onSelectFileClicked() +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt index 91ceb1411..7e940c2af 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt @@ -15,8 +15,8 @@ import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.auth.R import org.futo.circles.auth.base.LoginStageNavigationEvent import org.futo.circles.auth.databinding.FragmentLoginStagesBinding -import org.futo.circles.auth.feature.log_in.EnterPassPhraseDialog -import org.futo.circles.auth.feature.log_in.EnterPassPhraseDialogListener +import org.futo.circles.auth.feature.log_in.recovery.EnterPassPhraseDialog +import org.futo.circles.auth.feature.log_in.recovery.EnterPassPhraseDialogListener import org.futo.circles.core.CirclesAppConfig import org.futo.circles.core.extensions.navigateSafe import org.futo.circles.core.extensions.observeData @@ -98,8 +98,12 @@ class LogInStagesFragment : Fragment(R.layout.fragment_login_stages), private fun showPassPhraseDialog() { enterPassPhraseDialog = EnterPassPhraseDialog(requireContext(), object : EnterPassPhraseDialogListener { - override fun onRestoreBackup(passphrase: String) { - viewModel.restoreBackup(passphrase) + override fun onRestoreBackupWithPassphrase(passphrase: String) { + viewModel.restoreBackupWithPassPhrase(passphrase) + } + + override fun onRestoreBackupWithRawKey(key: String) { + viewModel.restoreBackupWithRawKey(key) } override fun onRestoreBackup(uri: Uri) { diff --git a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt index 2ffc7bd5f..3d153f77e 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt @@ -121,6 +121,17 @@ class RestoreBackupDataSource @Inject constructor( loadingLiveData.postValue(LoadingData(isLoading = false)) } + suspend fun restoreKeysWithRawKey(rawKey: String) { + try { + val keyData = ssssDataSource.getRecoveryKeyFromFileKey(rawKey, progressObserver) + restoreKeysWithRecoveryKey(keyData) + } catch (e: Throwable) { + loadingLiveData.postValue(LoadingData(isLoading = false)) + throw e + } + loadingLiveData.postValue(LoadingData(isLoading = false)) + } + suspend fun restoreKeysWithRecoveryKey(uri: Uri) { try { val key = readRecoveryKeyFile(uri) diff --git a/auth/src/main/res/layout/dialog_enter_passphrase.xml b/auth/src/main/res/layout/dialog_enter_passphrase.xml index b11b3a0a7..cbb6fc808 100644 --- a/auth/src/main/res/layout/dialog_enter_passphrase.xml +++ b/auth/src/main/res/layout/dialog_enter_passphrase.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> @@ -23,7 +22,7 @@ android:gravity="center" android:lines="1" android:paddingVertical="8dp" - android:text="@string/enter_passphrase" + android:text="@string/recovery" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -51,6 +50,40 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/titleDivider" /> + <RadioGroup + android:id="@+id/recoveryTypeGroup" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginTop="16dp" + android:gravity="center" + android:orientation="horizontal" + android:paddingVertical="4dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/tvSubtitle"> + + <com.google.android.material.radiobutton.MaterialRadioButton + android:id="@+id/btnPassphrase" + style="@style/body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:checked="true" + android:padding="8dp" + android:text="@string/passphrase" /> + + <com.google.android.material.radiobutton.MaterialRadioButton + android:id="@+id/btnRecoveryKey" + style="@style/body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:padding="8dp" + android:text="@string/recovery_key" /> + + </RadioGroup> + <com.google.android.material.textfield.TextInputLayout android:id="@+id/tilPassphrase" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" @@ -62,7 +95,7 @@ android:hint="@string/passphrase" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tvSubtitle" + app:layout_constraintTop_toBottomOf="@id/recoveryTypeGroup" app:passwordToggleEnabled="true"> <com.google.android.material.textfield.TextInputEditText @@ -74,60 +107,24 @@ </com.google.android.material.textfield.TextInputLayout> - <TextView - android:id="@+id/tvOr" - style="@style/body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="@string/or" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tilPassphrase" /> - - <TextView - android:id="@+id/tvFileName" - style="@style/body" - android:layout_width="wrap_content" + <org.futo.circles.auth.view.EnterRecoveryKeyView + android:id="@+id/vRecoveryKey" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="26dp" + android:layout_marginStart="16dp" android:layout_marginTop="16dp" - android:layout_marginEnd="8dp" - android:ellipsize="end" - android:lines="1" - app:layout_constrainedWidth="true" - app:layout_constraintEnd_toStartOf="@id/ivRemoveFile" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tvOr" - tools:text="File name.txt" /> - - <ImageView - android:id="@+id/ivRemoveFile" - android:layout_width="wrap_content" - android:layout_height="wrap_content" android:layout_marginEnd="16dp" - android:background="?android:selectableItemBackgroundBorderless" - android:clickable="true" - android:focusable="true" - android:src="@drawable/ic_close" - app:layout_constraintBottom_toBottomOf="@id/tvFileName" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/tvFileName" - app:layout_constraintTop_toTopOf="@id/tvFileName" - app:tint="@color/blue" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/recoveryTypeGroup" /> - <com.google.android.material.button.MaterialButton - android:id="@+id/btnUploadFile" - style="@style/PostButtonStyle" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.Barrier + android:id="@+id/barrier" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="@string/upload_recovery_key" - app:icon="@drawable/ic_file" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tvFileName" /> + app:barrierDirection="bottom" + app:constraint_referenced_ids="tilPassphrase,vRecoveryKey" /> <com.google.android.material.button.MaterialButton android:id="@+id/btnRestore" @@ -153,27 +150,15 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:text="@string/cancel" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/btnRestore" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/btnUploadFile" /> - + app:layout_constraintTop_toBottomOf="@id/barrier" /> - <androidx.constraintlayout.widget.Group - android:id="@+id/passPhraseGroup" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:constraint_referenced_ids="tvOr,tvSubtitle,tilPassphrase" /> - - <androidx.constraintlayout.widget.Group - android:id="@+id/fileNameGroup" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:constraint_referenced_ids="tvFileName,ivRemoveFile" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 1ca60b490..08958efe5 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -75,10 +75,11 @@ <string name="validate_your_token">Validate your token</string> <string name="sign_up_token_format">abcd–efgh–1234–5678</string> <string name="validate_token">Validate token</string> - <string name="enter_passphrase">Enter Passphrase</string> + <string name="recovery">Recovery</string> + <string name="recovery_key">Recovery Key</string> <string name="use_your_recovery_message">Use your Recovery Passphrase or Recovery Key to continue.</string> <string name="passphrase">Passphrase</string> - <string name="upload_recovery_key">Upload Recovery Key</string> + <string name="upload_recovery_key_file">Upload Recovery Key file</string> <string name="restore">Restore</string> <string name="validate_your_email">Validate your email</string> <string name="not_supported_navigation_event">Not supported navigation event</string> -- GitLab