diff options
author | Fabian Henneke <FabianHenneke@users.noreply.github.com> | 2020-05-30 19:39:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-30 19:39:17 +0200 |
commit | 2428d4c0deb5d088112ed10391932acf325e84d5 (patch) | |
tree | 692b15264e45ea4cb59b7d45dfa47edd7d4af033 /app/src/main | |
parent | 72ede314ef02dfc255b3a28b3540e6fef10da9ac (diff) |
Switch password authentication over to SSHJ (#811)
* Switch password authentication over to SSHJ
* Address review comments and refactor further
Diffstat (limited to 'app/src/main')
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt | 294 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/git/config/GitConfigSessionFactory.kt | 26 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java | 3 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt | 8 | ||||
-rw-r--r-- | app/src/main/res/layout/git_credential_layout.xml (renamed from app/src/main/res/layout/git_passphrase_layout.xml) | 6 | ||||
-rw-r--r-- | app/src/main/res/values-es/strings.xml | 3 | ||||
-rw-r--r-- | app/src/main/res/values-fr/strings.xml | 3 | ||||
-rw-r--r-- | app/src/main/res/values-ru/strings.xml | 3 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 6 |
9 files changed, 169 insertions, 183 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt index bfcf2f11..404b5860 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt @@ -8,6 +8,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.view.LayoutInflater +import androidx.annotation.StringRes import androidx.core.content.edit import androidx.preference.PreferenceManager import com.google.android.material.checkbox.MaterialCheckBox @@ -16,7 +17,6 @@ import com.google.android.material.textfield.TextInputEditText import com.zeapo.pwdstore.R import com.zeapo.pwdstore.UserPreference import com.zeapo.pwdstore.git.config.ConnectionMode -import com.zeapo.pwdstore.git.config.GitConfigSessionFactory import com.zeapo.pwdstore.git.config.InteractivePasswordFinder import com.zeapo.pwdstore.git.config.SshApiSessionFactory import com.zeapo.pwdstore.git.config.SshAuthData @@ -24,37 +24,129 @@ import com.zeapo.pwdstore.git.config.SshjSessionFactory import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.getEncryptedPrefs import com.zeapo.pwdstore.utils.requestInputFocusOnView +import net.schmizz.sshj.userauth.password.PasswordFinder import org.eclipse.jgit.api.GitCommand +import org.eclipse.jgit.errors.UnsupportedCredentialItem import org.eclipse.jgit.lib.Repository +import org.eclipse.jgit.transport.CredentialItem +import org.eclipse.jgit.transport.CredentialsProvider import org.eclipse.jgit.transport.SshSessionFactory -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider +import org.eclipse.jgit.transport.URIish import java.io.File +import kotlin.coroutines.Continuation import kotlin.coroutines.resume + +private class GitOperationCredentialFinder(val callingActivity: Activity, val connectionMode: ConnectionMode) : InteractivePasswordFinder() { + + override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) { + require(connectionMode == ConnectionMode.Password) + val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation") + val credentialPref: String + @StringRes val messageRes: Int + @StringRes val hintRes: Int + @StringRes val rememberRes: Int + @StringRes val errorRes: Int + when (connectionMode) { + ConnectionMode.SshKey -> { + credentialPref = "ssh_key_local_passphrase" + messageRes = R.string.passphrase_dialog_text + hintRes = R.string.ssh_keygen_passphrase + rememberRes = R.string.git_operation_remember_passphrase + errorRes = R.string.git_operation_wrong_passphrase + } + ConnectionMode.Password -> { + // Could be either an SSH or an HTTPS password + credentialPref = "https_password" + messageRes = R.string.password_dialog_text + hintRes = R.string.git_operation_hint_password + rememberRes = R.string.git_operation_remember_password + errorRes = R.string.git_operation_wrong_password + } + else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords") + } + val storedCredential = gitOperationPrefs.getString(credentialPref, null) + if (isRetry) + gitOperationPrefs.edit { remove(credentialPref) } + if (storedCredential.isNullOrEmpty()) { + val layoutInflater = LayoutInflater.from(callingActivity) + + @SuppressLint("InflateParams") + val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null) + val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential) + editCredential.setHint(hintRes) + val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential) + rememberCredential.setText(rememberRes) + if (isRetry) + editCredential.error = callingActivity.resources.getString(errorRes) + MaterialAlertDialogBuilder(callingActivity).run { + setTitle(R.string.passphrase_dialog_title) + setMessage(messageRes) + setView(dialogView) + setPositiveButton(R.string.dialog_ok) { _, _ -> + val credential = editCredential.text.toString() + if (rememberCredential.isChecked) { + gitOperationPrefs.edit { + putString(credentialPref, credential) + } + } + cont.resume(credential) + } + setNegativeButton(R.string.dialog_cancel) { _, _ -> + cont.resume(null) + } + setOnCancelListener { + cont.resume(null) + } + create() + }.run { + requestInputFocusOnView<TextInputEditText>(R.id.git_auth_credential) + show() + } + } else { + cont.resume(storedCredential) + } + } +} + /** * Creates a new git operation * - * @param fileDir the git working tree directory + * @param gitDir the git working tree directory * @param callingActivity the calling activity */ -abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) { +abstract class GitOperation(gitDir: File, internal val callingActivity: Activity) { - protected val repository: Repository? = PasswordRepository.getRepository(fileDir) - internal var provider: UsernamePasswordCredentialsProvider? = null + protected val repository: Repository? = PasswordRepository.getRepository(gitDir) + internal var provider: CredentialsProvider? = null internal var command: GitCommand<*>? = null private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key") private val hostKeyFile = callingActivity.filesDir.resolve(".host_key") - /** - * Sets the authentication using user/pwd scheme - * - * @param username the username - * @param password the password - * @return the current object - */ - internal open fun setAuthentication(username: String, password: String): GitOperation { - SshSessionFactory.setInstance(GitConfigSessionFactory()) - this.provider = UsernamePasswordCredentialsProvider(username, password) + private class PasswordFinderCredentialsProvider(private val username: String, private val passwordFinder: PasswordFinder) : CredentialsProvider() { + + override fun isInteractive() = true + + override fun get(uri: URIish?, vararg items: CredentialItem): Boolean { + for (item in items) { + when (item) { + is CredentialItem.Username -> item.value = username + is CredentialItem.Password -> item.value = passwordFinder.reqPassword(null) + else -> UnsupportedCredentialItem(uri, item.javaClass.name) + } + } + return true + } + + override fun supports(vararg items: CredentialItem) = items.all { + it is CredentialItem.Username || it is CredentialItem.Password + } + } + + private fun withPasswordAuthentication(username: String, passwordFinder: InteractivePasswordFinder): GitOperation { + val sessionFactory = SshjSessionFactory(username, SshAuthData.Password(passwordFinder), hostKeyFile) + SshSessionFactory.setInstance(sessionFactory) + this.provider = PasswordFinderCredentialsProvider(username, passwordFinder) return this } @@ -65,146 +157,58 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit return this } - /** - * Sets the authentication using OpenKeystore scheme - * - * @param identity The identiy to use - * @return the current object - */ - private fun setAuthentication(username: String, identity: SshApiSessionFactory.ApiIdentity?): GitOperation { + private fun withOpenKeychainAuthentication(username: String, identity: SshApiSessionFactory.ApiIdentity?): GitOperation { SshSessionFactory.setInstance(SshApiSessionFactory(username, identity)) this.provider = null return this } + private fun getSshKey(make: Boolean) { + try { + // Ask the UserPreference to provide us with the ssh-key + // onResult has to be handled by the callingActivity + val intent = Intent(callingActivity.applicationContext, UserPreference::class.java) + intent.putExtra("operation", if (make) "make_ssh_key" else "get_ssh_key") + callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE) + } catch (e: Exception) { + println("Exception caught :(") + e.printStackTrace() + } + } + /** * Executes the GitCommand in an async task */ abstract fun execute() - /** - * Executes the GitCommand in an async task after creating the authentication - * - * @param connectionMode the server-connection mode - * @param username the username - * @param identity the api identity to use for auth in OpenKeychain connection mode - */ fun executeAfterAuthentication( connectionMode: ConnectionMode, username: String, identity: SshApiSessionFactory.ApiIdentity? ) { - val encryptedSettings = callingActivity.applicationContext.getEncryptedPrefs("git_operation") when (connectionMode) { - ConnectionMode.SshKey -> { - if (!sshKeyFile.exists()) { - MaterialAlertDialogBuilder(callingActivity) - .setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text)) - .setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title)) - .setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ -> - try { - // Ask the UserPreference to provide us with the ssh-key - // onResult has to be handled by the callingActivity - val intent = Intent(callingActivity.applicationContext, UserPreference::class.java) - intent.putExtra("operation", "get_ssh_key") - callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE) - } catch (e: Exception) { - println("Exception caught :(") - e.printStackTrace() - } - } - .setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ -> - try { - // Duplicated code - val intent = Intent(callingActivity.applicationContext, UserPreference::class.java) - intent.putExtra("operation", "make_ssh_key") - callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE) - } catch (e: Exception) { - println("Exception caught :(") - e.printStackTrace() - } - } - .setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> - // Finish the blank GitActivity so user doesn't have to press back - callingActivity.finish() - }.show() - } else { - withPublicKeyAuthentication(username, InteractivePasswordFinder { cont, isRetry -> - val storedPassphrase = encryptedSettings.getString("ssh_key_local_passphrase", null) - if (isRetry) - encryptedSettings.edit { putString("ssh_key_local_passphrase", null) } - if (storedPassphrase.isNullOrEmpty()) { - val layoutInflater = LayoutInflater.from(callingActivity) - - @SuppressLint("InflateParams") - val dialogView = layoutInflater.inflate(R.layout.git_passphrase_layout, null) - val editPassphrase = dialogView.findViewById<TextInputEditText>(R.id.git_auth_passphrase) - val rememberPassphrase = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase) - if (isRetry) - editPassphrase.error = callingActivity.resources.getString(R.string.git_operation_wrong_passphrase) - MaterialAlertDialogBuilder(callingActivity).run { - setTitle(R.string.passphrase_dialog_title) - setMessage(R.string.passphrase_dialog_text) - setView(dialogView) - setPositiveButton(R.string.dialog_ok) { _, _ -> - val passphrase = editPassphrase.text.toString() - if (rememberPassphrase.isChecked) { - encryptedSettings.edit { - putString("ssh_key_local_passphrase", passphrase) - } - } - cont.resume(passphrase) - } - setNegativeButton(R.string.dialog_cancel) { _, _ -> - cont.resume(null) - } - setOnCancelListener { - cont.resume(null) - } - create() - }.run { - requestInputFocusOnView<TextInputEditText>(R.id.git_auth_passphrase) - show() - } - } else { - cont.resume(storedPassphrase) - } - }).execute() - } - } - ConnectionMode.OpenKeychain -> { - setAuthentication(username, identity).execute() - } - ConnectionMode.Password -> { - @SuppressLint("InflateParams") val dialogView = callingActivity.layoutInflater.inflate(R.layout.git_passphrase_layout, null) - val passwordView = dialogView.findViewById<TextInputEditText>(R.id.git_auth_passphrase) - val password = encryptedSettings.getString("https_password", null) - if (password != null && password.isNotEmpty()) { - setAuthentication(username, password).execute() - } else { - val dialog = MaterialAlertDialogBuilder(callingActivity) - .setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title)) - .setMessage(callingActivity.resources.getString(R.string.password_dialog_text)) - .setView(dialogView) - .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> - if (dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase).isChecked) { - encryptedSettings.edit { putString("https_password", passwordView.text.toString()) } - } - // authenticate using the user/pwd and then execute the command - setAuthentication(username, passwordView.text.toString()).execute() - } - .setNegativeButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> - callingActivity.finish() - } - .setOnCancelListener { callingActivity.finish() } - .create() - dialog.requestInputFocusOnView<TextInputEditText>(R.id.git_auth_passphrase) - dialog.show() - } - } - ConnectionMode.None -> { - execute() + ConnectionMode.SshKey -> if (!sshKeyFile.exists()) { + MaterialAlertDialogBuilder(callingActivity) + .setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text)) + .setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title)) + .setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ -> + getSshKey(false) + } + .setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ -> + getSshKey(true) + } + .setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> + // Finish the blank GitActivity so user doesn't have to press back + callingActivity.finish() + }.show() + } else { + withPublicKeyAuthentication(username, GitOperationCredentialFinder(callingActivity, + connectionMode)).execute() } + ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(username, identity).execute() + ConnectionMode.Password -> withPasswordAuthentication( + username, GitOperationCredentialFinder(callingActivity, connectionMode)).execute() + ConnectionMode.None -> execute() } } @@ -216,17 +220,15 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit when (SshSessionFactory.getInstance()) { is SshApiSessionFactory -> { PreferenceManager.getDefaultSharedPreferences(callingActivity.applicationContext) - .edit { putString("ssh_openkeystore_keyid", null) } + .edit { remove("ssh_openkeystore_keyid") } } is SshjSessionFactory -> { callingActivity.applicationContext .getEncryptedPrefs("git_operation") - .edit { remove("ssh_key_local_passphrase") } - } - is GitConfigSessionFactory -> { - callingActivity.applicationContext - .getEncryptedPrefs("git_operation") - .edit { remove("https_password") } + .edit { + remove("ssh_key_local_passphrase") + remove("https_password") + } } } } diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/GitConfigSessionFactory.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/GitConfigSessionFactory.kt deleted file mode 100644 index eb4365fa..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/git/config/GitConfigSessionFactory.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.git.config - -import com.jcraft.jsch.JSch -import com.jcraft.jsch.JSchException -import com.jcraft.jsch.Session -import org.eclipse.jgit.transport.JschConfigSessionFactory -import org.eclipse.jgit.transport.OpenSshConfig -import org.eclipse.jgit.util.FS - -open class GitConfigSessionFactory : JschConfigSessionFactory() { - - override fun configure(hc: OpenSshConfig.Host, session: Session) { - session.setConfig("StrictHostKeyChecking", "no") - } - - @Throws(JSchException::class) - override fun getJSch(hc: OpenSshConfig.Host, fs: FS): JSch { - val jsch = super.getJSch(hc, fs) - jsch.removeAllIdentity() - return jsch - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java index c21ec053..e5a5fd17 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java +++ b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java @@ -26,6 +26,7 @@ import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.transport.CredentialItem; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.CredentialsProviderUserInfo; +import org.eclipse.jgit.transport.JschConfigSessionFactory; import org.eclipse.jgit.transport.OpenSshConfig; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.Base64; @@ -43,7 +44,7 @@ import org.openintents.ssh.authentication.util.SshAuthenticationApiUtils; import java.util.List; import java.util.concurrent.CountDownLatch; -public class SshApiSessionFactory extends GitConfigSessionFactory { +public class SshApiSessionFactory extends JschConfigSessionFactory { /** * Intent request code indicating a completed signature that should be posted to an outstanding * ApiIdentity diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt index 5287989a..146fdb43 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt @@ -37,12 +37,14 @@ sealed class SshAuthData { class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData() } -class InteractivePasswordFinder(val askForPassword: (cont: Continuation<String?>, isRetry: Boolean) -> Unit) : PasswordFinder { +abstract class InteractivePasswordFinder : PasswordFinder { private var isRetry = false private var shouldRetry = true - override fun reqPassword(resource: Resource<*>?): CharArray { + abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) + + final override fun reqPassword(resource: Resource<*>?): CharArray { val password = runBlocking(Dispatchers.Main) { suspendCoroutine<String?> { cont -> askForPassword(cont, isRetry) @@ -57,7 +59,7 @@ class InteractivePasswordFinder(val askForPassword: (cont: Continuation<String?> } } - override fun shouldRetry(resource: Resource<*>?) = shouldRetry + final override fun shouldRetry(resource: Resource<*>?) = shouldRetry } class SshjSessionFactory(private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() { diff --git a/app/src/main/res/layout/git_passphrase_layout.xml b/app/src/main/res/layout/git_credential_layout.xml index 83eeddd0..511a9f8f 100644 --- a/app/src/main/res/layout/git_passphrase_layout.xml +++ b/app/src/main/res/layout/git_credential_layout.xml @@ -19,7 +19,7 @@ app:layout_constraintTop_toTopOf="parent"> <com.google.android.material.textfield.TextInputEditText - android:id="@+id/git_auth_passphrase" + android:id="@+id/git_auth_credential" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/ssh_keygen_passphrase" @@ -27,10 +27,10 @@ </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.checkbox.MaterialCheckBox - android:id="@+id/git_auth_remember_passphrase" + android:id="@+id/git_auth_remember_credential" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/remember_the_passphrase" + android:text="@string/git_operation_remember_passphrase" app:layout_constraintRight_toRightOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/git_auth_passphrase_layout" /> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 975f088f..5d74c581 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -208,7 +208,8 @@ <string name="git_push_nff_error">La subida fue rechazada por el servidor, Ejecuta \'Descargar desde servidor\' antes de subir o pulsa \'Sincronizar con servidor\' para realizar ambas acciones.</string> <string name="git_push_generic_error">El envío fue rechazado por el servidor, la razón:</string> <string name="jgit_error_push_dialog_text">Ocurrió un error durante el envío:</string> - <string name="remember_the_passphrase">Recordar contraseñagit (inseguro)</string> + <string name="hotp_remember_clear_choice">Limpiar preferencia para incremento HOTP</string> + <string name="git_operation_remember_passphrase">Recordar contraseñagit (inseguro)</string> <string name="hackish_tools">Hackish tools</string> <string name="abort_rebase">Abortar rebase</string> <string name="commit_hash">Hash del commit</string> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5a295a92..eb234323 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -209,7 +209,8 @@ <string name="git_push_generic_error">Poussée rejetée par le dépôt distant, raison:</string> <string name="git_push_other_error">Pousser au dépôt distant sans avance rapide rejetée. Vérifiez la variable receive.denyNonFastForwards dans le fichier de configuration du répertoire de destination.</string> <string name="jgit_error_push_dialog_text">Une erreur s\'est produite lors de l\'opération de poussée:</string> - <string name="remember_the_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string> + <string name="hotp_remember_clear_choice">Effacer les préférences enregistrées pour l’incrémentation HOTP</string> + <string name="git_operation_remember_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string> <string name="hackish_tools">Outils de hack</string> <string name="commit_hash">Commettre la clé</string> <string name="crypto_extra_edit_hint">nom d\'utilisateur: quelque chose d\'autre contenu supplémentaire</string> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d286bbb4..a4999f9b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -270,7 +270,8 @@ <string name="git_push_generic_error">Запись изменений была отклонена удаленным репозиторием, причина:</string> <string name="git_push_other_error">Удаленный репозиторий отклонил запись изменений без быстрой перемотки вперед. Проверьте переменную receive.denyNonFastForwards в файле конфигурации репозитория назначения.</string> <string name="jgit_error_push_dialog_text">В хоте операции записи изменений возникла ошибка:</string> - <string name="remember_the_passphrase">Заполнить парольную фразу в конфигурации приложнеия (небезопасно)</string> + <string name="hotp_remember_clear_choice">Очистить сохраненные настройки для увеличения HOTP</string> + <string name="git_operation_remember_passphrase">Заполнить парольную фразу в конфигурации приложнеия (небезопасно)</string> <string name="hackish_tools">Костыльные инструменты</string> <string name="abort_rebase">Прервать перебазирование и записать изменения в новую ветку</string> <string name="reset_to_remote">Полный сброс до состояния удаленной ветки</string> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b76e05f..d485f923 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -307,7 +307,8 @@ <string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string> <string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string> <string name="clear_saved_passphrase_https">Clear saved HTTPS password</string> - <string name="remember_the_passphrase">Remember key passphrase</string> + <string name="hotp_remember_clear_choice">Clear saved preference for HOTP incrementing</string> + <string name="git_operation_remember_passphrase">Remember key passphrase</string> <string name="hackish_tools">Hackish tools</string> <string name="abort_rebase">Abort rebase and push new branch</string> <string name="reset_to_remote">Hard reset to remote branch</string> @@ -360,6 +361,7 @@ <string name="git_operation_unable_to_open_ssh_key_title">Unable to open the ssh-key</string> <string name="git_operation_unable_to_open_ssh_key_message">Please check that it was imported.</string> <string name="git_operation_wrong_passphrase">Wrong passphrase</string> + <string name="git_operation_wrong_password">Wrong password</string> <string name="bottom_sheet_create_new_folder">Create new folder</string> <string name="bottom_sheet_create_new_password">Create new password</string> <string name="autofill_onboarding_dialog_title">New, revamped Autofill!</string> @@ -369,4 +371,6 @@ <string name="pref_debug_logging_title">Debug logging</string> <string name="preference_default_username_summary">If Autofill is unable to determine a username from your password file or directory structure, it will use the value specified here</string> <string name="preference_default_username_title">Default username</string> + <string name="git_operation_remember_password">Remember password</string> + <string name="git_operation_hint_password">Password</string> </resources> |