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(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(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 } }