mirror of https://github.com/tasks/tasks
Add EteSyncAccountSettingsActivity
parent
f97df9e884
commit
744a673c7b
@ -0,0 +1,29 @@
|
|||||||
|
package org.tasks.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
import com.etesync.journalmanager.Crypto.AsymmetricKeyPair;
|
||||||
|
import org.tasks.etesync.EteSyncClient;
|
||||||
|
import org.tasks.gtasks.PlayServices;
|
||||||
|
import org.tasks.ui.CompletableViewModel;
|
||||||
|
|
||||||
|
public class AddEteSyncAccountViewModel
|
||||||
|
extends CompletableViewModel<Pair<String, String>> {
|
||||||
|
public void addAccount(
|
||||||
|
PlayServices playServices,
|
||||||
|
Context context,
|
||||||
|
EteSyncClient client,
|
||||||
|
String url,
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
String encryptionPassword) {
|
||||||
|
run(
|
||||||
|
() -> {
|
||||||
|
playServices.updateSecurityProvider(context);
|
||||||
|
return client
|
||||||
|
.setForeground()
|
||||||
|
.forUrl(url, username, encryptionPassword, null)
|
||||||
|
.getKeyAndToken(password);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.tasks.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
import org.tasks.etesync.EteSyncClient;
|
||||||
|
import org.tasks.ui.CompletableViewModel;
|
||||||
|
|
||||||
|
public class UpdateEteSyncAccountViewModel extends CompletableViewModel<Pair<String, String>> {
|
||||||
|
public void addAccount(
|
||||||
|
Context context,
|
||||||
|
EteSyncClient client,
|
||||||
|
String url,
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
String encryptionPassword) {
|
||||||
|
run(
|
||||||
|
() ->
|
||||||
|
client
|
||||||
|
.setForeground()
|
||||||
|
.forUrl(url, username, encryptionPassword, null)
|
||||||
|
.getKeyAndToken(password));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package org.tasks.etesync;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
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 com.todoroo.astrid.helper.UUIDHelper;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import org.tasks.R;
|
||||||
|
import org.tasks.activities.AddEteSyncAccountViewModel;
|
||||||
|
import org.tasks.activities.UpdateEteSyncAccountViewModel;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsActivity
|
||||||
|
implements Toolbar.OnMenuItemClickListener {
|
||||||
|
|
||||||
|
@Inject @ForApplication Context context;
|
||||||
|
@Inject PlayServices playServices;
|
||||||
|
@Inject EteSyncClient eteSyncClient;
|
||||||
|
|
||||||
|
private AddEteSyncAccountViewModel addAccountViewModel;
|
||||||
|
private UpdateEteSyncAccountViewModel updateAccountViewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
binding.repeat.setVisibility(View.GONE);
|
||||||
|
binding.encryptionPasswordLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
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 updateAccount(Pair<String, String> authentication) {
|
||||||
|
applyTo(caldavAccount, authentication);
|
||||||
|
caldavAccount.setError("");
|
||||||
|
caldavDao.update(caldavAccount);
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTo(CaldavAccount account, @Nullable Pair<String, String> authentication) {
|
||||||
|
account.setName(getNewName());
|
||||||
|
account.setUrl(getNewURL());
|
||||||
|
account.setUsername(getNewUsername());
|
||||||
|
if (authentication != null) {
|
||||||
|
account.setPassword(encryption.encrypt(authentication.first));
|
||||||
|
account.setEncryptionKey(encryption.encrypt(authentication.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean needsValidation() {
|
||||||
|
return super.needsValidation() || encryptionPasswordChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean encryptionPasswordChanged() {
|
||||||
|
return caldavAccount == null
|
||||||
|
|| !PASSWORD_MASK.equals(binding.encryptionPassword.getText().toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addAccount(String url, String username, String password) {
|
||||||
|
addAccountViewModel.addAccount(
|
||||||
|
playServices, context, eteSyncClient, url, username, password, getNewEncryptionPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateAccount(String url, String username, String password) {
|
||||||
|
updateAccountViewModel.addAccount(
|
||||||
|
context, eteSyncClient, url, username, password, getNewEncryptionPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateAccount() {
|
||||||
|
updateAccount(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inject(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnTextChanged(R.id.encryption_password)
|
||||||
|
void onEncryptionPasswordChanged(CharSequence text) {
|
||||||
|
binding.encryptionPasswordLayout.setError(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnFocusChange(R.id.encryption_password)
|
||||||
|
void onEncryptionPasswordFocused(boolean hasFocus) {
|
||||||
|
changePasswordFocus(binding.encryptionPassword, hasFocus);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
package org.tasks.etesync;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
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.Crypto.CryptoManager;
|
||||||
|
import com.etesync.journalmanager.Exceptions;
|
||||||
|
import com.etesync.journalmanager.Exceptions.IntegrityException;
|
||||||
|
import com.etesync.journalmanager.Exceptions.VersionTooNewException;
|
||||||
|
import com.etesync.journalmanager.JournalAuthenticator;
|
||||||
|
import com.etesync.journalmanager.JournalManager;
|
||||||
|
import com.etesync.journalmanager.JournalManager.Journal;
|
||||||
|
import com.etesync.journalmanager.UserInfoManager;
|
||||||
|
import com.etesync.journalmanager.UserInfoManager.UserInfo;
|
||||||
|
import com.etesync.journalmanager.model.CollectionInfo;
|
||||||
|
import com.etesync.journalmanager.util.TokenAuthenticator;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.OkHttpClient.Builder;
|
||||||
|
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||||
|
import org.tasks.DebugNetworkInterceptor;
|
||||||
|
import org.tasks.caldav.MemoryCookieStore;
|
||||||
|
import org.tasks.injection.ForApplication;
|
||||||
|
import org.tasks.preferences.Preferences;
|
||||||
|
import org.tasks.security.Encryption;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class EteSyncClient {
|
||||||
|
|
||||||
|
private final Encryption encryption;
|
||||||
|
private final Preferences preferences;
|
||||||
|
private final DebugNetworkInterceptor interceptor;
|
||||||
|
private final String username;
|
||||||
|
private final String encryptionPassword;
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
private final HttpUrl httpUrl;
|
||||||
|
private final Context context;
|
||||||
|
private final JournalManager journalManager;
|
||||||
|
private boolean foreground;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public EteSyncClient(
|
||||||
|
@ForApplication Context context,
|
||||||
|
Encryption encryption,
|
||||||
|
Preferences preferences,
|
||||||
|
DebugNetworkInterceptor interceptor) {
|
||||||
|
this.context = context;
|
||||||
|
this.encryption = encryption;
|
||||||
|
this.preferences = preferences;
|
||||||
|
this.interceptor = interceptor;
|
||||||
|
username = null;
|
||||||
|
encryptionPassword = null;
|
||||||
|
httpClient = null;
|
||||||
|
httpUrl = null;
|
||||||
|
journalManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EteSyncClient(
|
||||||
|
Context context,
|
||||||
|
Encryption encryption,
|
||||||
|
Preferences preferences,
|
||||||
|
DebugNetworkInterceptor interceptor,
|
||||||
|
String url,
|
||||||
|
String username,
|
||||||
|
String encryptionPassword,
|
||||||
|
String token,
|
||||||
|
boolean foreground)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException {
|
||||||
|
this.context = context;
|
||||||
|
this.encryption = encryption;
|
||||||
|
this.preferences = preferences;
|
||||||
|
this.interceptor = interceptor;
|
||||||
|
this.username = username;
|
||||||
|
this.encryptionPassword = encryptionPassword;
|
||||||
|
|
||||||
|
CustomCertManager customCertManager = new CustomCertManager(context);
|
||||||
|
customCertManager.setAppInForeground(foreground);
|
||||||
|
CustomHostnameVerifier hostnameVerifier =
|
||||||
|
customCertManager.hostnameVerifier(OkHostnameVerifier.INSTANCE);
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(null, new TrustManager[] {customCertManager}, null);
|
||||||
|
|
||||||
|
Builder builder =
|
||||||
|
new OkHttpClient()
|
||||||
|
.newBuilder()
|
||||||
|
.addNetworkInterceptor(new TokenAuthenticator(null, token))
|
||||||
|
.cookieJar(new MemoryCookieStore())
|
||||||
|
.followRedirects(false)
|
||||||
|
.followSslRedirects(true)
|
||||||
|
.sslSocketFactory(sslContext.getSocketFactory(), customCertManager)
|
||||||
|
.hostnameVerifier(hostnameVerifier)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS);
|
||||||
|
if (preferences.isFlipperEnabled()) {
|
||||||
|
interceptor.add(builder);
|
||||||
|
}
|
||||||
|
httpClient = builder.build();
|
||||||
|
httpUrl = HttpUrl.parse(url);
|
||||||
|
journalManager = new JournalManager(httpClient, httpUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EteSyncClient forUrl(String url, String username, String encryptionPassword, String token)
|
||||||
|
throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
|
return new EteSyncClient(
|
||||||
|
context,
|
||||||
|
encryption,
|
||||||
|
preferences,
|
||||||
|
interceptor,
|
||||||
|
url,
|
||||||
|
username,
|
||||||
|
encryptionPassword,
|
||||||
|
token,
|
||||||
|
foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<String, String> getKeyAndToken(String password)
|
||||||
|
throws IOException, Exceptions.HttpException, VersionTooNewException, IntegrityException {
|
||||||
|
JournalAuthenticator journalAuthenticator = new JournalAuthenticator(httpClient, httpUrl);
|
||||||
|
String token = journalAuthenticator.getAuthToken(username, password);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CryptoManager getCrypto(Journal journal)
|
||||||
|
throws VersionTooNewException, IntegrityException {
|
||||||
|
return new CryptoManager(journal.getVersion(), encryptionPassword, journal.getUid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable CollectionInfo convertJournalToCollection(Journal journal) {
|
||||||
|
try {
|
||||||
|
CryptoManager cryptoManager = getCrypto(journal);
|
||||||
|
journal.verify(cryptoManager);
|
||||||
|
CollectionInfo collection =
|
||||||
|
CollectionInfo.Companion.fromJson(journal.getContent(cryptoManager));
|
||||||
|
collection.updateFromJournal(journal);
|
||||||
|
return collection;
|
||||||
|
} catch (IntegrityException | VersionTooNewException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Journal, CollectionInfo> getCalendars() throws Exceptions.HttpException {
|
||||||
|
Map<Journal, CollectionInfo> result = new HashMap<>();
|
||||||
|
for (Journal journal : journalManager.list()) {
|
||||||
|
CollectionInfo collection = convertJournalToCollection(journal);
|
||||||
|
if (collection != null && collection.getType().equals("TASKS")) {
|
||||||
|
result.put(journal, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EteSyncClient setForeground() {
|
||||||
|
foreground = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue