mirror of https://github.com/tasks/tasks
Two stage etesync login
parent
4e87a62911
commit
0b508ec45a
@ -0,0 +1,14 @@
|
|||||||
|
package org.tasks.etesync;
|
||||||
|
|
||||||
|
import org.tasks.data.CaldavAccount;
|
||||||
|
import org.tasks.ui.CompletableViewModel;
|
||||||
|
|
||||||
|
public class CreateUserInfoViewModel extends CompletableViewModel<String> {
|
||||||
|
|
||||||
|
void createUserInfo(EteSyncClient client, CaldavAccount caldavAccount, String derivedKey) {
|
||||||
|
run(() -> {
|
||||||
|
client.forAccount(caldavAccount).createUserInfo(derivedKey);
|
||||||
|
return derivedKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,224 @@
|
|||||||
|
package org.tasks.etesync;
|
||||||
|
|
||||||
|
import static android.text.TextUtils.isEmpty;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
import at.bitfire.dav4jvm.exception.HttpException;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnTextChanged;
|
||||||
|
import com.etesync.journalmanager.Constants;
|
||||||
|
import com.etesync.journalmanager.Crypto;
|
||||||
|
import com.etesync.journalmanager.Crypto.CryptoManager;
|
||||||
|
import com.etesync.journalmanager.Exceptions.IntegrityException;
|
||||||
|
import com.etesync.journalmanager.Exceptions.VersionTooNewException;
|
||||||
|
import com.etesync.journalmanager.GsonHelper;
|
||||||
|
import com.etesync.journalmanager.UserInfoManager.UserInfo;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import org.tasks.R;
|
||||||
|
import org.tasks.data.CaldavAccount;
|
||||||
|
import org.tasks.databinding.ActivityEtesyncEncryptionSettingsBinding;
|
||||||
|
import org.tasks.injection.ActivityComponent;
|
||||||
|
import org.tasks.injection.ThemedInjectingAppCompatActivity;
|
||||||
|
import org.tasks.security.Encryption;
|
||||||
|
import org.tasks.ui.DisplayableException;
|
||||||
|
import org.tasks.ui.MenuColorizer;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class EncryptionSettingsActivity extends ThemedInjectingAppCompatActivity
|
||||||
|
implements OnMenuItemClickListener {
|
||||||
|
|
||||||
|
public static final String EXTRA_USER_INFO = "extra_user_info";
|
||||||
|
public static final String EXTRA_ACCOUNT = "extra_account";
|
||||||
|
public static final String EXTRA_DERIVED_KEY = "extra_derived_key";
|
||||||
|
|
||||||
|
@Inject EteSyncClient client;
|
||||||
|
@Inject Encryption encryption;
|
||||||
|
|
||||||
|
ActivityEtesyncEncryptionSettingsBinding binding;
|
||||||
|
private UserInfo userInfo;
|
||||||
|
private CaldavAccount caldavAccount;
|
||||||
|
CreateUserInfoViewModel createUserInfoViewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
createUserInfoViewModel = ViewModelProviders.of(this).get(CreateUserInfoViewModel.class);
|
||||||
|
|
||||||
|
binding = ActivityEtesyncEncryptionSettingsBinding.inflate(getLayoutInflater());
|
||||||
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
caldavAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
|
||||||
|
userInfo = GsonHelper.gson.fromJson(intent.getStringExtra(EXTRA_USER_INFO), UserInfo.class);
|
||||||
|
|
||||||
|
if (userInfo == null) {
|
||||||
|
binding.repeatEncryptionPasswordLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Toolbar toolbar = binding.toolbar.toolbar;
|
||||||
|
toolbar.setTitle(
|
||||||
|
caldavAccount == null ? getString(R.string.add_account) : caldavAccount.getName());
|
||||||
|
toolbar.setNavigationIcon(ContextCompat.getDrawable(this, R.drawable.ic_outline_save_24px));
|
||||||
|
toolbar.setNavigationOnClickListener(v -> save());
|
||||||
|
toolbar.inflateMenu(R.menu.menu_etesync_encryption_settings);
|
||||||
|
toolbar.setOnMenuItemClickListener(this);
|
||||||
|
MenuColorizer.colorToolbar(this, toolbar);
|
||||||
|
|
||||||
|
createUserInfoViewModel.observe(this, this::returnDerivedKey, this::requestFailed);
|
||||||
|
|
||||||
|
if (createUserInfoViewModel.inProgress()) {
|
||||||
|
showProgressIndicator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showProgressIndicator() {
|
||||||
|
binding.progressBar.progressBar.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideProgressIndicator() {
|
||||||
|
binding.progressBar.progressBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean requestInProgress() {
|
||||||
|
return binding.progressBar.progressBar.getVisibility() == View.VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnDerivedKey(String derivedKey) {
|
||||||
|
hideProgressIndicator();
|
||||||
|
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(EXTRA_DERIVED_KEY, derivedKey);
|
||||||
|
setResult(RESULT_OK, result);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save() {
|
||||||
|
if (requestInProgress()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String encryptionPassword = getNewEncryptionPassword();
|
||||||
|
String derivedKey = caldavAccount.getEncryptionPassword(encryption);
|
||||||
|
|
||||||
|
if (isEmpty(encryptionPassword) && isEmpty(derivedKey)) {
|
||||||
|
binding.encryptionPasswordLayout.setError(getString(R.string.encryption_password_required));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userInfo == null) {
|
||||||
|
String repeatEncryptionPassword = binding.repeatEncryptionPassword.getText().toString().trim();
|
||||||
|
if (!encryptionPassword.equals(repeatEncryptionPassword)) {
|
||||||
|
binding.repeatEncryptionPasswordLayout.setError(getString(R.string.passwords_do_not_match));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String key =
|
||||||
|
isEmpty(encryptionPassword)
|
||||||
|
? derivedKey
|
||||||
|
: Crypto.deriveKey(caldavAccount.getUsername(), encryptionPassword);
|
||||||
|
CryptoManager cryptoManager;
|
||||||
|
try {
|
||||||
|
int version = userInfo == null ? Constants.CURRENT_VERSION : userInfo.getVersion();
|
||||||
|
cryptoManager = new CryptoManager(version, key, "userInfo");
|
||||||
|
} catch (VersionTooNewException | IntegrityException e) {
|
||||||
|
requestFailed(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userInfo == null) {
|
||||||
|
showProgressIndicator();
|
||||||
|
createUserInfoViewModel.createUserInfo(client, caldavAccount, key);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
userInfo.verify(cryptoManager);
|
||||||
|
returnDerivedKey(key);
|
||||||
|
} catch (IntegrityException e) {
|
||||||
|
requestFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void requestFailed(Throwable t) {
|
||||||
|
hideProgressIndicator();
|
||||||
|
|
||||||
|
if (t instanceof HttpException) {
|
||||||
|
showSnackbar(t.getMessage());
|
||||||
|
} else if (t instanceof DisplayableException) {
|
||||||
|
showSnackbar(((DisplayableException) t).getResId());
|
||||||
|
} else if (t instanceof ConnectException) {
|
||||||
|
showSnackbar(R.string.network_error);
|
||||||
|
} else {
|
||||||
|
Timber.e(t);
|
||||||
|
showSnackbar(R.string.error_adding_account, t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSnackbar(int resId, Object... formatArgs) {
|
||||||
|
showSnackbar(getString(resId, formatArgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSnackbar(String message) {
|
||||||
|
newSnackbar(message).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Snackbar newSnackbar(String message) {
|
||||||
|
Snackbar snackbar =
|
||||||
|
Snackbar.make(binding.rootLayout, message, 8000)
|
||||||
|
.setTextColor(ContextCompat.getColor(this, R.color.snackbar_text_color))
|
||||||
|
.setActionTextColor(ContextCompat.getColor(this, R.color.snackbar_action_color));
|
||||||
|
snackbar
|
||||||
|
.getView()
|
||||||
|
.setBackgroundColor(ContextCompat.getColor(this, R.color.snackbar_background));
|
||||||
|
return snackbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnTextChanged(R.id.repeat_encryption_password)
|
||||||
|
void onRpeatEncryptionPasswordChanged() {
|
||||||
|
binding.repeatEncryptionPasswordLayout.setError(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnTextChanged(R.id.encryption_password)
|
||||||
|
void onEncryptionPasswordChanged() {
|
||||||
|
binding.encryptionPasswordLayout.setError(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNewEncryptionPassword() {
|
||||||
|
return binding.encryptionPassword.getText().toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
if (!requestInProgress()) {
|
||||||
|
super.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inject(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.help) {
|
||||||
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://tasks.org/etesync")));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +1,25 @@
|
|||||||
package org.tasks.etesync;
|
package org.tasks.etesync;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.util.Pair;
|
import androidx.core.util.Pair;
|
||||||
|
import com.etesync.journalmanager.UserInfoManager.UserInfo;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import org.tasks.ui.CompletableViewModel;
|
import org.tasks.ui.CompletableViewModel;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class UpdateEteSyncAccountViewModel extends CompletableViewModel<Pair<String, String>> {
|
public class UpdateEteSyncAccountViewModel extends CompletableViewModel<Pair<UserInfo, String>> {
|
||||||
void updateAccount(
|
void updateAccount(
|
||||||
EteSyncClient client,
|
EteSyncClient client,
|
||||||
String url,
|
String url,
|
||||||
String username,
|
String username,
|
||||||
String password,
|
@Nullable String password,
|
||||||
String encryptionPassword) {
|
@Nullable String token) {
|
||||||
run(
|
run(
|
||||||
() ->
|
() -> {
|
||||||
client
|
EteSyncClient newClient = client.setForeground().forUrl(url, username, null, token);
|
||||||
.setForeground()
|
return Strings.isNullOrEmpty(password)
|
||||||
.forUrl(url, username, encryptionPassword, null)
|
? Pair.create(newClient.getUserInfo(), token)
|
||||||
.getKeyAndToken(password));
|
: newClient.getInfoAndToken(password);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/root_layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:descendantFocusability="beforeDescendants"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
layout="@layout/toolbar" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
layout="@layout/progress_view" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:descendantFocusability="beforeDescendants"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/encryption_password_layout"
|
||||||
|
style="@style/TagSettingsRow"
|
||||||
|
app:endIconMode="password_toggle">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/encryption_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/encryption_password"
|
||||||
|
android:imeOptions="flagNoExtractUi"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/repeat_encryption_password_layout"
|
||||||
|
style="@style/TagSettingsRow"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:endIconMode="password_toggle">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/repeat_encryption_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/reenter_encryption_password"
|
||||||
|
android:imeOptions="flagNoExtractUi"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/help"
|
||||||
|
android:icon="@drawable/ic_outline_help_outline_24px"
|
||||||
|
android:title="@string/help"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
</menu>
|
||||||
Loading…
Reference in New Issue