Cloud sync: activate

pull/212/head
M66B 3 years ago
parent f731796480
commit 0ccc27d55c

@ -170,6 +170,7 @@ android {
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", "CLOUD_URI", "\"https://api.fairemail.net/sync\""
buildConfigField "String", "CLOUD_EMAIL", "\"cloud@in.faircode.eu\""
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", "\"\"")
@ -191,6 +192,7 @@ android {
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", "CLOUD_URI", "\"https://api.fairemail.net/sync\""
buildConfigField "String", "CLOUD_EMAIL", "\"cloud@in.faircode.eu\""
buildConfigField "String", "TX_URI", "\"\"" buildConfigField "String", "TX_URI", "\"\""
buildConfigField "String", "GPA_URI", "\"\"" buildConfigField "String", "GPA_URI", "\"\""
buildConfigField "String", "INFO_URI", "\"\"" buildConfigField "String", "INFO_URI", "\"\""
@ -213,6 +215,7 @@ android {
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\"" buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\"" buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
buildConfigField "String", "CLOUD_URI", "\"\"" buildConfigField "String", "CLOUD_URI", "\"\""
buildConfigField "String", "CLOUD_EMAIL", "\"\""
buildConfigField "String", "TX_URI", "\"\"" buildConfigField "String", "TX_URI", "\"\""
buildConfigField "String", "GPA_URI", "\"\"" buildConfigField "String", "GPA_URI", "\"\""
buildConfigField "String", "INFO_URI", "\"\"" buildConfigField "String", "INFO_URI", "\"\""
@ -235,6 +238,7 @@ android {
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\"" buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\"" buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
buildConfigField "String", "CLOUD_URI", "\"\"" buildConfigField "String", "CLOUD_URI", "\"\""
buildConfigField "String", "CLOUD_EMAIL", "\"\""
buildConfigField "String", "TX_URI", "\"\"" buildConfigField "String", "TX_URI", "\"\""
buildConfigField "String", "GPA_URI", "\"\"" buildConfigField "String", "GPA_URI", "\"\""
buildConfigField "String", "INFO_URI", "\"\"" buildConfigField "String", "INFO_URI", "\"\""

@ -71,9 +71,21 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB
@Override @Override
public void onBackStackChanged() { public void onBackStackChanged() {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) { if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
String action = getIntent().getAction(); Intent intent = getIntent();
if (!isShared(action) &&
(action == null || !action.startsWith("widget:"))) { String action = intent.getAction();
boolean widget = (action != null && action.startsWith("widget:"));
String[] tos = intent.getStringArrayExtra(Intent.EXTRA_EMAIL);
boolean cloud = (tos != null && tos.length == 1 && BuildConfig.CLOUD_EMAIL.equals(tos[0]));
if (cloud) {
Intent setup = new Intent(this, ActivitySetup.class)
.setAction("misc")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.putExtra("tab", "backup");
startActivity(setup);
} else if (!isShared(action) && !widget) {
Intent parent = getParentActivityIntent(); Intent parent = getParentActivityIntent();
if (parent != null) if (parent != null)
if (shouldUpRecreateTask(parent)) if (shouldUpRecreateTask(parent))

@ -576,12 +576,24 @@ public class CloudSync {
} }
} }
private static JSONObject _call(Context context, String user, String password, JSONObject jrequest) private static byte[] getSalt(String user) throws NoSuchAlgorithmException {
throws GeneralSecurityException, JSONException, IOException, InvalidCipherTextException { return MessageDigest.getInstance("SHA256").digest(user.getBytes());
byte[] salt = MessageDigest.getInstance("SHA256").digest(user.getBytes()); }
static String getCloudUser(String user) throws NoSuchAlgorithmException {
return getCloudUser(getSalt(user));
}
private static String getCloudUser(byte[] salt) throws NoSuchAlgorithmException {
byte[] huser = MessageDigest.getInstance("SHA256").digest(salt); byte[] huser = MessageDigest.getInstance("SHA256").digest(salt);
byte[] userid = Arrays.copyOfRange(huser, 0, 8); byte[] userid = Arrays.copyOfRange(huser, 0, 8);
String cloudUser = Base64.encodeToString(userid, Base64.NO_PADDING | Base64.NO_WRAP); return Base64.encodeToString(userid, Base64.NO_PADDING | Base64.NO_WRAP);
}
private static JSONObject _call(Context context, String user, String password, JSONObject jrequest)
throws GeneralSecurityException, JSONException, IOException, InvalidCipherTextException {
byte[] salt = getSalt(user);
String cloudUser = getCloudUser(salt);
Pair<byte[], byte[]> key; Pair<byte[], byte[]> key;
String lookup = Helper.hex(salt) + ":" + password; String lookup = Helper.hex(salt) + ":" + password;

@ -128,6 +128,7 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
private Button btnLogout; private Button btnLogout;
private CheckBox cbDelete; private CheckBox cbDelete;
private Group grpLogin; private Group grpLogin;
private Group grpActivate;
private Group grpLogout; private Group grpLogout;
private DateFormat DTF; private DateFormat DTF;
@ -171,6 +172,7 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
btnLogout = view.findViewById(R.id.btnLogout); btnLogout = view.findViewById(R.id.btnLogout);
cbDelete = view.findViewById(R.id.cbDelete); cbDelete = view.findViewById(R.id.cbDelete);
grpLogin = view.findViewById(R.id.grpLogin); grpLogin = view.findViewById(R.id.grpLogin);
grpActivate = view.findViewById(R.id.grpActivate);
grpLogout = view.findViewById(R.id.grpLogout); grpLogout = view.findViewById(R.id.grpLogout);
// Wire controls // Wire controls
@ -215,7 +217,18 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
btnActivate.setOnClickListener(new View.OnClickListener() { btnActivate.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// TODO try {
String user = prefs.getString("cloud_user", null);
Intent intent = new Intent(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setType("text/plain")
.putExtra(Intent.EXTRA_EMAIL, new String[]{BuildConfig.CLOUD_EMAIL})
.putExtra(Intent.EXTRA_SUBJECT, CloudSync.getCloudUser(user))
.putExtra(Intent.EXTRA_TEXT, "Activate");
v.getContext().startActivity(intent);
} catch (Throwable ex) {
Log.e(ex);
}
} }
}); });
@ -256,7 +269,6 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
!TextUtils.isEmpty(BuildConfig.CLOUD_URI) !TextUtils.isEmpty(BuildConfig.CLOUD_URI)
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
Helper.linkPro(tvCloudPro); Helper.linkPro(tvCloudPro);
btnActivate.setVisibility(View.GONE);
cbSend.setChecked(prefs.getBoolean("cloud_send", true)); cbSend.setChecked(prefs.getBoolean("cloud_send", true));
cbReceive.setChecked(prefs.getBoolean("cloud_receive", false)); cbReceive.setChecked(prefs.getBoolean("cloud_receive", false));
@ -278,10 +290,14 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
if (key == null || if (key == null ||
"cloud_user".equals(key) || "cloud_user".equals(key) ||
"cloud_password".equals(key) || "cloud_password".equals(key) ||
"cloud_activated".equals(key) ||
"cloud_busy".equals(key) ||
"cloud_last_sync".equals(key)) { "cloud_last_sync".equals(key)) {
String user = prefs.getString("cloud_user", null); String user = prefs.getString("cloud_user", null);
String password = prefs.getString("cloud_password", null); String password = prefs.getString("cloud_password", null);
boolean auth = !(TextUtils.isEmpty(user) || TextUtils.isEmpty(password)); boolean auth = !(TextUtils.isEmpty(user) || TextUtils.isEmpty(password));
boolean activated = prefs.getBoolean("cloud_activated", false);
boolean busy = prefs.getBoolean("cloud_busy", false);
long last_sync = prefs.getLong("cloud_last_sync", 0); long last_sync = prefs.getLong("cloud_last_sync", 0);
etUser.setText(user); etUser.setText(user);
@ -291,6 +307,7 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
last_sync == 0 ? "-" : DTF.format(last_sync))); last_sync == 0 ? "-" : DTF.format(last_sync)));
cbDelete.setChecked(false); cbDelete.setChecked(false);
grpLogin.setVisibility(auth ? View.GONE : View.VISIBLE); grpLogin.setVisibility(auth ? View.GONE : View.VISIBLE);
grpActivate.setVisibility(auth && !activated && !busy ? View.VISIBLE : View.GONE);
grpLogout.setVisibility(auth ? View.VISIBLE : View.GONE); grpLogout.setVisibility(auth ? View.VISIBLE : View.GONE);
} }
} }
@ -1522,15 +1539,19 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
} }
private void cloud(Bundle args) { private void cloud(Bundle args) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
new SimpleTask<Void>() { new SimpleTask<Void>() {
@Override @Override
protected void onPreExecute(Bundle args) { protected void onPreExecute(Bundle args) {
Helper.setViewsEnabled(cardCloud, false); Helper.setViewsEnabled(cardCloud, false);
prefs.edit().putBoolean("cloud_busy", true).apply();
} }
@Override @Override
protected void onPostExecute(Bundle args) { protected void onPostExecute(Bundle args) {
Helper.setViewsEnabled(cardCloud, true); Helper.setViewsEnabled(cardCloud, true);
prefs.edit().putBoolean("cloud_busy", false).apply();
WorkerSync.init(getContext()); WorkerSync.init(getContext());
} }
@ -1566,7 +1587,8 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
@Override @Override
protected void onExecuted(Bundle args, Void data) { protected void onExecuted(Bundle args, Void data) {
btnActivate.setVisibility(View.GONE); prefs.edit().putBoolean("cloud_activated", true).apply();
view.post(new Runnable() { view.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -1577,8 +1599,9 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
@Override @Override
protected void onException(Bundle args, Throwable ex) { protected void onException(Bundle args, Throwable ex) {
btnActivate.setVisibility(ex instanceof OperationCanceledException ? View.VISIBLE : View.GONE); if (ex instanceof OperationCanceledException)
if (ex instanceof SecurityException) { prefs.edit().putBoolean("cloud_activated", false).apply();
else if (ex instanceof SecurityException) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setIcon(R.drawable.twotone_warning_24) .setIcon(R.drawable.twotone_warning_24)
.setTitle(getString(R.string.title_advanced_cloud_invalid)) .setTitle(getString(R.string.title_advanced_cloud_invalid))
@ -1587,7 +1610,7 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere
if (!TextUtils.isEmpty(message)) if (!TextUtils.isEmpty(message))
builder.setMessage(message); builder.setMessage(message);
builder.show(); builder.show();
} else if (!(ex instanceof OperationCanceledException)) } else
Log.unexpectedError(getParentFragmentManager(), ex); Log.unexpectedError(getParentFragmentManager(), ex);
} }
}.execute(FragmentOptionsBackup.this, args, "cloud"); }.execute(FragmentOptionsBackup.this, args, "cloud");

@ -324,11 +324,22 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnLogin" /> 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" />
<Button <Button
android:id="@+id/btnActivate" android:id="@+id/btnActivate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="12dp"
android:backgroundTint="?attr/colorInfoBackground" android:backgroundTint="?attr/colorInfoBackground"
android:drawableEnd="@drawable/twotone_redeem_24" android:drawableEnd="@drawable/twotone_redeem_24"
android:drawablePadding="6dp" android:drawablePadding="6dp"
@ -340,16 +351,17 @@
android:textStyle="bold" android:textStyle="bold"
app:drawableTint="?attr/colorInfoForeground" app:drawableTint="?attr/colorInfoForeground"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvRegister" /> app:layout_constraintTop_toBottomOf="@id/tvLogin" />
<TextView <TextView
android:id="@+id/tvLogin" android:id="@+id/tvActivateRemark"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="6dp"
android:text="username" android:text="@string/title_advanced_cloud_activate_remark"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold" android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnActivate" /> app:layout_constraintTop_toBottomOf="@id/btnActivate" />
@ -363,7 +375,7 @@
android:textStyle="italic" android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvLogin" /> app:layout_constraintTop_toBottomOf="@id/tvActivateRemark" />
<CheckBox <CheckBox
android:id="@+id/cbSend" android:id="@+id/cbSend"
@ -436,6 +448,13 @@
tvUser,etUser,tvPassword,tilPassword,tvPasswordRemark, tvUser,etUser,tvPassword,tilPassword,tvPasswordRemark,
btnLogin,tvRegister" /> btnLogin,tvRegister" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpActivate"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="
btnActivate,tvActivateRemark" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/grpLogout" android:id="@+id/grpLogout"
android:layout_width="0dp" android:layout_width="0dp"

@ -989,6 +989,10 @@
<string name="title_advanced_cloud_password_remark" translatable="false">You can change the password by wiping the data and logging in again</string> <string name="title_advanced_cloud_password_remark" translatable="false">You can change the password by wiping the data and logging in again</string>
<string name="title_advanced_cloud_invalid" translatable="false">Invalid username or password</string> <string name="title_advanced_cloud_invalid" translatable="false">Invalid username or password</string>
<string name="title_advanced_cloud_activate" translatable="false">Activate</string> <string name="title_advanced_cloud_activate" translatable="false">Activate</string>
<string name="title_advanced_cloud_activate_remark" translatable="false">
Please use the same email address you used to activate the pro features.
The cloud account and the email address will not be linked.
</string>
<string name="title_advanced_cloud_account_remark" translatable="false"> <string name="title_advanced_cloud_account_remark" translatable="false">
Only enabled accounts and identities will be synchronized. Only enabled accounts and identities will be synchronized.
Folder properties will not be synchronized. Folder properties will not be synchronized.

Loading…
Cancel
Save