mirror of https://github.com/M66B/FairEmail.git
Cloud sync foundation
This commit is contained in:
parent
47d0d7c6ad
commit
da6fe6f555
|
@ -168,6 +168,7 @@ android {
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"https://api.bitbucket.org/2.0/repositories/M66B/fairemail-test/downloads\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"https://api.bitbucket.org/2.0/repositories/M66B/fairemail-test/downloads\""
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"https://bitbucket.org/M66B/fairemail-test/downloads/\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"https://bitbucket.org/M66B/fairemail-test/downloads/\""
|
||||||
buildConfigField "String", "ANNOUNCEMENT_URI", "\"https://gist.githubusercontent.com/M66B/d544192ca56224839d6ba0f2f6314c1f/raw/\""
|
buildConfigField "String", "ANNOUNCEMENT_URI", "\"https://gist.githubusercontent.com/M66B/d544192ca56224839d6ba0f2f6314c1f/raw/\""
|
||||||
|
buildConfigField "String", "CLOUD_URI", "\"https://api.fairemail.net/sync\""
|
||||||
buildConfigField "String", "TX_URI", localProperties.getProperty("paypal.uri", "\"\"")
|
buildConfigField "String", "TX_URI", localProperties.getProperty("paypal.uri", "\"\"")
|
||||||
buildConfigField "String", "GPA_URI", localProperties.getProperty("gpa.uri", "\"\"")
|
buildConfigField "String", "GPA_URI", localProperties.getProperty("gpa.uri", "\"\"")
|
||||||
buildConfigField "String", "INFO_URI", localProperties.getProperty("info.uri", "\"\"")
|
buildConfigField "String", "INFO_URI", localProperties.getProperty("info.uri", "\"\"")
|
||||||
|
@ -187,6 +188,7 @@ android {
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"https://api.bitbucket.org/2.0/repositories/M66B/fairemail-test/downloads\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"https://api.bitbucket.org/2.0/repositories/M66B/fairemail-test/downloads\""
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"https://bitbucket.org/M66B/fairemail-test/downloads/\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"https://bitbucket.org/M66B/fairemail-test/downloads/\""
|
||||||
buildConfigField "String", "ANNOUNCEMENT_URI", "\"https://gist.githubusercontent.com/M66B/d544192ca56224839d6ba0f2f6314c1f/raw/\""
|
buildConfigField "String", "ANNOUNCEMENT_URI", "\"https://gist.githubusercontent.com/M66B/d544192ca56224839d6ba0f2f6314c1f/raw/\""
|
||||||
|
buildConfigField "String", "CLOUD_URI", "\"https://api.fairemail.net/sync\""
|
||||||
buildConfigField "String", "TX_URI", "\"\""
|
buildConfigField "String", "TX_URI", "\"\""
|
||||||
buildConfigField "String", "GPA_URI", "\"\""
|
buildConfigField "String", "GPA_URI", "\"\""
|
||||||
buildConfigField "String", "INFO_URI", "\"\""
|
buildConfigField "String", "INFO_URI", "\"\""
|
||||||
|
@ -207,6 +209,7 @@ android {
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"\""
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
|
||||||
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
|
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
|
||||||
|
buildConfigField "String", "CLOUD_URI", "\"\""
|
||||||
buildConfigField "String", "TX_URI", "\"\""
|
buildConfigField "String", "TX_URI", "\"\""
|
||||||
buildConfigField "String", "GPA_URI", "\"\""
|
buildConfigField "String", "GPA_URI", "\"\""
|
||||||
buildConfigField "String", "INFO_URI", "\"\""
|
buildConfigField "String", "INFO_URI", "\"\""
|
||||||
|
@ -227,6 +230,7 @@ android {
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"\""
|
||||||
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
|
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
|
||||||
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
|
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
|
||||||
|
buildConfigField "String", "CLOUD_URI", "\"\""
|
||||||
buildConfigField "String", "TX_URI", "\"\""
|
buildConfigField "String", "TX_URI", "\"\""
|
||||||
buildConfigField "String", "GPA_URI", "\"\""
|
buildConfigField "String", "GPA_URI", "\"\""
|
||||||
buildConfigField "String", "INFO_URI", "\"\""
|
buildConfigField "String", "INFO_URI", "\"\""
|
||||||
|
|
|
@ -62,6 +62,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.cardview.widget.CardView;
|
import androidx.cardview.widget.CardView;
|
||||||
|
import androidx.constraintlayout.widget.Group;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
@ -81,12 +82,14 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.KeySpec;
|
import java.security.spec.KeySpec;
|
||||||
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -106,22 +109,45 @@ import javax.crypto.SecretKey;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
public class FragmentOptionsBackup extends FragmentBase {
|
public class FragmentOptionsBackup extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private View view;
|
private View view;
|
||||||
private ImageButton ibHelp;
|
private ImageButton ibHelp;
|
||||||
private Button btnExport;
|
private Button btnExport;
|
||||||
private Button btnImport;
|
private Button btnImport;
|
||||||
private CardView cardCloud;
|
private CardView cardCloud;
|
||||||
|
private TextView tvCloudInfo;
|
||||||
|
private TextView tvCloudPro;
|
||||||
private EditText etUser;
|
private EditText etUser;
|
||||||
private TextInputLayout tilPassword;
|
private TextInputLayout tilPassword;
|
||||||
private Button btnLogin;
|
private Button btnLogin;
|
||||||
|
private TextView tvLogin;
|
||||||
|
private CheckBox cbAccounts;
|
||||||
|
private CheckBox cbBlockedSenders;
|
||||||
|
private CheckBox cbFilterRules;
|
||||||
|
private ImageButton ibSync;
|
||||||
|
private TextView tvLastSync;
|
||||||
|
private Button btnLogout;
|
||||||
|
private CheckBox cbDelete;
|
||||||
|
private Group grpLogin;
|
||||||
|
private Group grpLogout;
|
||||||
|
|
||||||
|
private DateFormat DTF;
|
||||||
|
|
||||||
private static final int REQUEST_EXPORT_SELECT = 1;
|
private static final int REQUEST_EXPORT_SELECT = 1;
|
||||||
private static final int REQUEST_IMPORT_SELECT = 2;
|
private static final int REQUEST_IMPORT_SELECT = 2;
|
||||||
private static final int REQUEST_EXPORT_HANDLE = 3;
|
private static final int REQUEST_EXPORT_HANDLE = 3;
|
||||||
private static final int REQUEST_IMPORT_HANDLE = 4;
|
private static final int REQUEST_IMPORT_HANDLE = 4;
|
||||||
|
|
||||||
|
private static final int CLOUD_TIMEOUT = 10 * 1000; // timeout
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
DTF = Helper.getDateTimeInstance(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
@ -135,9 +161,21 @@ public class FragmentOptionsBackup extends FragmentBase {
|
||||||
btnExport = view.findViewById(R.id.btnExport);
|
btnExport = view.findViewById(R.id.btnExport);
|
||||||
btnImport = view.findViewById(R.id.btnImport);
|
btnImport = view.findViewById(R.id.btnImport);
|
||||||
cardCloud = view.findViewById(R.id.cardCloud);
|
cardCloud = view.findViewById(R.id.cardCloud);
|
||||||
|
tvCloudInfo = view.findViewById(R.id.tvCloudInfo);
|
||||||
|
tvCloudPro = view.findViewById(R.id.tvCloudPro);
|
||||||
etUser = view.findViewById(R.id.etUser);
|
etUser = view.findViewById(R.id.etUser);
|
||||||
tilPassword = view.findViewById(R.id.tilPassword);
|
tilPassword = view.findViewById(R.id.tilPassword);
|
||||||
btnLogin = view.findViewById(R.id.btnLogin);
|
btnLogin = view.findViewById(R.id.btnLogin);
|
||||||
|
tvLogin = view.findViewById(R.id.tvLogin);
|
||||||
|
cbAccounts = view.findViewById(R.id.cbAccounts);
|
||||||
|
cbBlockedSenders = view.findViewById(R.id.cbBlockedSenders);
|
||||||
|
cbFilterRules = view.findViewById(R.id.cbFilterRules);
|
||||||
|
ibSync = view.findViewById(R.id.ibSync);
|
||||||
|
tvLastSync = view.findViewById(R.id.tvLastSync);
|
||||||
|
btnLogout = view.findViewById(R.id.btnLogout);
|
||||||
|
cbDelete = view.findViewById(R.id.cbDelete);
|
||||||
|
grpLogin = view.findViewById(R.id.grpLogin);
|
||||||
|
grpLogout = view.findViewById(R.id.grpLogout);
|
||||||
|
|
||||||
// Wire controls
|
// Wire controls
|
||||||
|
|
||||||
|
@ -150,6 +188,13 @@ public class FragmentOptionsBackup extends FragmentBase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tvCloudInfo.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Helper.viewFAQ(v.getContext(), 999);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
btnExport.setOnClickListener(new View.OnClickListener() {
|
btnExport.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
@ -171,14 +216,86 @@ public class FragmentOptionsBackup extends FragmentBase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cbAccounts.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
prefs.edit().putBoolean("cloud_sync_accounts", isChecked).apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cbBlockedSenders.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
prefs.edit().putBoolean("cloud_sync_blocked_senders", isChecked).apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cbFilterRules.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
prefs.edit().putBoolean("cloud_sync_filter_rules", isChecked).apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ibSync.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLogout.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onLogout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
FragmentDialogTheme.setBackground(getContext(), view, false);
|
FragmentDialogTheme.setBackground(getContext(), view, false);
|
||||||
cardCloud.setVisibility(BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
cardCloud.setVisibility(
|
||||||
? View.VISIBLE : View.GONE);
|
BuildConfig.DEBUG &&
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||||
|
!TextUtils.isEmpty(BuildConfig.CLOUD_URI)
|
||||||
|
? View.VISIBLE : View.GONE);
|
||||||
|
Helper.linkPro(tvCloudPro);
|
||||||
|
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
cbAccounts.setChecked(prefs.getBoolean("cloud_sync_accounts", true));
|
||||||
|
cbBlockedSenders.setChecked(prefs.getBoolean("cloud_sync_blocked_senders", true));
|
||||||
|
cbFilterRules.setChecked(prefs.getBoolean("cloud_sync_filter_rules", true));
|
||||||
|
onSharedPreferenceChanged(prefs, null);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
|
if (key == null ||
|
||||||
|
"cloud_user".equals(key) ||
|
||||||
|
"cloud_password".equals(key)) {
|
||||||
|
String user = prefs.getString("cloud_user", null);
|
||||||
|
String password = prefs.getString("cloud_password", null);
|
||||||
|
boolean auth = !(TextUtils.isEmpty(user) || TextUtils.isEmpty(password));
|
||||||
|
long last_sync = prefs.getLong("cloud_last_sync", 0);
|
||||||
|
|
||||||
|
etUser.setText(user);
|
||||||
|
tilPassword.getEditText().setText(password);
|
||||||
|
tvLogin.setText(user);
|
||||||
|
tvLastSync.setText(getString(R.string.title_advanced_cloud_last_sync,
|
||||||
|
last_sync == 0 ? "-" : DTF.format(last_sync)));
|
||||||
|
cbDelete.setChecked(false);
|
||||||
|
grpLogin.setVisibility(auth ? View.GONE : View.VISIBLE);
|
||||||
|
grpLogout.setVisibility(auth ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
@ -1362,46 +1479,142 @@ public class FragmentOptionsBackup extends FragmentBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onLogin() {
|
private void onLogin() {
|
||||||
|
String username = etUser.getText().toString();
|
||||||
|
String password = tilPassword.getEditText().getText().toString();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(username.trim())) {
|
||||||
|
etUser.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(password)) {
|
||||||
|
tilPassword.getEditText().requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("user", etUser.getText().toString());
|
cloud(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLogout() {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean("logout", true);
|
||||||
|
args.putBoolean("wipe", cbDelete.isChecked());
|
||||||
|
cloud(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cloud(Bundle args) {
|
||||||
|
args.putString("user", etUser.getText().toString().trim());
|
||||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||||
|
|
||||||
new SimpleTask<Void>() {
|
new SimpleTask<String>() {
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute(Bundle args) {
|
protected void onPreExecute(Bundle args) {
|
||||||
btnLogin.setEnabled(false);
|
Helper.setViewsEnabled(cardCloud, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Bundle args) {
|
protected void onPostExecute(Bundle args) {
|
||||||
btnLogin.setEnabled(true);
|
Helper.setViewsEnabled(cardCloud, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void onExecute(Context context, Bundle args) throws Throwable {
|
protected String onExecute(Context context, Bundle args) throws Throwable {
|
||||||
String user = args.getString("user");
|
String user = args.getString("user");
|
||||||
String password = args.getString("password");
|
String password = args.getString("password");
|
||||||
|
boolean wipe = args.getBoolean("wipe");
|
||||||
|
|
||||||
Pair<byte[], byte[]> key = getKeyPair(user, password);
|
byte[] salt = MessageDigest.getInstance("SHA256").digest(user.getBytes());
|
||||||
|
String cloudUser = Helper.hex(MessageDigest.getInstance("SHA256").digest(salt));
|
||||||
|
|
||||||
return null;
|
Pair<byte[], byte[]> key = getKeyPair(salt, password);
|
||||||
|
String cloudPassword = Helper.hex(key.first);
|
||||||
|
|
||||||
|
JSONObject jroot = new JSONObject();
|
||||||
|
jroot.put("username", cloudUser);
|
||||||
|
jroot.put("password", cloudPassword);
|
||||||
|
jroot.put("wipe", wipe);
|
||||||
|
jroot.put("debug", BuildConfig.DEBUG);
|
||||||
|
String request = jroot.toString();
|
||||||
|
Log.i("Cloud request=" + request);
|
||||||
|
|
||||||
|
URL url = new URL(BuildConfig.CLOUD_URI);
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setDoInput(true);
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setReadTimeout(CLOUD_TIMEOUT);
|
||||||
|
connection.setConnectTimeout(CLOUD_TIMEOUT);
|
||||||
|
ConnectionHelper.setUserAgent(context, connection);
|
||||||
|
connection.setRequestProperty("Accept", "application/json");
|
||||||
|
connection.setRequestProperty("Content-Length", Integer.toString(request.length()));
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection.getOutputStream().write(request.getBytes());
|
||||||
|
|
||||||
|
int status = connection.getResponseCode();
|
||||||
|
if (status != HttpsURLConnection.HTTP_OK) {
|
||||||
|
String error = "Error " + status + ": " + connection.getResponseMessage();
|
||||||
|
String detail = Helper.readStream(connection.getErrorStream());
|
||||||
|
JSONObject jerror = new JSONObject(detail);
|
||||||
|
if (status == HttpsURLConnection.HTTP_FORBIDDEN)
|
||||||
|
throw new SecurityException(jerror.optString("error"));
|
||||||
|
else
|
||||||
|
throw new IOException(error + " " + jerror);
|
||||||
|
}
|
||||||
|
|
||||||
|
String response = Helper.readStream(connection.getInputStream());
|
||||||
|
Log.i("Cloud response=" + response);
|
||||||
|
JSONObject jresponse = new JSONObject(response);
|
||||||
|
return jresponse.optString("status");
|
||||||
|
} finally {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onExecuted(Bundle args, Void data) {
|
protected void onExecuted(Bundle args, String status) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
|
if ("ok".equals(status) && !args.getBoolean("logout"))
|
||||||
|
prefs.edit()
|
||||||
|
.putString("cloud_user", args.getString("user"))
|
||||||
|
.putString("cloud_password", args.getString("password"))
|
||||||
|
.apply();
|
||||||
|
else
|
||||||
|
prefs.edit()
|
||||||
|
.remove("cloud_user")
|
||||||
|
.remove("cloud_password")
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
view.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
view.scrollTo(0, cardCloud.getTop());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onException(Bundle args, Throwable ex) {
|
protected void onException(Bundle args, Throwable ex) {
|
||||||
Log.unexpectedError(getParentFragmentManager(), ex);
|
if (ex instanceof SecurityException) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
|
||||||
|
.setIcon(R.drawable.twotone_warning_24)
|
||||||
|
.setTitle(getString(R.string.title_advanced_cloud_invalid))
|
||||||
|
.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
String message = ex.getMessage();
|
||||||
|
if (!TextUtils.isEmpty(message))
|
||||||
|
builder.setMessage(message);
|
||||||
|
builder.show();
|
||||||
|
} else
|
||||||
|
Log.unexpectedError(getParentFragmentManager(), ex);
|
||||||
}
|
}
|
||||||
}.execute(FragmentOptionsBackup.this, args, "cloud:login");
|
}.execute(FragmentOptionsBackup.this, args, "cloud");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Pair<byte[], byte[]> getKeyPair(String user, String password)
|
private static Pair<byte[], byte[]> getKeyPair(byte[] salt, String password)
|
||||||
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
byte[] salt = MessageDigest.getInstance("SHA256").digest(user.getBytes());
|
|
||||||
|
|
||||||
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
||||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||||
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 310000, 2 * 256);
|
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 310000, 2 * 256);
|
||||||
|
|
|
@ -2242,7 +2242,11 @@ public class Log {
|
||||||
Object value = settings.get(key);
|
Object value = settings.get(key);
|
||||||
if ("wipe_mnemonic".equals(key) && value != null)
|
if ("wipe_mnemonic".equals(key) && value != null)
|
||||||
value = "[redacted]";
|
value = "[redacted]";
|
||||||
if (key != null && key.startsWith("oauth."))
|
else if ("cloud_user".equals(key) && value != null)
|
||||||
|
value = "[redacted]";
|
||||||
|
else if ("cloud_password".equals(key) && value != null)
|
||||||
|
value = "[redacted]";
|
||||||
|
else if (key != null && key.startsWith("oauth."))
|
||||||
value = "[redacted]";
|
value = "[redacted]";
|
||||||
size += write(os, key + "=" + value + "\r\n");
|
size += write(os, key + "=" + value + "\r\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,32 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCloudInfo"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:drawableEnd="@drawable/twotone_info_24"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:text="@string/title_advanced_cloud_security"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textStyle="italic"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvCloud" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCloudPro"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/title_pro_feature"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textColor="?android:attr/textColorLink"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvCloudInfo" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvUser"
|
android:id="@+id/tvUser"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -177,7 +203,7 @@
|
||||||
android:text="@string/title_user"
|
android:text="@string/title_user"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvCloud" />
|
app:layout_constraintTop_toBottomOf="@id/tvCloudPro" />
|
||||||
|
|
||||||
<eu.faircode.email.EditTextPlain
|
<eu.faircode.email.EditTextPlain
|
||||||
android:id="@+id/etUser"
|
android:id="@+id/etUser"
|
||||||
|
@ -223,12 +249,122 @@
|
||||||
style="?android:attr/buttonStyleSmall"
|
style="?android:attr/buttonStyleSmall"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="24dp"
|
||||||
android:drawableEnd="@drawable/twotone_login_24"
|
android:drawableEnd="@drawable/twotone_login_24"
|
||||||
android:drawablePadding="6dp"
|
android:drawablePadding="6dp"
|
||||||
android:text="@string/title_advanced_login"
|
android:tag="disable"
|
||||||
|
android:text="@string/title_advanced_cloud_login"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
|
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRegister"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/title_advanced_cloud_register"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textStyle="italic"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/btnLogin" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLogin"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="username"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvRegister" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbAccounts"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/title_advanced_cloud_accounts"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvLogin" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbBlockedSenders"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/title_advanced_cloud_blocked_senders"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/cbAccounts" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbFilterRules"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/title_advanced_cloud_filter_rules"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/cbBlockedSenders" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/ibSync"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/cbFilterRules"
|
||||||
|
app:srcCompat="@drawable/twotone_compare_arrows_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLastSync"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/title_advanced_cloud_last_sync"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/ibSync" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnLogout"
|
||||||
|
style="?android:attr/buttonStyleSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:drawableEnd="@drawable/twotone_logout_24"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:tag="disable"
|
||||||
|
android:text="@string/title_advanced_cloud_logout"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvLastSync" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbDelete"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:drawableEnd="@drawable/twotone_warning_24"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:tag="disable"
|
||||||
|
android:text="@string/title_advanced_cloud_wipe"
|
||||||
|
app:drawableTint="?attr/colorWarning"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/btnLogout" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/grpLogin"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:constraint_referenced_ids="
|
||||||
|
tvUser,etUser,tvPassword,tilPassword,btnLogin,tvRegister" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/grpLogout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:constraint_referenced_ids="
|
||||||
|
tvLogin,cbAccounts,cbBlockedSenders,cbFilterRules,
|
||||||
|
ibSync,tvLastSync,
|
||||||
|
btnLogout,cbDelete" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -972,7 +972,17 @@
|
||||||
<string name="title_advanced_cleanup_hint">This will delete all temporary files</string>
|
<string name="title_advanced_cleanup_hint">This will delete all temporary files</string>
|
||||||
<string name="title_advanced_keywords_hint" translatable="false">Space separated</string>
|
<string name="title_advanced_keywords_hint" translatable="false">Space separated</string>
|
||||||
|
|
||||||
<string name="title_advanced_login" translatable="false">Login</string>
|
<string name="title_advanced_cloud_security" translatable="false">All data is encrypted end-to-end and the cloud server will never see the username, password and data</string>
|
||||||
|
<string name="title_advanced_cloud_login" translatable="false">Login</string>
|
||||||
|
<string name="title_advanced_cloud_register" translatable="false">Logging in for the first time will automatically create an account</string>
|
||||||
|
<string name="title_advanced_cloud_invalid" translatable="false">Invalid username or password</string>
|
||||||
|
<string name="title_advanced_cloud_accounts" translatable="false">Sync accounts</string>
|
||||||
|
<string name="title_advanced_cloud_blocked_senders" translatable="false">Sync blocked senders</string>
|
||||||
|
<string name="title_advanced_cloud_filter_rules" translatable="false">Sync filter rules</string>
|
||||||
|
<string name="title_advanced_cloud_last_sync" translatable="false">Last sync: %1$s</string>
|
||||||
|
<string name="title_advanced_cloud_update" translatable="false">Update settings</string>
|
||||||
|
<string name="title_advanced_cloud_logout" translatable="false">Logout</string>
|
||||||
|
<string name="title_advanced_cloud_wipe" translatable="false">Wipe cloud data on logout</string>
|
||||||
|
|
||||||
<string name="title_advanced_never_favorite">Never favorite</string>
|
<string name="title_advanced_never_favorite">Never favorite</string>
|
||||||
<string name="title_advanced_edit_name">Edit name</string>
|
<string name="title_advanced_edit_name">Edit name</string>
|
||||||
|
|
Loading…
Reference in New Issue