Two stage etesync login

pull/898/head
Alex Baker 4 years ago
parent 4e87a62911
commit 0b508ec45a

@ -570,6 +570,10 @@
android:name=".etesync.EteSyncCalendarSettingsActivity"
android:theme="@style/Tasks" />
<activity
android:name=".etesync.EncryptionSettingsActivity"
android:theme="@style/Tasks" />
<!-- launcher icons -->
<activity-alias

@ -10,7 +10,7 @@ import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.StringRes;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import at.bitfire.dav4jvm.exception.HttpException;
@ -19,7 +19,6 @@ import butterknife.OnFocusChange;
import butterknife.OnTextChanged;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.todoroo.astrid.service.TaskDeleter;
import java.net.ConnectException;
import java.net.IDN;
@ -34,7 +33,6 @@ import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.databinding.ActivityCaldavAccountSettingsBinding;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.etesync.EteSyncAccountSettingsActivity;
import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.security.Encryption;
import org.tasks.ui.DisplayableException;
@ -65,7 +63,10 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
setContentView(binding.getRoot());
ButterKnife.bind(this);
caldavAccount = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
caldavAccount =
savedInstanceState == null
? getIntent().getParcelableExtra(EXTRA_CALDAV_DATA)
: savedInstanceState.getParcelable(EXTRA_CALDAV_DATA);
if (savedInstanceState == null) {
if (caldavAccount != null) {
@ -75,9 +76,6 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
if (!isEmpty(caldavAccount.getPassword())) {
binding.password.setText(PASSWORD_MASK);
}
if (!isEmpty(caldavAccount.getEncryptionKey())) {
binding.encryptionPassword.setText(PASSWORD_MASK);
}
binding.repeat.setChecked(caldavAccount.isSuppressRepeatingTasks());
}
}
@ -101,7 +99,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
}
if (!inventory.hasPro()) {
newSnackbar(R.string.this_feature_requires_a_subscription)
newSnackbar(getString(R.string.this_feature_requires_a_subscription))
.setDuration(BaseTransientBottomBar.LENGTH_INDEFINITE)
.setAction(
R.string.button_subscribe,
@ -110,11 +108,18 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_CALDAV_DATA, caldavAccount);
}
private void showProgressIndicator() {
binding.progressBar.progressBar.setVisibility(View.VISIBLE);
}
private void hideProgressIndicator() {
protected void hideProgressIndicator() {
binding.progressBar.progressBar.setVisibility(View.GONE);
}
@ -123,38 +128,34 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
}
@OnTextChanged(R.id.name)
void onNameChanged(CharSequence text) {
void onNameChanged() {
binding.nameLayout.setError(null);
}
@OnTextChanged(R.id.url)
void onUrlChanged(CharSequence text) {
void onUrlChanged() {
binding.urlLayout.setError(null);
}
@OnTextChanged(R.id.user)
void onUserChanged(CharSequence text) {
void onUserChanged() {
binding.userLayout.setError(null);
}
@OnTextChanged(R.id.password)
void onPasswordChanged(CharSequence text) {
void onPasswordChanged() {
binding.passwordLayout.setError(null);
}
@OnFocusChange(R.id.password)
void onPasswordFocused(boolean hasFocus) {
changePasswordFocus(binding.password, hasFocus);
}
protected void changePasswordFocus(TextInputEditText text, boolean hasFocus) {
if (hasFocus) {
if (PASSWORD_MASK.equals(text.getText().toString())) {
text.setText("");
if (PASSWORD_MASK.equals(binding.password.getText().toString())) {
binding.password.setText("");
}
} else {
if (isEmpty(text.getText()) && caldavAccount != null) {
text.setText(PASSWORD_MASK);
if (isEmpty(binding.password.getText()) && caldavAccount != null) {
binding.password.setText(PASSWORD_MASK);
}
}
}
@ -175,17 +176,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
return caldavAccount == null || !PASSWORD_MASK.equals(binding.password.getText().toString().trim());
}
protected String getNewPassword() {
String input = binding.password.getText().toString().trim();
return PASSWORD_MASK.equals(input) ? encryption.decrypt(caldavAccount.getPassword()) : input;
}
protected String getNewEncryptionPassword() {
String input = binding.encryptionPassword.getText().toString().trim();
return PASSWORD_MASK.equals(input)
? null
: input;
}
protected abstract String getNewPassword();
private void save() {
if (requestInProgress()) {
@ -196,7 +187,6 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
String username = getNewUsername();
String url = getNewURL();
String password = getNewPassword();
String encryptionPassword = getNewEncryptionPassword();
boolean failed = false;
@ -253,13 +243,6 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
failed = true;
}
if (this instanceof EteSyncAccountSettingsActivity) {
if (caldavAccount == null && isEmpty(encryptionPassword)) {
binding.encryptionPassword.setError(getString(R.string.password_required));
failed = true;
}
}
if (failed) {
return;
}
@ -308,10 +291,6 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
newSnackbar(message).show();
}
private Snackbar newSnackbar(@StringRes int resId) {
return newSnackbar(getString(resId));
}
private Snackbar newSnackbar(String message) {
Snackbar snackbar =
Snackbar.make(binding.rootLayout, message, 8000)
@ -327,7 +306,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
if (caldavAccount == null) {
return !isEmpty(getNewName())
|| !isEmpty(getNewPassword())
|| !isEmpty(getNewURL())
|| !isEmpty(binding.url.getText().toString().trim())
|| !isEmpty(getNewUsername())
|| binding.repeat.isChecked();
}
@ -344,9 +323,11 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA
@Override
public void finish() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(binding.name.getWindowToken(), 0);
super.finish();
if (!requestInProgress()) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(binding.name.getWindowToken(), 0);
super.finish();
}
}
@Override

@ -36,6 +36,8 @@ public class CaldavAccountSettingsActivity extends BaseCaldavAccountSettingsActi
}
private void addAccount(String principal) {
hideProgressIndicator();
Timber.d("Found principal: %s", principal);
CaldavAccount newAccount = new CaldavAccount();
@ -53,6 +55,8 @@ public class CaldavAccountSettingsActivity extends BaseCaldavAccountSettingsActi
}
protected void updateAccount(String principal) {
hideProgressIndicator();
caldavAccount.setName(getNewName());
caldavAccount.setUrl(principal);
caldavAccount.setUsername(getNewUsername());
@ -82,6 +86,12 @@ public class CaldavAccountSettingsActivity extends BaseCaldavAccountSettingsActi
updateAccount(caldavAccount.getUrl());
}
@Override
protected String getNewPassword() {
String input = binding.password.getText().toString().trim();
return PASSWORD_MASK.equals(input) ? encryption.decrypt(caldavAccount.getPassword()) : input;
}
@Override
protected String getHelpUrl() {
return "http://tasks.org/caldav";

@ -2,29 +2,23 @@ package org.tasks.etesync;
import android.content.Context;
import androidx.core.util.Pair;
import com.etesync.journalmanager.Crypto.AsymmetricKeyPair;
import org.tasks.etesync.EteSyncClient;
import com.etesync.journalmanager.UserInfoManager.UserInfo;
import org.tasks.gtasks.PlayServices;
import org.tasks.ui.CompletableViewModel;
@SuppressWarnings("WeakerAccess")
public class AddEteSyncAccountViewModel
extends CompletableViewModel<Pair<String, String>> {
public class AddEteSyncAccountViewModel extends CompletableViewModel<Pair<UserInfo, String>> {
void addAccount(
PlayServices playServices,
Context context,
EteSyncClient client,
String url,
String username,
String password,
String encryptionPassword) {
String password) {
run(
() -> {
playServices.updateSecurityProvider(context);
return client
.setForeground()
.forUrl(url, username, encryptionPassword, null)
.getKeyAndToken(password);
return client.setForeground().forUrl(url, username, null, null).getInfoAndToken(password);
});
}
}

@ -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,29 +1,39 @@
package org.tasks.etesync;
import static com.todoroo.astrid.data.Task.NO_ID;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.util.Pair;
import androidx.lifecycle.ViewModelProviders;
import butterknife.OnFocusChange;
import butterknife.OnTextChanged;
import butterknife.OnCheckedChanged;
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.common.base.Strings;
import com.todoroo.astrid.helper.UUIDHelper;
import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracking.Events;
import org.tasks.caldav.BaseCaldavAccountSettingsActivity;
import org.tasks.data.CaldavAccount;
import org.tasks.gtasks.PlayServices;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ForApplication;
import timber.log.Timber;
public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsActivity
implements Toolbar.OnMenuItemClickListener {
private static final int REQUEST_ENCRYPTION_PASSWORD = 10101;
@Inject @ForApplication Context context;
@Inject PlayServices playServices;
@Inject EteSyncClient eteSyncClient;
@ -36,77 +46,113 @@ public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsAct
super.onCreate(savedInstanceState);
binding.repeat.setVisibility(View.GONE);
binding.encryptionPasswordLayout.setVisibility(View.VISIBLE);
binding.showAdvanced.setVisibility(View.VISIBLE);
updateUrlVisibility();
addAccountViewModel = ViewModelProviders.of(this).get(AddEteSyncAccountViewModel.class);
updateAccountViewModel = ViewModelProviders.of(this).get(UpdateEteSyncAccountViewModel.class);
if (savedInstanceState == null) {
if (caldavAccount == null) {
binding.url.setText(R.string.etesync_url);
}
}
addAccountViewModel.observe(this, this::addAccount, this::requestFailed);
updateAccountViewModel.observe(this, this::updateAccount, this::requestFailed);
}
private void addAccount(Pair<String, String> authentication) {
CaldavAccount newAccount = new CaldavAccount();
newAccount.setAccountType(CaldavAccount.TYPE_ETESYNC);
newAccount.setUuid(UUIDHelper.newUUID());
applyTo(newAccount, authentication);
newAccount.setId(caldavDao.insert(newAccount));
tracker.reportEvent(Events.CALDAV_ACCOUNT_ADDED);
setResult(RESULT_OK);
finish();
private void addAccount(Pair<UserInfo, String> userInfoAndToken) {
caldavAccount = new CaldavAccount();
caldavAccount.setAccountType(CaldavAccount.TYPE_ETESYNC);
caldavAccount.setUuid(UUIDHelper.newUUID());
applyTo(caldavAccount, userInfoAndToken);
}
private void updateAccount(Pair<String, String> authentication) {
applyTo(caldavAccount, authentication);
private void updateAccount(Pair<UserInfo, String> userInfoAndToken) {
caldavAccount.setError("");
caldavDao.update(caldavAccount);
setResult(RESULT_OK);
finish();
applyTo(caldavAccount, userInfoAndToken);
}
private void applyTo(CaldavAccount account, @Nullable Pair<String, String> authentication) {
private void applyTo(CaldavAccount account, Pair<UserInfo, String> userInfoAndToken) {
hideProgressIndicator();
account.setName(getNewName());
account.setUrl(getNewURL());
account.setUsername(getNewUsername());
if (authentication != null) {
account.setPassword(encryption.encrypt(authentication.first));
account.setEncryptionKey(encryption.encrypt(authentication.second));
String token = userInfoAndToken.second;
if (!token.equals(account.getPassword(encryption))) {
account.setPassword(encryption.encrypt(token));
}
UserInfo userInfo = userInfoAndToken.first;
if (testUserInfo(userInfo)) {
saveAccountAndFinish();
} else {
Intent intent = new Intent(this, EncryptionSettingsActivity.class);
intent.putExtra(EncryptionSettingsActivity.EXTRA_USER_INFO, toJson(userInfo));
intent.putExtra(EncryptionSettingsActivity.EXTRA_ACCOUNT, account);
startActivityForResult(intent, REQUEST_ENCRYPTION_PASSWORD);
}
}
@Override
protected boolean needsValidation() {
return super.needsValidation() || encryptionPasswordChanged();
private boolean testUserInfo(UserInfo userInfo) {
String encryptionKey = caldavAccount.getEncryptionPassword(encryption);
if (userInfo != null && !Strings.isNullOrEmpty(encryptionKey)) {
try {
CryptoManager cryptoManager =
new CryptoManager(userInfo.getVersion(), encryptionKey, "userInfo");
userInfo.verify(cryptoManager);
return true;
} catch (IntegrityException | VersionTooNewException e) {
Timber.e(e);
}
}
return false;
}
private String toJson(UserInfo userInfo) {
return GsonHelper.gson.toJson(userInfo);
}
@OnCheckedChanged(R.id.show_advanced)
void toggleUrl() {
updateUrlVisibility();
}
private void updateUrlVisibility() {
binding.urlLayout.setVisibility(binding.showAdvanced.isChecked() ? View.VISIBLE : View.GONE);
}
protected boolean encryptionPasswordChanged() {
return caldavAccount == null
|| !PASSWORD_MASK.equals(binding.encryptionPassword.getText().toString().trim());
@Override
protected boolean needsValidation() {
return super.needsValidation() || Strings.isNullOrEmpty(caldavAccount.getEncryptionKey());
}
@Override
protected void addAccount(String url, String username, String password) {
addAccountViewModel.addAccount(
playServices, context, eteSyncClient, url, username, password, getNewEncryptionPassword());
addAccountViewModel.addAccount(playServices, context, eteSyncClient, url, username, password);
}
@Override
protected void updateAccount(String url, String username, String password) {
updateAccountViewModel.updateAccount(
eteSyncClient, url, username, password, getNewEncryptionPassword());
eteSyncClient,
url,
username,
PASSWORD_MASK.equals(password) ? null : password,
caldavAccount.getPassword(encryption));
}
@Override
protected void updateAccount() {
updateAccount(null);
caldavAccount.setName(getNewName());
saveAccountAndFinish();
}
@Override
protected String getNewURL() {
String url = super.getNewURL();
return Strings.isNullOrEmpty(url) ? getString(R.string.etesync_url) : url;
}
@Override
protected String getNewPassword() {
return binding.password.getText().toString().trim();
}
@Override
@ -119,14 +165,27 @@ public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsAct
component.inject(this);
}
@OnTextChanged(R.id.encryption_password)
void onEncryptionPasswordChanged(CharSequence text) {
binding.encryptionPasswordLayout.setError(null);
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_ENCRYPTION_PASSWORD) {
if (resultCode == RESULT_OK) {
String key = data.getStringExtra(EncryptionSettingsActivity.EXTRA_DERIVED_KEY);
caldavAccount.setEncryptionKey(encryption.encrypt(key));
saveAccountAndFinish();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@OnFocusChange(R.id.encryption_password)
void onEncryptionPasswordFocused(boolean hasFocus) {
changePasswordFocus(binding.encryptionPassword, hasFocus);
private void saveAccountAndFinish() {
if (caldavAccount.getId() == NO_ID) {
caldavDao.insert(caldavAccount);
} else {
caldavDao.update(caldavAccount);
}
setResult(RESULT_OK);
finish();
}
@Override

@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.cert4android.CustomCertManager.CustomHostnameVerifier;
import com.etesync.journalmanager.Crypto;
import com.etesync.journalmanager.Constants;
import com.etesync.journalmanager.Crypto.CryptoManager;
import com.etesync.journalmanager.Exceptions;
import com.etesync.journalmanager.Exceptions.HttpException;
@ -151,16 +151,15 @@ public class EteSyncClient {
foreground);
}
Pair<String, String> getKeyAndToken(String password)
throws IOException, Exceptions.HttpException, VersionTooNewException, IntegrityException {
Pair<UserInfo, String> getInfoAndToken(String password) throws IOException, HttpException {
JournalAuthenticator journalAuthenticator = new JournalAuthenticator(httpClient, httpUrl);
String token = journalAuthenticator.getAuthToken(username, password);
return Pair.create(getUserInfo(), token);
}
UserInfo getUserInfo() throws HttpException {
UserInfoManager userInfoManager = new UserInfoManager(httpClient, httpUrl);
UserInfo userInfo = userInfoManager.fetch(username);
String key = Crypto.deriveKey(username, encryptionPassword);
CryptoManager cryptoManager = new CryptoManager(userInfo.getVersion(), key, "userInfo");
userInfo.verify(cryptoManager);
return Pair.create(token, key);
return userInfoManager.fetch(username);
}
CryptoManager getCrypto(Journal journal) throws VersionTooNewException, IntegrityException {
@ -185,8 +184,13 @@ public class EteSyncClient {
Map<Journal, CollectionInfo> result = new HashMap<>();
for (Journal journal : journalManager.list()) {
CollectionInfo collection = convertJournalToCollection(journal);
if (collection != null && TYPE_TASKS.equals(collection.getType())) {
result.put(journal, collection);
if (collection != null) {
if (TYPE_TASKS.equals(collection.getType())) {
Timber.v("Found %s", collection);
result.put(journal, collection);
} else {
Timber.v("Ignoring %s", collection);
}
}
}
return result;
@ -222,7 +226,7 @@ public class EteSyncClient {
void invalidateToken() {
try {
new JournalAuthenticator(httpClient, httpUrl).invalidateAuthToken(token);
new JournalAuthenticator(httpClient, httpUrl).invalidateAuthToken(token);
} catch (Exception e) {
Timber.e(e);
}
@ -244,4 +248,12 @@ public class EteSyncClient {
void deleteCollection(CaldavCalendar calendar) throws HttpException {
journalManager.delete(Journal.fakeWithUid(calendar.getUrl()));
}
void createUserInfo(String derivedKey)
throws HttpException, VersionTooNewException, IntegrityException, IOException {
CryptoManager cryptoManager =
new CryptoManager(Constants.CURRENT_VERSION, derivedKey, "userInfo");
UserInfo userInfo = UserInfo.generate(cryptoManager, username);
new UserInfoManager(httpClient, httpUrl).create(userInfo);
}
}

@ -1,21 +1,25 @@
package org.tasks.etesync;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.etesync.journalmanager.UserInfoManager.UserInfo;
import com.google.common.base.Strings;
import org.tasks.ui.CompletableViewModel;
@SuppressWarnings("WeakerAccess")
public class UpdateEteSyncAccountViewModel extends CompletableViewModel<Pair<String, String>> {
public class UpdateEteSyncAccountViewModel extends CompletableViewModel<Pair<UserInfo, String>> {
void updateAccount(
EteSyncClient client,
String url,
String username,
String password,
String encryptionPassword) {
@Nullable String password,
@Nullable String token) {
run(
() ->
client
.setForeground()
.forUrl(url, username, encryptionPassword, null)
.getKeyAndToken(password));
() -> {
EteSyncClient newClient = client.setForeground().forUrl(url, username, null, token);
return Strings.isNullOrEmpty(password)
? Pair.create(newClient.getUserInfo(), token)
: newClient.getInfoAndToken(password);
});
}
}

@ -19,6 +19,7 @@ import org.tasks.activities.DatePickerActivity;
import org.tasks.activities.FilterSelectionActivity;
import org.tasks.activities.FilterSettingsActivity;
import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.etesync.EncryptionSettingsActivity;
import org.tasks.etesync.EteSyncAccountSettingsActivity;
import org.tasks.etesync.EteSyncCalendarSettingsActivity;
import org.tasks.tags.TagPickerActivity;
@ -152,4 +153,6 @@ public interface ActivityComponent {
void inject(TagPickerViewModel viewModel);
void inject(EteSyncCalendarSettingsActivity eteSyncCalendarSettingsActivity);
void inject(EncryptionSettingsActivity encryptionSettingsActivity);
}

@ -29,6 +29,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:orientation="vertical">
@ -47,19 +48,6 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_layout"
style="@style/TagSettingsRow">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/url"
android:imeOptions="flagNoExtractUi" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/user_layout"
style="@style/TagSettingsRow">
@ -76,7 +64,7 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_layout"
style="@style/TagSettingsRow"
app:passwordToggleEnabled="true">
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
@ -88,27 +76,28 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/encryption_password_layout"
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/show_advanced"
style="@style/TagSettingsRow"
android:visibility="gone"
app:passwordToggleEnabled="true">
android:text="@string/show_advanced_settings"
android:visibility="gone" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_layout"
style="@style/TagSettingsRow">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/encryption_password"
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/encryption_password"
android:imeOptions="flagNoExtractUi"
android:inputType="textPassword" />
android:hint="@string/url"
android:imeOptions="flagNoExtractUi" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/repeat"
style="@style/TagSettingsRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/caldav_account_repeating_tasks" />
</LinearLayout>

@ -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>

@ -379,6 +379,7 @@ File %1$s contained %2$s.\n\n
<string name="username_required">Username required</string>
<string name="password_required">Password required</string>
<string name="encryption_password_required">Encryption password required</string>
<string name="passwords_do_not_match">Passwords do not match</string>
<string name="url_required">URL required</string>
<string name="url_host_name_required">Host name required</string>
<string name="url_invalid_scheme">Must begin with http(s)://</string>
@ -448,6 +449,7 @@ File %1$s contained %2$s.\n\n
<string name="user">User</string>
<string name="password">Password</string>
<string name="encryption_password">Encryption password</string>
<string name="reenter_encryption_password">Confirm encryption password</string>
<string name="url">URL</string>
<string name="error_adding_account">Error: %s</string>
<string name="calendar_settings">Calendar settings</string>
@ -571,4 +573,5 @@ File %1$s contained %2$s.\n\n
<string name="google_tasks_selection_description">Basic service that synchronizes with your Google account</string>
<string name="caldav_selection_description">Synchronization based on open internet standards</string>
<string name="etesync_selection_description">Open source, end-to-end encrypted synchronization</string>
<string name="show_advanced_settings">Show advanced settings</string>
</resources>

Loading…
Cancel
Save