Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ dependencies {
implementation("com.jayway.android.robotium:robotium-solo:5.6.3")
implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION))
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
implementation("org.sufficientlysecure:sshauthentication-api:1.0")

// Testing-only dependencies
androidTestImplementation("junit:junit:4.12")
Expand Down
150 changes: 95 additions & 55 deletions app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.zeapo.pwdstore.R;
import com.zeapo.pwdstore.UserPreference;
import com.zeapo.pwdstore.git.config.SshApiSessionFactory;
import com.zeapo.pwdstore.utils.PasswordRepository;

import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.RebaseCommand;
Expand Down Expand Up @@ -53,6 +57,8 @@ public class GitActivity extends AppCompatActivity {
private File localDir;
private String hostname;
private SharedPreferences settings;
private SshApiSessionFactory.IdentityBuilder identityBuilder;
private SshApiSessionFactory.ApiIdentity identity;

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand Down Expand Up @@ -117,8 +123,10 @@ public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l)
connection_mode_spinner.setEnabled(true);

// however, if we have some saved that, that's more important!
if (connectionMode.equals("ssh-key")) {
if (connectionMode.equalsIgnoreCase("ssh-key")) {
connection_mode_spinner.setSelection(0);
} else if (connectionMode.equalsIgnoreCase("OpenKeychain")) {
connection_mode_spinner.setSelection(2);
} else {
connection_mode_spinner.setSelection(1);
}
Expand Down Expand Up @@ -370,6 +378,16 @@ public void onResume() {
updateURI();
}

@Override
protected void onDestroy() {
// Do not leak the service connection
if (identityBuilder != null) {
identityBuilder.close();
identityBuilder = null;
}
super.onDestroy();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
Expand Down Expand Up @@ -556,16 +574,7 @@ public void cloneRepository(View view) {
(dialog, id) -> {
try {
FileUtils.deleteDirectory(localDir);
try {
new CloneOperation(localDir, activity)
.setCommand(hostname)
.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
} catch (Exception e) {
//This is what happens when jgit fails :(
//TODO Handle the diffent cases of exceptions
e.printStackTrace();
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
}
launchGitOperation(REQUEST_CLONE);
} catch (IOException e) {
//TODO Handle the exception correctly if we are unable to delete the directory...
e.printStackTrace();
Expand All @@ -590,15 +599,13 @@ public void cloneRepository(View view) {
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
}
}
new CloneOperation(localDir, activity)
.setCommand(hostname)
.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
} catch (Exception e) {
//This is what happens when jgit fails :(
//TODO Handle the diffent cases of exceptions
e.printStackTrace();
new AlertDialog.Builder(this).setMessage(e.getMessage()).show();
}
launchGitOperation(REQUEST_CLONE);
}
}

Expand Down Expand Up @@ -627,47 +634,45 @@ private void syncRepository(int operation) {
else {
// check that the remote origin is here, else add it
PasswordRepository.addRemote("origin", hostname, false);
GitOperation op;

switch (operation) {
case REQUEST_PULL:
op = new PullOperation(localDir, activity).setCommand();
break;
case REQUEST_PUSH:
op = new PushOperation(localDir, activity).setCommand();
break;
case REQUEST_SYNC:
op = new SyncOperation(localDir, activity).setCommands();
break;
default:
Log.e(TAG, "Sync operation not recognized : " + operation);
return;
}

try {
op.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
} catch (Exception e) {
e.printStackTrace();
}
launchGitOperation(operation);
}
}

protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (resultCode == RESULT_CANCELED) {
setResult(RESULT_CANCELED);
finish();
return;
}
/**
* Attempt to launch the requested GIT operation. Depending on the configured auth, it may not
* be possible to launch the operation immediately. In that case, this function may launch an
* intermediate activity instead, which will gather necessary information and post it back via
* onActivityResult, which will then re-call this function. This may happen multiple times,
* until either an error is encountered or the operation is successfully launched.
*
* @param operation The type of GIT operation to launch
*/
protected void launchGitOperation(int operation) {
GitOperation op;

try {

// Before launching the operation with OpenKeychain auth, we need to issue several requests
// to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
// we just need to keep calling it until it returns a completed ApiIdentity.
if (connectionMode.equalsIgnoreCase("OpenKeychain") && identity == null) {
// Lazy initialization of the IdentityBuilder
if (identityBuilder == null) {
identityBuilder = new SshApiSessionFactory.IdentityBuilder(this);
}

if (resultCode == RESULT_OK) {
GitOperation op;
// Try to get an ApiIdentity and bail if one is not ready yet. The builder will ensure
// that onActivityResult is called with operation again, which will re-invoke us here
identity = identityBuilder.tryBuild(operation);
if (identity == null)
return;
}

switch (requestCode) {
switch (operation) {
case REQUEST_CLONE:
setResult(RESULT_OK);
finish();
return;
op = new CloneOperation(localDir, activity).setCommand(hostname);
break;

case REQUEST_PULL:
op = new PullOperation(localDir, activity).setCommand();
break;
Expand All @@ -676,22 +681,57 @@ protected void onActivityResult(int requestCode, int resultCode,
op = new PushOperation(localDir, activity).setCommand();
break;

case REQUEST_SYNC:
op = new SyncOperation(localDir, activity).setCommands();
break;

case GitOperation.GET_SSH_KEY_FROM_CLONE:
op = new CloneOperation(localDir, activity).setCommand(hostname);
break;

case SshApiSessionFactory.POST_SIGNATURE:
return;

default:
Log.e(TAG, "Operation not recognized : " + resultCode);
Log.e(TAG, "Operation not recognized : " + operation);
setResult(RESULT_CANCELED);
finish();
return;
}

try {
op.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
} catch (Exception e) {
e.printStackTrace();
}
op.executeAfterAuthentication(connectionMode,
settings.getString("git_remote_username", "git"),
new File(getFilesDir() + "/.ssh_key"),
identity);
} catch (Exception e) {
e.printStackTrace();
new AlertDialog.Builder(this).setMessage(e.getMessage()).show();
}
}

protected void onActivityResult(int requestCode, int resultCode,
Intent data) {

// In addition to the pre-operation-launch series of intents for OpenKeychain auth
// that will pass through here and back to launchGitOperation, there is one
// synchronous operation that happens /after/ the operation has been launched in the
// background thread - the actual signing of the SSH challenge. We pass through the
// completed signature to the ApiIdentity, which will be blocked in the other thread
// waiting for it.
if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null)
identity.postSignature(data);

if (resultCode == RESULT_CANCELED) {
setResult(RESULT_CANCELED);
finish();
} else if (resultCode == RESULT_OK) {
// If an operation has been re-queued via this mechanism, let the
// IdentityBuilder attempt to extract some updated state from the intent before
// trying to re-launch the operation.
if (identityBuilder != null) {
identityBuilder.consume(data);
}
launchGitOperation(requestCode);
}
}

Expand Down
41 changes: 34 additions & 7 deletions app/src/main/java/com/zeapo/pwdstore/git/GitOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;
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.JschConfigSessionFactory;
Expand Down Expand Up @@ -76,6 +80,18 @@ GitOperation setAuthentication(File sshKey, String username, String passphrase)
return this;
}

/**
* Sets the authentication using OpenKeystore scheme
*
* @param identity The identiy to use
* @return the current object
*/
GitOperation setAuthentication(String username, SshApiSessionFactory.ApiIdentity identity) {
SshSessionFactory.setInstance(new SshApiSessionFactory(username, identity));
this.provider = null;
return this;
}

/**
* Executes the GitCommand in an async task
*/
Expand All @@ -86,21 +102,30 @@ GitOperation setAuthentication(File sshKey, String username, String passphrase)
*
* @param connectionMode the server-connection mode
* @param username the username
* @param sshKey the ssh-key file
* @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
*/
public void executeAfterAuthentication(final String connectionMode, final String username, @Nullable final File sshKey) {
executeAfterAuthentication(connectionMode, username, sshKey, false);
public void executeAfterAuthentication(final String connectionMode,
final String username,
@Nullable final File sshKey,
SshApiSessionFactory.ApiIdentity identity) {
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
* @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 void executeAfterAuthentication(final String connectionMode, final String username, @Nullable final File sshKey, final boolean showError) {
private void executeAfterAuthentication(final String connectionMode,
final String username,
@Nullable final File sshKey,
SshApiSessionFactory.ApiIdentity identity,
final boolean showError) {
if (connectionMode.equalsIgnoreCase("ssh-key")) {
if (sshKey == null || !sshKey.exists()) {
new AlertDialog.Builder(callingActivity)
Expand Down Expand Up @@ -153,7 +178,7 @@ private void executeAfterAuthentication(final String connectionMode, final Strin
setAuthentication(sshKey, username, sshKeyPassphrase).execute();
} else {
// call back the method
executeAfterAuthentication(connectionMode, username, sshKey, true);
executeAfterAuthentication(connectionMode, username, sshKey, identity, true);
}
} else {
new AlertDialog.Builder(callingActivity)
Expand All @@ -171,7 +196,7 @@ private void executeAfterAuthentication(final String connectionMode, final Strin
} else {
settings.edit().putString("ssh_key_passphrase", null).apply();
// call back the method
executeAfterAuthentication(connectionMode, username, sshKey, true);
executeAfterAuthentication(connectionMode, username, sshKey, identity, true);
}
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, whichButton) -> {
// Do nothing.
Expand All @@ -189,6 +214,8 @@ private void executeAfterAuthentication(final String connectionMode, final Strin
}).show();
}
}
} else if (connectionMode.equalsIgnoreCase("OpenKeychain")) {
setAuthentication(username, identity).execute();
} else {
final EditText password = new EditText(callingActivity);
password.setHint("Password");
Expand Down
Loading