Skip to content

Commit

Permalink
Add options to Serve feature
Browse files Browse the repository at this point in the history
Option allow remote access. This fixes previous security bug where
external access was enabled by default.

Option to specify user and password for authentication
  • Loading branch information
patrykcoding committed Sep 20, 2018
1 parent 47608f5 commit 40668d6
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 50 deletions.
148 changes: 148 additions & 0 deletions app/src/main/java/ca/pkay/rcloneexplorer/Dialogs/ServeDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package ca.pkay.rcloneexplorer.Dialogs;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioGroup;

import ca.pkay.rcloneexplorer.R;
import ca.pkay.rcloneexplorer.Rclone;

public class ServeDialog extends DialogFragment {

private Context context;
private boolean isDarkTheme;
private Callback callback;
private RadioGroup protocol;
private CheckBox allowRemoteAccess;
private EditText user;
private EditText password;

public interface Callback {
void onServeOptionsSelected(int protocol, boolean allowRemoteAccess, String user, String password);
}

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (getParentFragment() != null) {
callback = (Callback) getParentFragment();
}

if (savedInstanceState != null) {
isDarkTheme = savedInstanceState.getBoolean("isDarkTheme");
}

AlertDialog.Builder builder;
if (isDarkTheme) {
builder = new AlertDialog.Builder(context, R.style.DarkDialogTheme);
} else {
builder = new AlertDialog.Builder(context);
}

LayoutInflater layoutInflater = ((FragmentActivity)context).getLayoutInflater();
View view = layoutInflater.inflate(R.layout.dialog_serve, null);

protocol = view.findViewById(R.id.radio_group_protocol);
allowRemoteAccess = view.findViewById(R.id.checkbox_allow_remote_access);
user = view.findViewById(R.id.edit_text_user);
password = view.findViewById(R.id.edit_text_password);

((TextInputLayout) view.findViewById(R.id.text_input_layout_user)).setHint("Username");
((TextInputLayout) view.findViewById(R.id.text_input_layout_password)).setHint("Password");

builder.setTitle(R.string.serve_dialog_title);
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendCallback();
}
});

builder.setNegativeButton(R.string.cancel, null);

builder.setView(view);

return builder.show();
}

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("isDarkTheme", isDarkTheme);
outState.putInt("protocol", protocol.getCheckedRadioButtonId());
outState.putBoolean("allowRemoteAccess", allowRemoteAccess.isChecked());
if (!user.getText().toString().trim().isEmpty()) {
outState.putString("user", user.getText().toString());
}
if (!password.getText().toString().trim().isEmpty()) {
outState.putString("password", password.getText().toString());
}
}

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState == null) {
return;
}

allowRemoteAccess.setChecked(savedInstanceState.getBoolean("allowRemoteAccess", false));
String savedUser = savedInstanceState.getString("user");
if (savedUser != null) {
user.setText(savedUser);
}

String savedPassword = savedInstanceState.getString("password");
if (savedPassword != null) {
password.setText(savedPassword);
}

int savedProtocol = savedInstanceState.getInt("protocol", -1);
if (savedProtocol == R.id.radio_http || savedProtocol == R.id.radio_webdav) {
protocol.check(savedProtocol);
}
}

@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;

if (context instanceof Callback) {
callback = (Callback) context;
}
}

public ServeDialog setDarkTheme(boolean isDarkTheme) {
this.isDarkTheme = isDarkTheme;
return this;
}

private void sendCallback() {
int selectedProtocolId = protocol.getCheckedRadioButtonId();
int selectedProtocol;
switch (selectedProtocolId) {
case R.id.radio_webdav:
selectedProtocol = Rclone.SERVE_PROTOCOL_WEBDAV;
break;
case R.id.radio_http:
default:
selectedProtocol = Rclone.SERVE_PROTOCOL_HTTP;
break;
}

callback.onServeOptionsSelected(selectedProtocol, allowRemoteAccess.isChecked(), user.getText().toString(), password.getText().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import ca.pkay.rcloneexplorer.Dialogs.InputDialog;
import ca.pkay.rcloneexplorer.Dialogs.LinkDialog;
import ca.pkay.rcloneexplorer.Dialogs.LoadingDialog;
import ca.pkay.rcloneexplorer.Dialogs.ServeDialog;
import ca.pkay.rcloneexplorer.Dialogs.SortDialog;
import ca.pkay.rcloneexplorer.FileComparators;
import ca.pkay.rcloneexplorer.Dialogs.FilePropertiesDialog;
Expand Down Expand Up @@ -90,7 +91,8 @@ public class FileExplorerFragment extends Fragment implements FileExplorerRecy
OpenAsDialog.OnClickListener,
InputDialog.OnPositive,
GoToDialog.Callbacks,
SortDialog.OnClickListener {
SortDialog.OnClickListener,
ServeDialog.Callback {

private static final String ARG_REMOTE = "remote_param";
private static final String SHARED_PREFS_SORT_ORDER = "ca.pkay.rcexplorer.sort_order";
Expand Down Expand Up @@ -593,34 +595,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
recyclerViewAdapter.toggleSelectAll();
return true;
case R.id.action_serve:
String[] serveOptions = new String[] {"HTTP", "Webdav"};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setItems(serveOptions, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(getContext(), StreamingService.class);
switch (which) {
case 0: // HTTP
intent.putExtra(StreamingService.SERVE_PATH_ARG, directoryObject.getCurrentPath());
intent.putExtra(StreamingService.REMOTE_ARG, remote);
intent.putExtra(StreamingService.SERVE_PROTOCOL, StreamingService.SERVE_HTTP);
intent.putExtra(StreamingService.SHOW_NOTIFICATION_TEXT, true);
break;
case 1: // Webdav
intent.putExtra(StreamingService.SERVE_PATH_ARG, directoryObject.getCurrentPath());
intent.putExtra(StreamingService.REMOTE_ARG, remote);
intent.putExtra(StreamingService.SERVE_PROTOCOL, StreamingService.SERVE_WEBDAV);
intent.putExtra(StreamingService.SHOW_NOTIFICATION_TEXT, true);
break;
default:
return;
}
context.startService(intent);
}
});
builder.setTitle(R.string.pick_a_protocol);
builder.show();

serve();
return true;
case R.id.action_empty_trash:
emptyTrash();
Expand Down Expand Up @@ -650,6 +625,36 @@ public void onClick(DialogInterface dialog, int which) {
}
}

private void serve() {
ServeDialog serveDialog = new ServeDialog();
serveDialog.setDarkTheme(isDarkTheme);
serveDialog.show(getChildFragmentManager(), "serve dialog");
}

// serve callback
@Override
public void onServeOptionsSelected(int protocol, boolean allowRemoteAccess, String user, String password) {
Intent intent = new Intent(getContext(), StreamingService.class);
intent.putExtra(StreamingService.SERVE_PATH_ARG, directoryObject.getCurrentPath());
intent.putExtra(StreamingService.REMOTE_ARG, remote);
intent.putExtra(StreamingService.SHOW_NOTIFICATION_TEXT, true);
intent.putExtra(StreamingService.ALLOW_REMOTE_ACCESS, allowRemoteAccess);
intent.putExtra(StreamingService.AUTHENTICATION_USERNAME, user);
intent.putExtra(StreamingService.AUTHENTICATION_PASSWORD, password);

switch (protocol) {
case Rclone.SERVE_PROTOCOL_HTTP: // HTTP
intent.putExtra(StreamingService.SERVE_PROTOCOL, StreamingService.SERVE_HTTP);
break;
case Rclone.SERVE_PROTOCOL_WEBDAV: // Webdav
intent.putExtra(StreamingService.SERVE_PROTOCOL, StreamingService.SERVE_WEBDAV);
break;
default:
return;
}
context.startService(intent);
}

private void emptyTrash() {
AlertDialog.Builder builder;

Expand Down
50 changes: 32 additions & 18 deletions app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@

public class Rclone {

public static int SYNC_DIRECTION_LOCAL_TO_REMOTE = 1;
public static int SYNC_DIRECTION_REMOTE_TO_LOCAL = 2;
public static final int SYNC_DIRECTION_LOCAL_TO_REMOTE = 1;
public static final int SYNC_DIRECTION_REMOTE_TO_LOCAL = 2;
public static final int SERVE_PROTOCOL_HTTP = 1;
public static final int SERVE_PROTOCOL_WEBDAV = 2;
private Context context;
private String rclone;
private String rcloneConf;
Expand Down Expand Up @@ -353,37 +355,49 @@ public String obscure(String pass) {
}
}

public Process serveHttp(RemoteItem remote, String servePath, int port) {
public Process serve(int protocol, int port, boolean allowRemoteAccess, String user, String password, RemoteItem remote, String servePath) {
String remoteName = remote.getName();
String localRemotePath = (remote.isRemoteType(RemoteItem.LOCAL)) ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/" : "";
String path = (servePath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + servePath;
String[] command = createCommandWithOptions("serve", "http", "--addr", ":" + String.valueOf(port), path);

try {
return Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
return null;
String commandProtocol = protocol == SERVE_PROTOCOL_HTTP ? "http" : "webdav";
String address;
if (allowRemoteAccess) {
address = ":" + String.valueOf(port);
} else {
address = "127.0.0.1:" + String.valueOf(port);
}
}

public Process serveWebdav(RemoteItem remote, String servePath, int port) {
String remoteName = remote.getName();
String localRemotePath = (remote.isRemoteType(RemoteItem.LOCAL)) ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/" : "";
String path = (servePath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + servePath;
String[] command = createCommandWithOptions("serve", "webdav", "--addr", ":" + String.valueOf(port), path);

String cachePath = context.getCacheDir().getAbsolutePath();
String[] environmentalVariables = {"TMPDIR=" + cachePath}; // this is a fix for #199

String[] command;

if (user == null && password != null) {
command = createCommandWithOptions("serve", commandProtocol, "--addr", address, path, "--pass", password);
} else if (user != null && password == null) {
command = createCommandWithOptions("serve", commandProtocol, "--addr", address, path, "--user", user);
} else if (user != null) {
command = createCommandWithOptions("serve", commandProtocol, "--addr", address, path, "--user", user, "--pass", password);
} else {
command = createCommandWithOptions("serve", commandProtocol, "--addr", address, path);
}

try {
return Runtime.getRuntime().exec(command, environmentalVariables);
if (protocol == SERVE_PROTOCOL_WEBDAV) {
return Runtime.getRuntime().exec(command, environmentalVariables);
} else {
return Runtime.getRuntime().exec(command);
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}

public Process serve(int protocol, int port, boolean localhostOnly, RemoteItem remote, String servePath) {
return serve(protocol, port, localhostOnly, null, null, remote, servePath);
}

public Process sync(RemoteItem remoteItem, String remote, String localPath, int syncDirection) {
String[] command;
String remoteName = remoteItem.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class StreamingService extends IntentService {
public static final String REMOTE_ARG = "ca.pkay.rcexplorer.streaming_service.arg2";
public static final String SHOW_NOTIFICATION_TEXT = "ca.pkay.rcexplorer.streaming_service.arg3";
public static final String SERVE_PROTOCOL = "ca.pkay.rcexplorer.serve_protocol";
public static final String ALLOW_REMOTE_ACCESS = "ca.pkay.rcexplorer.allow_remote_access";
public static final String AUTHENTICATION_USERNAME = "ca.pkay.rcexplorer.username";
public static final String AUTHENTICATION_PASSWORD = "ca.pkay.rcexplorer.password";
public static final int SERVE_HTTP = 11;
public static final int SERVE_WEBDAV = 12;
private final String CHANNEL_ID = "ca.pkay.rcexplorer.streaming_channel";
Expand Down Expand Up @@ -54,6 +57,9 @@ protected void onHandleIntent(@Nullable Intent intent) {
final RemoteItem remote = intent.getParcelableExtra(REMOTE_ARG);
final Boolean showNotificationText = intent.getBooleanExtra(SHOW_NOTIFICATION_TEXT, false);
final int protocol = intent.getIntExtra(SERVE_PROTOCOL, SERVE_HTTP);
final Boolean allowRemoteAccess = intent.getBooleanExtra(ALLOW_REMOTE_ACCESS, false);
final String authenticationUsername = intent.getStringExtra(AUTHENTICATION_USERNAME);
final String authenticationPassword = intent.getStringExtra(AUTHENTICATION_PASSWORD);

Intent foregroundIntent = new Intent(this, StreamingService.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, foregroundIntent, 0);
Expand Down Expand Up @@ -81,11 +87,11 @@ protected void onHandleIntent(@Nullable Intent intent) {

switch (protocol) {
case SERVE_WEBDAV:
runningProcess = rclone.serveWebdav(remote, servePath, 8080);
runningProcess = rclone.serve(Rclone.SERVE_PROTOCOL_WEBDAV, 8080, allowRemoteAccess, authenticationUsername, authenticationPassword, remote, servePath);
break;
case SERVE_HTTP:
default:
runningProcess = rclone.serveHttp(remote, servePath, 8080);
runningProcess = rclone.serve(Rclone.SERVE_PROTOCOL_HTTP, 8080, allowRemoteAccess, authenticationUsername, authenticationPassword, remote, servePath);
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected void onHandleIntent(@Nullable Intent intent) {
}

RemoteItem remote = intent.getParcelableExtra(REMOTE_ARG);
process = rclone.serveHttp(remote, "", 29170);
process = rclone.serve(Rclone.SERVE_PROTOCOL_HTTP, 29170, true, remote, "");
if (process != null) {
try {
process.waitFor();
Expand Down
Loading

0 comments on commit 40668d6

Please sign in to comment.