diff options
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt')
| -rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt | 252 |
1 files changed, 252 insertions, 0 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 new file mode 100644 index 00000000..5c945c30 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt @@ -0,0 +1,252 @@ +package com.zeapo.pwdstore.git + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.preference.PreferenceManager +import android.text.InputType +import android.view.LayoutInflater +import android.view.View +import android.widget.CheckBox +import android.widget.EditText +import android.widget.LinearLayout +import androidx.appcompat.app.AlertDialog + +import com.jcraft.jsch.JSch +import com.jcraft.jsch.JSchException +import com.jcraft.jsch.KeyPair +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.UserPreference +import com.zeapo.pwdstore.git.config.GitConfigSessionFactory +import com.zeapo.pwdstore.git.config.SshApiSessionFactory +import com.zeapo.pwdstore.git.config.SshConfigSessionFactory +import com.zeapo.pwdstore.utils.PasswordRepository + +import org.eclipse.jgit.api.GitCommand +import org.eclipse.jgit.lib.Repository +import org.eclipse.jgit.transport.SshSessionFactory +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider + +import java.io.File + +/** + * Creates a new git operation + * + * @param fileDir the git working tree directory + * @param callingActivity the calling activity + */ +abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) { + + protected val repository: Repository = PasswordRepository.getRepository(fileDir) + internal var provider: UsernamePasswordCredentialsProvider? = null + internal var command: GitCommand<*>? = null + + /** + * 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) + return this + } + + /** + * Sets the authentication using ssh-key scheme + * + * @param sshKey the ssh-key file + * @param username the username + * @param passphrase the passphrase + * @return the current object + */ + internal open fun setAuthentication(sshKey: File, username: String, passphrase: String): GitOperation { + val sessionFactory = SshConfigSessionFactory(sshKey.absolutePath, username, passphrase) + SshSessionFactory.setInstance(sessionFactory) + this.provider = null + 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 { + SshSessionFactory.setInstance(SshApiSessionFactory(username, identity)) + this.provider = null + return this + } + + /** + * 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 sshKey the ssh-key file to use in ssh-key connection mode + * @param identity the api identity to use for auth in OpenKeychain connection mode + */ + fun executeAfterAuthentication(connectionMode: String, + username: String, + sshKey: File?, + identity: SshApiSessionFactory.ApiIdentity?) { + executeAfterAuthentication(connectionMode, username, sshKey, identity, false) + } + + /** + * Executes the GitCommand in an async task after creating the authentication + * + * @param connectionMode the server-connection mode + * @param username the username + * @param sshKey the ssh-key file to use in ssh-key connection mode + * @param identity the api identity to use for auth in OpenKeychain connection mode + * @param showError show the passphrase edit text in red + */ + private fun executeAfterAuthentication(connectionMode: String, + username: String, + sshKey: File?, + identity: SshApiSessionFactory.ApiIdentity?, + showError: Boolean) { + if (connectionMode.equals("ssh-key", ignoreCase = true)) { + if (sshKey == null || !sshKey.exists()) { + AlertDialog.Builder(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 { + val layoutInflater = LayoutInflater.from(callingActivity.applicationContext) + @SuppressLint("InflateParams") val dialogView = layoutInflater.inflate(R.layout.git_passphrase_layout, null) + val passphrase = dialogView.findViewById<EditText>(R.id.sshkey_passphrase) + val settings = PreferenceManager.getDefaultSharedPreferences(callingActivity.applicationContext) + val sshKeyPassphrase = settings.getString("ssh_key_passphrase", null) + if (showError) { + passphrase.error = "Wrong passphrase" + } + val jsch = JSch() + try { + val keyPair = KeyPair.load(jsch, callingActivity.filesDir.toString() + "/.ssh_key") + + if (keyPair.isEncrypted) { + if (sshKeyPassphrase != null && sshKeyPassphrase.isNotEmpty()) { + if (keyPair.decrypt(sshKeyPassphrase)) { + // Authenticate using the ssh-key and then execute the command + setAuthentication(sshKey, username, sshKeyPassphrase).execute() + } else { + // call back the method + executeAfterAuthentication(connectionMode, username, sshKey, identity, true) + } + } else { + AlertDialog.Builder(callingActivity) + .setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title)) + .setMessage(callingActivity.resources.getString(R.string.passphrase_dialog_text)) + .setView(dialogView) + .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> + if (keyPair.decrypt(passphrase.text.toString())) { + val rememberPassphrase = (dialogView.findViewById<View>(R.id.sshkey_remember_passphrase) as CheckBox).isChecked + if (rememberPassphrase) { + settings.edit().putString("ssh_key_passphrase", passphrase.text.toString()).apply() + } + // Authenticate using the ssh-key and then execute the command + setAuthentication(sshKey, username, passphrase.text.toString()).execute() + } else { + settings.edit().putString("ssh_key_passphrase", null).apply() + // call back the method + executeAfterAuthentication(connectionMode, username, sshKey, identity, true) + } + }.setNegativeButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> + // Do nothing. + }.show() + } + } else { + setAuthentication(sshKey, username, "").execute() + } + } catch (e: JSchException) { + AlertDialog.Builder(callingActivity) + .setTitle("Unable to open the ssh-key") + .setMessage("Please check that it was imported.") + .setPositiveButton("Ok") { _, _ -> } + .show() + } + + } + } else if (connectionMode.equals("OpenKeychain", ignoreCase = true)) { + setAuthentication(username, identity).execute() + } else { + val password = EditText(callingActivity) + password.hint = "Password" + password.width = LinearLayout.LayoutParams.MATCH_PARENT + password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + + AlertDialog.Builder(callingActivity) + .setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title)) + .setMessage(callingActivity.resources.getString(R.string.password_dialog_text)) + .setView(password) + .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> + // authenticate using the user/pwd and then execute the command + setAuthentication(username, password.text.toString()).execute() + + } + .setNegativeButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> + callingActivity.finish() + } + .show() + } + } + + /** + * Action to execute on error + */ + open fun onError(errorMessage: String) { + AlertDialog.Builder(callingActivity) + .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) + .setMessage(callingActivity.resources.getString(R.string.jgit_error_dialog_text) + errorMessage) + .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> + callingActivity.setResult(Activity.RESULT_CANCELED) + callingActivity.finish() + } + .show() + } + + /** + * Action to execute on success + */ + open fun onSuccess() {} + + companion object { + const val GET_SSH_KEY_FROM_CLONE = 201 + } +} |
