Extract code to find server settings via providers.xml

This commit is contained in:
cketti 2019-03-10 20:03:18 +01:00
parent 0c9bb833e5
commit 22798072fd
11 changed files with 249 additions and 140 deletions

View file

@ -0,0 +1,5 @@
package com.fsck.k9.autodiscovery
import com.fsck.k9.mail.ServerSettings
data class ConnectionSettings(val incoming: ServerSettings, val outgoing: ServerSettings)

View file

@ -0,0 +1,5 @@
package com.fsck.k9.autodiscovery
interface ConnectionSettingsDiscovery {
fun discover(email: String): ConnectionSettings?
}

View file

@ -0,0 +1,10 @@
package com.fsck.k9.autodiscovery
import com.fsck.k9.autodiscovery.providersxml.ProvidersXmlDiscovery
import com.fsck.k9.autodiscovery.providersxml.ProvidersXmlProvider
import org.koin.dsl.module.applicationContext
val autodiscoveryModule = applicationContext {
factory { ProvidersXmlProvider(get()) }
factory { ProvidersXmlDiscovery(get(), get()) }
}

View file

@ -0,0 +1,132 @@
package com.fsck.k9.autodiscovery.providersxml;
import java.net.URI;
import java.net.URISyntaxException;
import android.content.res.XmlResourceParser;
import com.fsck.k9.autodiscovery.ConnectionSettings;
import com.fsck.k9.autodiscovery.ConnectionSettingsDiscovery;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.mail.ServerSettings;
import org.xmlpull.v1.XmlPullParser;
import timber.log.Timber;
public class ProvidersXmlDiscovery implements ConnectionSettingsDiscovery {
private final BackendManager backendManager;
private final ProvidersXmlProvider xmlProvider;
public ProvidersXmlDiscovery(BackendManager backendManager, ProvidersXmlProvider xmlProvider) {
this.backendManager = backendManager;
this.xmlProvider = xmlProvider;
}
private String[] splitEmail(String email) {
String[] retParts = new String[2];
String[] emailParts = email.split("@");
retParts[0] = (emailParts.length > 0) ? emailParts[0] : "";
retParts[1] = (emailParts.length > 1) ? emailParts[1] : "";
return retParts;
}
@Override
public ConnectionSettings discover(String email) {
String password = "";
String[] emailParts = splitEmail(email);
String user = emailParts[0];
String domain = emailParts[1];
Provider mProvider = findProviderForDomain(domain);
if (mProvider == null) {
return null;
}
try {
String userEnc = UrlEncodingHelper.encodeUtf8(user);
String passwordEnc = UrlEncodingHelper.encodeUtf8(password);
String incomingUsername = mProvider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email);
incomingUsername = incomingUsername.replaceAll("\\$user", userEnc);
incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
URI incomingUriTemplate = mProvider.incomingUriTemplate;
URI incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":" + passwordEnc,
incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), null, null, null);
String outgoingUsername = mProvider.outgoingUsernameTemplate;
URI outgoingUriTemplate = mProvider.outgoingUriTemplate;
URI outgoingUri;
if (outgoingUsername != null) {
outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
outgoingUsername = outgoingUsername.replaceAll("\\$user", userEnc);
outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
+ passwordEnc, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
} else {
outgoingUri = new URI(outgoingUriTemplate.getScheme(),
null, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
}
ServerSettings incomingSettings = backendManager.decodeStoreUri(incomingUri.toString());
ServerSettings outgoingSettings = backendManager.decodeTransportUri(outgoingUri.toString());
return new ConnectionSettings(incomingSettings, outgoingSettings);
} catch (URISyntaxException use) {
return null;
}
}
private Provider findProviderForDomain(String domain) {
try {
XmlResourceParser xml = xmlProvider.getXml();
int xmlEventType;
Provider provider = null;
while ((xmlEventType = xml.next()) != XmlPullParser.END_DOCUMENT) {
if (xmlEventType == XmlPullParser.START_TAG &&
"provider".equals(xml.getName()) &&
domain.equalsIgnoreCase(xml.getAttributeValue(null, "domain"))) {
provider = new Provider();
provider.id = xml.getAttributeValue(null, "id");
provider.label = xml.getAttributeValue(null, "label");
provider.domain = xml.getAttributeValue(null, "domain");
} else if (xmlEventType == XmlPullParser.START_TAG &&
"incoming".equals(xml.getName()) &&
provider != null) {
provider.incomingUriTemplate = new URI(xml.getAttributeValue(null, "uri"));
provider.incomingUsernameTemplate = xml.getAttributeValue(null, "username");
} else if (xmlEventType == XmlPullParser.START_TAG &&
"outgoing".equals(xml.getName()) &&
provider != null) {
provider.outgoingUriTemplate = new URI(xml.getAttributeValue(null, "uri"));
provider.outgoingUsernameTemplate = xml.getAttributeValue(null, "username");
} else if (xmlEventType == XmlPullParser.END_TAG &&
"provider".equals(xml.getName()) &&
provider != null) {
return provider;
}
}
} catch (Exception e) {
Timber.e(e, "Error while trying to load provider settings.");
}
return null;
}
static class Provider {
String id;
String label;
String domain;
URI incomingUriTemplate;
String incomingUsernameTemplate;
URI outgoingUriTemplate;
String outgoingUsernameTemplate;
}
}

View file

@ -0,0 +1,11 @@
package com.fsck.k9.autodiscovery.providersxml
import android.content.Context
import android.content.res.XmlResourceParser
import com.fsck.k9.autodiscovery.R
class ProvidersXmlProvider(private val context: Context) {
fun getXml(): XmlResourceParser {
return context.resources.getXml(R.xml.providers)
}
}

View file

@ -0,0 +1,53 @@
package com.fsck.k9.autodiscovery.providersxml
import com.fsck.k9.RobolectricTest
import com.fsck.k9.backend.BackendManager
import com.fsck.k9.backend.imap.ImapStoreUriDecoder
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.transport.smtp.SmtpTransportUriDecoder
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockito_kotlin.doAnswer
import com.nhaarman.mockito_kotlin.mock
import org.junit.Test
import org.mockito.ArgumentMatchers.anyString
import org.robolectric.RuntimeEnvironment
class ProvidersXmlDiscoveryTest : RobolectricTest() {
val backendManager = mock<BackendManager> {
on { decodeStoreUri(anyString()) } doAnswer { mock -> ImapStoreUriDecoder.decode(mock.getArgument(0)) }
on { decodeTransportUri(anyString()) } doAnswer { mock ->
SmtpTransportUriDecoder.decodeSmtpUri(mock.getArgument(0))
}
}
val xmlProvider = ProvidersXmlProvider(RuntimeEnvironment.application)
val providersXmlDiscovery = ProvidersXmlDiscovery(backendManager, xmlProvider)
@Test
fun discover_withGmailDomain_shouldReturnCorrectSettings() {
val connectionSettings = providersXmlDiscovery.discover("user@gmail.com")
assertThat(connectionSettings).isNotNull()
with(connectionSettings!!.incoming) {
assertThat(host).isEqualTo("imap.gmail.com")
assertThat(connectionSecurity).isEqualTo(ConnectionSecurity.SSL_TLS_REQUIRED)
assertThat(authenticationType).isEqualTo(AuthType.PLAIN)
assertThat(username).isEqualTo("user@gmail.com")
}
with(connectionSettings.outgoing) {
assertThat(host).isEqualTo("smtp.gmail.com")
assertThat(connectionSecurity).isEqualTo(ConnectionSecurity.SSL_TLS_REQUIRED)
assertThat(authenticationType).isEqualTo(AuthType.PLAIN)
assertThat(username).isEqualTo("user@gmail.com")
}
}
@Test
fun discover_withUnknownDomain_shouldReturnNull() {
val connectionSettings = providersXmlDiscovery.discover("user@not.present.in.providers.xml.example")
assertThat(connectionSettings).isNull()
}
}

View file

@ -0,0 +1 @@
mock-maker-inline

View file

@ -9,6 +9,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"
implementation project(":app:core")
implementation project(":app:autodiscovery")
implementation project(":mail:common")
//TODO: Remove AccountSetupIncoming's dependency on these

View file

@ -2,6 +2,7 @@ package com.fsck.k9
import com.fsck.k9.account.accountModule
import com.fsck.k9.activity.activityModule
import com.fsck.k9.autodiscovery.autodiscoveryModule
import com.fsck.k9.contacts.contactsModule
import com.fsck.k9.fragment.fragmentModule
import com.fsck.k9.ui.endtoend.endToEndUiModule
@ -15,5 +16,6 @@ val uiModules = listOf(
endToEndUiModule,
fragmentModule,
contactsModule,
accountModule
accountModule,
autodiscoveryModule
)

View file

@ -1,12 +1,8 @@
package com.fsck.k9.activity.setup;
import java.net.URI;
import java.net.URISyntaxException;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
@ -27,8 +23,10 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.account.AccountCreator;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.autodiscovery.ConnectionSettings;
import com.fsck.k9.autodiscovery.providersxml.ProvidersXmlDiscovery;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.EmailHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
@ -53,6 +51,7 @@ public class AccountSetupBasics extends K9Activity
private final BackendManager backendManager = DI.get(BackendManager.class);
private final ProvidersXmlDiscovery providersXmlDiscovery = DI.get(ProvidersXmlDiscovery.class);
private EditText mEmailView;
private EditText mPasswordView;
@ -236,67 +235,30 @@ public class AccountSetupBasics extends K9Activity
return name;
}
private void finishAutoSetup(Provider provider) {
private void finishAutoSetup(ConnectionSettings connectionSettings) {
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
String[] emailParts = splitEmail(email);
String user = emailParts[0];
String domain = emailParts[1];
try {
String userEnc = UrlEncodingHelper.encodeUtf8(user);
String passwordEnc = UrlEncodingHelper.encodeUtf8(password);
String incomingUsername = provider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email);
incomingUsername = incomingUsername.replaceAll("\\$user", userEnc);
incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
URI incomingUriTemplate = provider.incomingUriTemplate;
URI incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":" + passwordEnc,
incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), null, null, null);
String outgoingUsername = provider.outgoingUsernameTemplate;
URI outgoingUriTemplate = provider.outgoingUriTemplate;
URI outgoingUri;
if (outgoingUsername != null) {
outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
outgoingUsername = outgoingUsername.replaceAll("\\$user", userEnc);
outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
+ passwordEnc, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
} else {
outgoingUri = new URI(outgoingUriTemplate.getScheme(),
null, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
}
if (mAccount == null) {
mAccount = Preferences.getPreferences(this).newAccount();
mAccount.setChipColor(AccountCreator.pickColor(this));
}
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
mAccount.setStoreUri(incomingUri.toString());
mAccount.setTransportUri(outgoingUri.toString());
ServerSettings incomingSettings = backendManager.decodeStoreUri(incomingUri.toString());
mAccount.setDeletePolicy(AccountCreator.getDefaultDeletePolicy(incomingSettings.type));
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
} catch (URISyntaxException use) {
/*
* If there is some problem with the URI we give up and go on to
* manual setup.
*/
onManualSetup();
if (mAccount == null) {
mAccount = Preferences.getPreferences(this).newAccount();
mAccount.setChipColor(AccountCreator.pickColor(this));
}
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
ServerSettings incomingServerSettings = connectionSettings.getIncoming().newPassword(password);
String storeUri = backendManager.createStoreUri(incomingServerSettings);
mAccount.setStoreUri(storeUri);
ServerSettings outgoingServerSettings = connectionSettings.getOutgoing().newPassword(password);
String transportUri = backendManager.createTransportUri(outgoingServerSettings);
mAccount.setTransportUri(transportUri);
mAccount.setDeletePolicy(AccountCreator.getDefaultDeletePolicy(incomingServerSettings.type));
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
}
private void onNext() {
@ -308,12 +270,10 @@ public class AccountSetupBasics extends K9Activity
}
String email = mEmailView.getText().toString();
String[] emailParts = splitEmail(email);
String domain = emailParts[1];
Provider provider = findProviderForDomain(domain);
if (provider != null) {
finishAutoSetup(provider);
ConnectionSettings connectionSettings = providersXmlDiscovery.discover(email);
if (connectionSettings != null) {
finishAutoSetup(connectionSettings);
} else {
// We don't have default settings for this account, start the manual setup process.
onManualSetup();
@ -340,8 +300,7 @@ public class AccountSetupBasics extends K9Activity
private void onManualSetup() {
String email = mEmailView.getText().toString();
String[] emailParts = splitEmail(email);
String domain = emailParts[1];
String domain = EmailHelper.getDomainFromEmailAddress(email);
String password = null;
String clientCertificateAlias = null;
@ -385,74 +344,4 @@ public class AccountSetupBasics extends K9Activity
onManualSetup();
}
}
/**
* Attempts to get the given attribute as a String resource first, and if it fails
* returns the attribute as a simple String value.
* @param xml
* @param name
* @return
*/
private String getXmlAttribute(XmlResourceParser xml, String name) {
int resId = xml.getAttributeResourceValue(null, name, 0);
if (resId == 0) {
return xml.getAttributeValue(null, name);
} else {
return getString(resId);
}
}
private Provider findProviderForDomain(String domain) {
try {
XmlResourceParser xml = getResources().getXml(R.xml.providers);
int xmlEventType;
Provider provider = null;
while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
if (xmlEventType == XmlResourceParser.START_TAG
&& "provider".equals(xml.getName())
&& domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) {
provider = new Provider();
provider.id = getXmlAttribute(xml, "id");
provider.label = getXmlAttribute(xml, "label");
provider.domain = getXmlAttribute(xml, "domain");
} else if (xmlEventType == XmlResourceParser.START_TAG
&& "incoming".equals(xml.getName())
&& provider != null) {
provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
provider.incomingUsernameTemplate = getXmlAttribute(xml, "username");
} else if (xmlEventType == XmlResourceParser.START_TAG
&& "outgoing".equals(xml.getName())
&& provider != null) {
provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username");
} else if (xmlEventType == XmlResourceParser.END_TAG
&& "provider".equals(xml.getName())
&& provider != null) {
return provider;
}
}
} catch (Exception e) {
Timber.e(e, "Error while trying to load provider settings.");
}
return null;
}
private String[] splitEmail(String email) {
String[] retParts = new String[2];
String[] emailParts = email.split("@");
retParts[0] = (emailParts.length > 0) ? emailParts[0] : "";
retParts[1] = (emailParts.length > 1) ? emailParts[1] : "";
return retParts;
}
static class Provider {
public String id;
public String label;
public String domain;
public URI incomingUriTemplate;
public String incomingUsernameTemplate;
public URI outgoingUriTemplate;
public String outgoingUsernameTemplate;
}
}