Added/storing auth provider id

pull/170/head
M66B 5 years ago
parent e80bc630e1
commit 02dfb75542

File diff suppressed because it is too large Load Diff

@ -56,7 +56,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 123, version = 124,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
@ -1196,6 +1196,14 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `identity` ADD COLUMN `fingerprint` TEXT"); db.execSQL("ALTER TABLE `identity` ADD COLUMN `fingerprint` TEXT");
} }
}) })
.addMigrations(new Migration(123, 124) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `account` ADD COLUMN `provider` TEXT");
db.execSQL("ALTER TABLE `identity` ADD COLUMN `provider` TEXT");
}
})
.build(); .build();
} }

@ -36,6 +36,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlPullParserFactory;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -57,6 +58,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; import java.util.concurrent.Future;
public class EmailProvider { public class EmailProvider {
public String id;
public String name; public String name;
public List<String> domain; public List<String> domain;
public int order; public int order;
@ -107,6 +109,7 @@ public class EmailProvider {
result = new ArrayList<>(); result = new ArrayList<>();
else if ("provider".equals(name)) { else if ("provider".equals(name)) {
provider = new EmailProvider(); provider = new EmailProvider();
provider.id = xml.getAttributeValue(null, "id");
provider.name = xml.getAttributeValue(null, "name"); provider.name = xml.getAttributeValue(null, "name");
String domain = xml.getAttributeValue(null, "domain"); String domain = xml.getAttributeValue(null, "domain");
if (domain != null) if (domain != null)
@ -170,6 +173,14 @@ public class EmailProvider {
return result; return result;
} }
static EmailProvider getProvider(Context context, String id) throws FileNotFoundException {
for (EmailProvider provider : loadProfiles(context))
if (id.equals(provider.id))
return provider;
throw new FileNotFoundException("provider id=" + id);
}
@NonNull @NonNull
static EmailProvider fromDomain(Context context, String domain, Discover discover) throws IOException { static EmailProvider fromDomain(Context context, String domain, Discover discover) throws IOException {
return fromEmail(context, domain, discover); return fromEmail(context, domain, discover);

@ -72,6 +72,7 @@ public class EntityAccount extends EntityOrder implements Serializable {
public Integer port; public Integer port;
@NonNull @NonNull
public Integer auth_type; // immutable public Integer auth_type; // immutable
public String provider;
@NonNull @NonNull
public String user; public String user;
@NonNull @NonNull

@ -72,6 +72,7 @@ public class EntityIdentity {
public Integer port; public Integer port;
@NonNull @NonNull
public Integer auth_type; public Integer auth_type;
public String provider;
@NonNull @NonNull
public String user; public String user;
@NonNull @NonNull

@ -143,6 +143,7 @@ public class FragmentAccount extends FragmentBase {
private long id = -1; private long id = -1;
private long copy = -1; private long copy = -1;
private int auth = MailService.AUTH_TYPE_PASSWORD; private int auth = MailService.AUTH_TYPE_PASSWORD;
private String provider = null;
private boolean saving = false; private boolean saving = false;
private static final int REQUEST_COLOR = 1; private static final int REQUEST_COLOR = 1;
@ -500,6 +501,7 @@ public class FragmentAccount extends FragmentBase {
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("provider", provider);
args.putString("user", etUser.getText().toString()); args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString()); args.putString("realm", etRealm.getText().toString());
@ -537,6 +539,7 @@ public class FragmentAccount extends FragmentBase {
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String provider = args.getString("provider");
String user = args.getString("user"); String user = args.getString("user");
String password = args.getString("password"); String password = args.getString("password");
String realm = args.getString("realm"); String realm = args.getString("realm");
@ -568,7 +571,7 @@ public class FragmentAccount extends FragmentBase {
// Check IMAP server / get folders // Check IMAP server / get folders
String protocol = "imap" + (starttls ? "" : "s"); String protocol = "imap" + (starttls ? "" : "s");
try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) { try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) {
iservice.connect(host, Integer.parseInt(port), auth, user, password, fingerprint); iservice.connect(host, Integer.parseInt(port), auth, provider, user, password, fingerprint);
result.idle = iservice.hasCapability("IDLE"); result.idle = iservice.hasCapability("IDLE");
@ -707,6 +710,7 @@ public class FragmentAccount extends FragmentBase {
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("provider", provider);
args.putString("user", etUser.getText().toString()); args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString()); args.putString("realm", etRealm.getText().toString());
@ -767,6 +771,7 @@ public class FragmentAccount extends FragmentBase {
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String provider = args.getString("provider");
String user = args.getString("user").trim(); String user = args.getString("user").trim();
String password = args.getString("password"); String password = args.getString("password");
String realm = args.getString("realm"); String realm = args.getString("realm");
@ -942,7 +947,7 @@ public class FragmentAccount extends FragmentBase {
if (check) { if (check) {
String protocol = "imap" + (starttls ? "" : "s"); String protocol = "imap" + (starttls ? "" : "s");
try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) { try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) {
iservice.connect(host, Integer.parseInt(port), auth, user, password, fingerprint); iservice.connect(host, Integer.parseInt(port), auth, provider, user, password, fingerprint);
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) { for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
// Check folder attributes // Check folder attributes
@ -989,6 +994,7 @@ public class FragmentAccount extends FragmentBase {
account.user = user; account.user = user;
account.password = password; account.password = password;
} }
account.provider = provider;
account.realm = realm; account.realm = realm;
account.fingerprint = fingerprint; account.fingerprint = fingerprint;
@ -1213,6 +1219,7 @@ public class FragmentAccount extends FragmentBase {
outState.putString("fair:password", tilPassword.getEditText().getText().toString()); outState.putString("fair:password", tilPassword.getEditText().getText().toString());
outState.putInt("fair:advanced", grpAdvanced.getVisibility()); outState.putInt("fair:advanced", grpAdvanced.getVisibility());
outState.putInt("fair:auth", auth); outState.putInt("fair:auth", auth);
outState.putString("fair:authprovider", provider);
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }
@ -1300,6 +1307,7 @@ public class FragmentAccount extends FragmentBase {
cbUseDate.setChecked(account == null ? false : account.use_date); cbUseDate.setChecked(account == null ? false : account.use_date);
auth = (account == null ? MailService.AUTH_TYPE_PASSWORD : account.auth_type); auth = (account == null ? MailService.AUTH_TYPE_PASSWORD : account.auth_type);
provider = (account == null ? null : account.provider);
new SimpleTask<EntityAccount>() { new SimpleTask<EntityAccount>() {
@Override @Override
@ -1319,13 +1327,14 @@ public class FragmentAccount extends FragmentBase {
} }
}.execute(FragmentAccount.this, new Bundle(), "account:primary"); }.execute(FragmentAccount.this, new Bundle(), "account:primary");
} else { } else {
int provider = savedInstanceState.getInt("fair:provider"); int p = savedInstanceState.getInt("fair:provider");
spProvider.setTag(provider); spProvider.setTag(p);
spProvider.setSelection(provider); spProvider.setSelection(p);
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password")); tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced")); grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
auth = savedInstanceState.getInt("fair:auth"); auth = savedInstanceState.getInt("fair:auth");
provider = savedInstanceState.getString("fair:authprovider");
} }
Helper.setViewsEnabled(view, true); Helper.setViewsEnabled(view, true);

@ -284,7 +284,7 @@ public class FragmentGmail extends FragmentBase {
String aprotocol = provider.imap.starttls ? "imap" : "imaps"; String aprotocol = provider.imap.starttls ? "imap" : "imaps";
try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) { try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) {
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_GMAIL, user, password, null); iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_GMAIL, null, user, password, null);
folders = iservice.getFolders(); folders = iservice.getFolders();
@ -294,7 +294,7 @@ public class FragmentGmail extends FragmentBase {
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps"; String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) { try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) {
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_GMAIL, user, password, null); iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_GMAIL, null, user, password, null);
} }
DB db = DB.getInstance(context); DB db = DB.getInstance(context);

@ -125,6 +125,7 @@ public class FragmentIdentity extends FragmentBase {
private long copy = -1; private long copy = -1;
private long account = -1; private long account = -1;
private int auth = MailService.AUTH_TYPE_PASSWORD; private int auth = MailService.AUTH_TYPE_PASSWORD;
private String provider = null;
private boolean saving = false; private boolean saving = false;
private static final int REQUEST_COLOR = 1; private static final int REQUEST_COLOR = 1;
@ -253,7 +254,8 @@ public class FragmentIdentity extends FragmentBase {
} }
// Copy account credentials // Copy account credentials
auth = (account.auth_type == null ? MailService.AUTH_TYPE_PASSWORD : account.auth_type); auth = account.auth_type;
provider = account.provider;
etEmail.setText(account.user); etEmail.setText(account.user);
etUser.setText(account.user); etUser.setText(account.user);
tilPassword.getEditText().setText(account.password); tilPassword.getEditText().setText(account.password);
@ -519,6 +521,7 @@ public class FragmentIdentity extends FragmentBase {
args.putBoolean("insecure", cbInsecure.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked());
args.putString("port", etPort.getText().toString()); args.putString("port", etPort.getText().toString());
args.putInt("auth", auth); args.putInt("auth", auth);
args.putString("provider", provider);
args.putString("user", etUser.getText().toString()); args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString()); args.putString("realm", etRealm.getText().toString());
@ -567,6 +570,7 @@ public class FragmentIdentity extends FragmentBase {
boolean insecure = args.getBoolean("insecure"); boolean insecure = args.getBoolean("insecure");
String port = args.getString("port"); String port = args.getString("port");
int auth = args.getInt("auth"); int auth = args.getInt("auth");
String provider = args.getString("provider");
String user = args.getString("user").trim(); String user = args.getString("user").trim();
String password = args.getString("password"); String password = args.getString("password");
String realm = args.getString("realm"); String realm = args.getString("realm");
@ -741,7 +745,7 @@ public class FragmentIdentity extends FragmentBase {
String protocol = (starttls ? "smtp" : "smtps"); String protocol = (starttls ? "smtp" : "smtps");
try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) { try (MailService iservice = new MailService(context, protocol, realm, insecure, true, true)) {
iservice.setUseIp(use_ip); iservice.setUseIp(use_ip);
iservice.connect(host, Integer.parseInt(port), auth, user, password, fingerprint); iservice.connect(host, Integer.parseInt(port), auth, provider, user, password, fingerprint);
} }
} }
@ -775,6 +779,7 @@ public class FragmentIdentity extends FragmentBase {
identity.user = user; identity.user = user;
identity.password = password; identity.password = password;
} }
identity.provider = provider;
identity.realm = realm; identity.realm = realm;
identity.fingerprint = fingerprint; identity.fingerprint = fingerprint;
identity.use_ip = use_ip; identity.use_ip = use_ip;
@ -881,6 +886,7 @@ public class FragmentIdentity extends FragmentBase {
outState.putString("fair:password", tilPassword.getEditText().getText().toString()); outState.putString("fair:password", tilPassword.getEditText().getText().toString());
outState.putInt("fair:advanced", grpAdvanced.getVisibility()); outState.putInt("fair:advanced", grpAdvanced.getVisibility());
outState.putInt("fair:auth", auth); outState.putInt("fair:auth", auth);
outState.putString("fair:authprovider", provider);
outState.putString("fair:html", (String) etSignature.getTag()); outState.putString("fair:html", (String) etSignature.getTag());
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }
@ -941,6 +947,7 @@ public class FragmentIdentity extends FragmentBase {
etBcc.setText(identity == null ? null : identity.bcc); etBcc.setText(identity == null ? null : identity.bcc);
auth = (identity == null ? MailService.AUTH_TYPE_PASSWORD : identity.auth_type); auth = (identity == null ? MailService.AUTH_TYPE_PASSWORD : identity.auth_type);
provider = (identity == null ? null : identity.provider);
if (identity == null || copy > 0) if (identity == null || copy > 0)
new SimpleTask<Integer>() { new SimpleTask<Integer>() {
@ -963,6 +970,7 @@ public class FragmentIdentity extends FragmentBase {
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password")); tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced")); grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
auth = savedInstanceState.getInt("fair:auth"); auth = savedInstanceState.getInt("fair:auth");
provider = savedInstanceState.getString("fair:authprovider");
etSignature.setTag(savedInstanceState.getString("fair:html")); etSignature.setTag(savedInstanceState.getString("fair:html"));
} }

@ -75,6 +75,7 @@ import java.util.Map;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
public class FragmentOAuth extends FragmentBase { public class FragmentOAuth extends FragmentBase {
private String id;
private String name; private String name;
private ViewGroup view; private ViewGroup view;
@ -97,6 +98,7 @@ public class FragmentOAuth extends FragmentBase {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle args = getArguments(); Bundle args = getArguments();
id = args.getString("id");
name = args.getString("name"); name = args.getString("name");
} }
@ -133,7 +135,7 @@ public class FragmentOAuth extends FragmentBase {
tvGrantHint.setText(getString(R.string.title_setup_oauth_rationale, name)); tvGrantHint.setText(getString(R.string.title_setup_oauth_rationale, name));
pbOAuth.setVisibility(View.GONE); pbOAuth.setVisibility(View.GONE);
tvAuthorized.setVisibility(View.GONE); tvAuthorized.setVisibility(View.GONE);
tvGmailHint.setVisibility("Gmail".equals(name) ? View.VISIBLE : View.GONE); tvGmailHint.setVisibility("gmail".equals(id) ? View.VISIBLE : View.GONE);
hideError(); hideError();
return view; return view;
@ -186,61 +188,56 @@ public class FragmentOAuth extends FragmentBase {
pbOAuth.setVisibility(View.VISIBLE); pbOAuth.setVisibility(View.VISIBLE);
hideError(); hideError();
for (EmailProvider provider : EmailProvider.loadProfiles(getContext())) EmailProvider provider = EmailProvider.getProvider(getContext(), id);
if (provider.name.equals(name) && provider.oauth != null) {
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder() AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
.setBrowserMatcher(new BrowserMatcher() { .setBrowserMatcher(new BrowserMatcher() {
@Override @Override
public boolean matches(@NonNull BrowserDescriptor descriptor) { public boolean matches(@NonNull BrowserDescriptor descriptor) {
BrowserMatcher sbrowser = new VersionedBrowserMatcher( BrowserMatcher sbrowser = new VersionedBrowserMatcher(
Browsers.SBrowser.PACKAGE_NAME, Browsers.SBrowser.PACKAGE_NAME,
Browsers.SBrowser.SIGNATURE_SET, Browsers.SBrowser.SIGNATURE_SET,
true, true,
VersionRange.atMost("5.3")); VersionRange.atMost("5.3"));
return !sbrowser.matches(descriptor); return !sbrowser.matches(descriptor);
} }
}) })
.build(); .build();
AuthorizationService authService = new AuthorizationService(getContext(), appAuthConfig); AuthorizationService authService = new AuthorizationService(getContext(), appAuthConfig);
AuthorizationServiceConfiguration serviceConfig = new AuthorizationServiceConfiguration( AuthorizationServiceConfiguration serviceConfig = new AuthorizationServiceConfiguration(
Uri.parse(provider.oauth.authorizationEndpoint), Uri.parse(provider.oauth.authorizationEndpoint),
Uri.parse(provider.oauth.tokenEndpoint)); Uri.parse(provider.oauth.tokenEndpoint));
AuthState authState = new AuthState(serviceConfig); AuthState authState = new AuthState(serviceConfig);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putString("oauth." + provider.name, authState.jsonSerializeString()).apply(); prefs.edit().putString("oauth." + provider.id, authState.jsonSerializeString()).apply();
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
if ("Gmail".equals(provider.name)) if ("gmail".equals(provider.id))
params.put("access_type", "offline"); params.put("access_type", "offline");
AuthorizationRequest.Builder authRequestBuilder = AuthorizationRequest.Builder authRequestBuilder =
new AuthorizationRequest.Builder( new AuthorizationRequest.Builder(
serviceConfig, serviceConfig,
provider.oauth.clientId, provider.oauth.clientId,
ResponseTypeValues.CODE, ResponseTypeValues.CODE,
Uri.parse(provider.oauth.redirectUri)) Uri.parse(provider.oauth.redirectUri))
.setScopes(provider.oauth.scopes) .setScopes(provider.oauth.scopes)
.setState(provider.name) .setState(provider.id)
.setAdditionalParameters(params); .setAdditionalParameters(params);
if ("Gmail".equals(provider.name) && BuildConfig.DEBUG) if ("gmail".equals(provider.id) && BuildConfig.DEBUG)
authRequestBuilder.setPrompt("consent"); authRequestBuilder.setPrompt("consent");
AuthorizationRequest authRequest = authRequestBuilder.build(); AuthorizationRequest authRequest = authRequestBuilder.build();
Log.i("OAuth request provider=" + provider.name); Log.i("OAuth request provider=" + provider.id);
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.i("OAuth uri=" + authRequest.toUri()); Log.i("OAuth uri=" + authRequest.toUri());
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest); Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
startActivityForResult(authIntent, ActivitySetup.REQUEST_OAUTH); startActivityForResult(authIntent, ActivitySetup.REQUEST_OAUTH);
return;
}
throw new IllegalArgumentException("Unknown provider=" + name);
} catch (Throwable ex) { } catch (Throwable ex) {
showError(ex); showError(ex);
btnOAuth.setEnabled(true); btnOAuth.setEnabled(true);
@ -256,54 +253,51 @@ public class FragmentOAuth extends FragmentBase {
tvAuthorized.setVisibility(View.VISIBLE); tvAuthorized.setVisibility(View.VISIBLE);
for (final EmailProvider provider : EmailProvider.loadProfiles(getContext())) final EmailProvider provider = EmailProvider.getProvider(getContext(), auth.state);
if (provider.name.equals(auth.state)) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
final AuthState authState = AuthState.jsonDeserialize(prefs.getString("oauth." + provider.name, null)); String json = prefs.getString("oauth." + provider.id, null);
prefs.edit().remove("oauth." + provider.name).apply(); prefs.edit().remove("oauth." + provider.id).apply();
Log.i("OAuth get token provider=" + provider.name); final AuthState authState = AuthState.jsonDeserialize(json);
authState.update(auth, null);
if (BuildConfig.DEBUG) Log.i("OAuth get token provider=" + provider.id);
Log.i("OAuth response=" + authState.jsonSerializeString()); authState.update(auth, null);
if (BuildConfig.DEBUG)
AuthorizationService authService = new AuthorizationService(getContext()); Log.i("OAuth response=" + authState.jsonSerializeString());
ClientAuthentication clientAuth;
if (provider.oauth.clientSecret == null)
clientAuth = NoClientAuthentication.INSTANCE;
else
clientAuth = new ClientSecretPost(provider.oauth.clientSecret);
authService.performTokenRequest(
auth.createTokenExchangeRequest(),
clientAuth,
new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(TokenResponse access, AuthorizationException error) {
try {
if (access == null)
throw error;
Log.i("OAuth got token provider=" + provider.name);
authState.update(access, null);
if (BuildConfig.DEBUG)
Log.i("OAuth response=" + authState.jsonSerializeString());
if (TextUtils.isEmpty(access.refreshToken))
throw new IllegalStateException("No refresh token");
onOAuthorized(access.accessToken, authState);
} catch (Throwable ex) {
showError(ex);
}
}
});
return;
}
throw new IllegalArgumentException("Unknown state=" + auth.state); AuthorizationService authService = new AuthorizationService(getContext());
ClientAuthentication clientAuth;
if (provider.oauth.clientSecret == null)
clientAuth = NoClientAuthentication.INSTANCE;
else
clientAuth = new ClientSecretPost(provider.oauth.clientSecret);
authService.performTokenRequest(
auth.createTokenExchangeRequest(),
clientAuth,
new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(TokenResponse access, AuthorizationException error) {
try {
if (access == null)
throw error;
Log.i("OAuth got token provider=" + provider.id);
authState.update(access, null);
if (BuildConfig.DEBUG)
Log.i("OAuth response=" + authState.jsonSerializeString());
if (TextUtils.isEmpty(access.refreshToken))
throw new IllegalStateException("No refresh token");
onOAuthorized(access.accessToken, authState);
} catch (Throwable ex) {
showError(ex);
}
}
});
} catch (Throwable ex) { } catch (Throwable ex) {
showError(ex); showError(ex);
btnOAuth.setEnabled(true); btnOAuth.setEnabled(true);
@ -313,6 +307,7 @@ public class FragmentOAuth extends FragmentBase {
private void onOAuthorized(String accessToken, AuthState state) { private void onOAuthorized(String accessToken, AuthState state) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString("id", id);
args.putString("name", name); args.putString("name", name);
args.putString("token", accessToken); args.putString("token", accessToken);
args.putString("state", state.jsonSerializeString()); args.putString("state", state.jsonSerializeString());
@ -320,6 +315,7 @@ public class FragmentOAuth extends FragmentBase {
new SimpleTask<Void>() { new SimpleTask<Void>() {
@Override @Override
protected Void onExecute(Context context, Bundle args) throws Throwable { protected Void onExecute(Context context, Bundle args) throws Throwable {
String id = args.getString("id");
String name = args.getString("name"); String name = args.getString("name");
String token = args.getString("token"); String token = args.getString("token");
String state = args.getString("state"); String state = args.getString("state");
@ -327,7 +323,7 @@ public class FragmentOAuth extends FragmentBase {
String primaryEmail = null; String primaryEmail = null;
List<Pair<String, String>> identities = new ArrayList<>(); List<Pair<String, String>> identities = new ArrayList<>();
if ("Gmail".equals(name)) { if ("gmail".equals(id)) {
// https://developers.google.com/gmail/api/v1/reference/users/getProfile // https://developers.google.com/gmail/api/v1/reference/users/getProfile
URL url = new URL("https://www.googleapis.com/gmail/v1/users/me/settings/sendAs"); URL url = new URL("https://www.googleapis.com/gmail/v1/users/me/settings/sendAs");
Log.i("Fetching " + url); Log.i("Fetching " + url);
@ -366,7 +362,7 @@ public class FragmentOAuth extends FragmentBase {
} }
} }
} else if ("Outlook/Office365".equals(name)) { } else if ("outlook".equals(id)) {
// https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http#http-request // https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http#http-request
URL url = new URL("https://graph.microsoft.com/v1.0/me?$select=displayName,otherMails"); URL url = new URL("https://graph.microsoft.com/v1.0/me?$select=displayName,otherMails");
Log.i("Fetching " + url); Log.i("Fetching " + url);
@ -403,7 +399,7 @@ public class FragmentOAuth extends FragmentBase {
} }
} }
} else } else
throw new IllegalArgumentException("Unknown provider=" + name); throw new IllegalArgumentException("Unknown provider=" + id);
if (TextUtils.isEmpty(primaryEmail) || identities.size() == 0) if (TextUtils.isEmpty(primaryEmail) || identities.size() == 0)
throw new IllegalArgumentException("Primary email address not found"); throw new IllegalArgumentException("Primary email address not found");
@ -412,101 +408,101 @@ public class FragmentOAuth extends FragmentBase {
for (Pair<String, String> identity : identities) for (Pair<String, String> identity : identities)
Log.i("OAuth identity=" + identity.first + "/" + identity.second); Log.i("OAuth identity=" + identity.first + "/" + identity.second);
for (EmailProvider provider : EmailProvider.loadProfiles(context)) EmailProvider provider = EmailProvider.getProvider(context, id);
if (provider.name.equals(name)) {
List<EntityFolder> folders; List<EntityFolder> folders;
Log.i("OAuth checking IMAP provider=" + provider.name); Log.i("OAuth checking IMAP provider=" + provider.id);
String aprotocol = provider.imap.starttls ? "imap" : "imaps"; String aprotocol = provider.imap.starttls ? "imap" : "imaps";
try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) { try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) {
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_OAUTH, primaryEmail, state, null); iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_OAUTH, provider.id, primaryEmail, state, null);
folders = iservice.getFolders(); folders = iservice.getFolders();
if (folders == null) if (folders == null)
throw new IllegalArgumentException(context.getString(R.string.title_setup_no_system_folders)); throw new IllegalArgumentException(context.getString(R.string.title_setup_no_system_folders));
} }
Log.i("OAuth checking SMTP provider=" + provider.name); Log.i("OAuth checking SMTP provider=" + provider.id);
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps"; String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) { try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) {
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_OAUTH, primaryEmail, state, null); iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_OAUTH, provider.id, primaryEmail, state, null);
} }
Log.i("OAuth passed provider=" + provider.name); Log.i("OAuth passed provider=" + provider.id);
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
try { try {
db.beginTransaction(); db.beginTransaction();
EntityAccount primary = db.account().getPrimaryAccount(); EntityAccount primary = db.account().getPrimaryAccount();
// Create account // Create account
EntityAccount account = new EntityAccount(); EntityAccount account = new EntityAccount();
account.host = provider.imap.host; account.host = provider.imap.host;
account.starttls = provider.imap.starttls; account.starttls = provider.imap.starttls;
account.port = provider.imap.port; account.port = provider.imap.port;
account.auth_type = MailService.AUTH_TYPE_OAUTH; account.auth_type = MailService.AUTH_TYPE_OAUTH;
account.user = primaryEmail; account.provider = provider.id;
account.password = state; account.user = primaryEmail;
account.password = state;
account.name = provider.name; account.name = provider.name;
account.synchronize = true; account.synchronize = true;
account.primary = (primary == null); account.primary = (primary == null);
account.created = new Date().getTime(); account.created = new Date().getTime();
account.last_connected = account.created; account.last_connected = account.created;
account.id = db.account().insertAccount(account); account.id = db.account().insertAccount(account);
args.putLong("account", account.id); args.putLong("account", account.id);
EntityLog.log(context, "OAuth account=" + account.name); EntityLog.log(context, "OAuth account=" + account.name);
// Create folders // Create folders
for (EntityFolder folder : folders) { for (EntityFolder folder : folders) {
folder.account = account.id; folder.account = account.id;
folder.id = db.folder().insertFolder(folder); folder.id = db.folder().insertFolder(folder);
EntityLog.log(context, "OAuth folder=" + folder.name + " type=" + folder.type); EntityLog.log(context, "OAuth folder=" + folder.name + " type=" + folder.type);
} }
// Set swipe left/right folder // Set swipe left/right folder
for (EntityFolder folder : folders) for (EntityFolder folder : folders)
if (EntityFolder.TRASH.equals(folder.type)) if (EntityFolder.TRASH.equals(folder.type))
account.swipe_left = folder.id; account.swipe_left = folder.id;
else if (EntityFolder.ARCHIVE.equals(folder.type)) else if (EntityFolder.ARCHIVE.equals(folder.type))
account.swipe_right = folder.id; account.swipe_right = folder.id;
db.account().updateAccount(account); db.account().updateAccount(account);
// Create identities // Create identities
for (Pair<String, String> identity : identities) { for (Pair<String, String> identity : identities) {
EntityIdentity ident = new EntityIdentity(); EntityIdentity ident = new EntityIdentity();
ident.name = identity.second; ident.name = identity.second;
ident.email = identity.first; ident.email = identity.first;
ident.account = account.id; ident.account = account.id;
ident.host = provider.smtp.host; ident.host = provider.smtp.host;
ident.starttls = provider.smtp.starttls; ident.starttls = provider.smtp.starttls;
ident.port = provider.smtp.port; ident.port = provider.smtp.port;
ident.auth_type = MailService.AUTH_TYPE_OAUTH; ident.auth_type = MailService.AUTH_TYPE_OAUTH;
ident.user = primaryEmail; ident.provider = provider.id;
ident.password = state; ident.user = primaryEmail;
ident.synchronize = true; ident.password = state;
ident.primary = ident.user.equals(ident.email); ident.synchronize = true;
ident.primary = ident.user.equals(ident.email);
ident.id = db.identity().insertIdentity(ident);
EntityLog.log(context, "OAuth identity=" + ident.name + " email=" + ident.email); ident.id = db.identity().insertIdentity(ident);
} EntityLog.log(context, "OAuth identity=" + ident.name + " email=" + ident.email);
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
ServiceSynchronize.eval(context, "OAuth"); ServiceSynchronize.eval(context, "OAuth");
}
return null; return null;
} }
@ -542,7 +538,7 @@ public class FragmentOAuth extends FragmentBase {
grpError.setVisibility(View.VISIBLE); grpError.setVisibility(View.VISIBLE);
if ("Gmail".equals(name)) if ("gmail".equals(id))
tvGmailDraftsHint.setVisibility(View.VISIBLE); tvGmailDraftsHint.setVisibility(View.VISIBLE);
new Handler().post(new Runnable() { new Handler().post(new Runnable() {

@ -278,7 +278,7 @@ public class FragmentPop extends FragmentBase {
if (check) { if (check) {
String protocol = "pop3" + (starttls ? "" : "s"); String protocol = "pop3" + (starttls ? "" : "s");
try (MailService iservice = new MailService(context, protocol, null, insecure, true, true)) { try (MailService iservice = new MailService(context, protocol, null, insecure, true, true)) {
iservice.connect(host, Integer.parseInt(port), MailService.AUTH_TYPE_PASSWORD, user, password, null); iservice.connect(host, Integer.parseInt(port), MailService.AUTH_TYPE_PASSWORD, null, user, password, null);
} }
} }

@ -250,13 +250,13 @@ public class FragmentQuickSetup extends FragmentBase {
String aprotocol = provider.imap.starttls ? "imap" : "imaps"; String aprotocol = provider.imap.starttls ? "imap" : "imaps";
try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) { try (MailService iservice = new MailService(context, aprotocol, null, false, true, true)) {
try { try {
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password, null); iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, null, user, password, null);
} catch (AuthenticationFailedException ex) { } catch (AuthenticationFailedException ex) {
if (!user.equals(username)) { if (!user.equals(username)) {
Log.w(ex); Log.w(ex);
user = username; user = username;
Log.i("Retry with user=" + user); Log.i("Retry with user=" + user);
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password, null); iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, null, user, password, null);
} else } else
throw ex; throw ex;
} }
@ -270,7 +270,7 @@ public class FragmentQuickSetup extends FragmentBase {
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps"; String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) { try (MailService iservice = new MailService(context, iprotocol, null, false, true, true)) {
iservice.setUseIp(provider.useip); iservice.setUseIp(provider.useip);
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_PASSWORD, user, password, null); iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_PASSWORD, null, user, password, null);
} }
if (check) if (check)

@ -174,6 +174,7 @@ public class FragmentSetup extends FragmentBase {
popupMenu.getMenu() popupMenu.getMenu()
.add(Menu.NONE, -1, order++, getString(R.string.title_setup_oauth, provider.name)) .add(Menu.NONE, -1, order++, getString(R.string.title_setup_oauth, provider.name))
.setIntent(new Intent(ActivitySetup.ACTION_QUICK_OAUTH) .setIntent(new Intent(ActivitySetup.ACTION_QUICK_OAUTH)
.putExtra("id", provider.id)
.putExtra("name", provider.name)); .putExtra("name", provider.name));
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_activesync, order++, R.string.title_setup_activesync); popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_activesync, order++, R.string.title_setup_activesync);

@ -17,6 +17,9 @@ import com.sun.mail.util.MailConnectException;
import net.openid.appauth.AuthState; import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException; import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationService; import net.openid.appauth.AuthorizationService;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretPost;
import net.openid.appauth.NoClientAuthentication;
import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralName;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -200,7 +203,7 @@ public class MailService implements AutoCloseable {
} }
public void connect(EntityAccount account) throws MessagingException { public void connect(EntityAccount account) throws MessagingException {
String password = connect(account.host, account.port, account.auth_type, account.user, account.password, account.fingerprint); String password = connect(account.host, account.port, account.auth_type, account.provider, account.user, account.password, account.fingerprint);
if (password != null) { if (password != null) {
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
int count = db.account().setAccountPassword(account.id, account.password); int count = db.account().setAccountPassword(account.id, account.password);
@ -209,7 +212,7 @@ public class MailService implements AutoCloseable {
} }
public void connect(EntityIdentity identity) throws MessagingException { public void connect(EntityIdentity identity) throws MessagingException {
String password = connect(identity.host, identity.port, identity.auth_type, identity.user, identity.password, identity.fingerprint); String password = connect(identity.host, identity.port, identity.auth_type, identity.provider, identity.user, identity.password, identity.fingerprint);
if (password != null) { if (password != null) {
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
int count = db.identity().setIdentityPassword(identity.id, identity.password); int count = db.identity().setIdentityPassword(identity.id, identity.password);
@ -217,7 +220,7 @@ public class MailService implements AutoCloseable {
} }
} }
public String connect(String host, int port, int auth, String user, String password, String fingerprint) throws MessagingException { public String connect(String host, int port, int auth, String provider, String user, String password, String fingerprint) throws MessagingException {
SSLSocketFactoryService factory = null; SSLSocketFactoryService factory = null;
try { try {
factory = new SSLSocketFactoryService(host, insecure, fingerprint); factory = new SSLSocketFactoryService(host, insecure, fingerprint);
@ -240,7 +243,7 @@ public class MailService implements AutoCloseable {
// new SocketConnectException("Debug", new Exception("Test"), host, port, 0)); // new SocketConnectException("Debug", new Exception("Test"), host, port, 0));
if (auth == AUTH_TYPE_OAUTH) { if (auth == AUTH_TYPE_OAUTH) {
AuthState authState = OAuthRefresh(context, password); AuthState authState = OAuthRefresh(context, provider, password);
_connect(context, host, port, user, authState.getAccessToken(), factory); _connect(context, host, port, user, authState.getAccessToken(), factory);
return authState.jsonSerializeString(); return authState.jsonSerializeString();
} else { } else {
@ -272,7 +275,7 @@ public class MailService implements AutoCloseable {
throw new AuthenticationFailedException(ex.getMessage(), ex1); throw new AuthenticationFailedException(ex.getMessage(), ex1);
} }
else if (auth == AUTH_TYPE_OAUTH) { else if (auth == AUTH_TYPE_OAUTH) {
AuthState authState = OAuthRefresh(context, password); AuthState authState = OAuthRefresh(context, provider, password);
_connect(context, host, port, user, authState.getAccessToken(), factory); _connect(context, host, port, user, authState.getAccessToken(), factory);
return authState.jsonSerializeString(); return authState.jsonSerializeString();
} else } else
@ -377,24 +380,33 @@ public class MailService implements AutoCloseable {
AuthorizationException error; AuthorizationException error;
} }
static AuthState OAuthRefresh(Context context, String json) throws MessagingException { private static AuthState OAuthRefresh(Context context, String id, String json) throws MessagingException {
try { try {
AuthState authState = AuthState.jsonDeserialize(json); AuthState authState = AuthState.jsonDeserialize(json);
Semaphore semaphore = new Semaphore(0); ClientAuthentication clientAuth;
EmailProvider provider = EmailProvider.getProvider(context, id);
if (provider.oauth.clientSecret == null)
clientAuth = NoClientAuthentication.INSTANCE;
else
clientAuth = new ClientSecretPost(provider.oauth.clientSecret);
ErrorHolder holder = new ErrorHolder(); ErrorHolder holder = new ErrorHolder();
Semaphore semaphore = new Semaphore(0);
Log.i("OAuth refresh"); Log.i("OAuth refresh");
AuthorizationService authService = new AuthorizationService(context); AuthorizationService authService = new AuthorizationService(context);
authState.performActionWithFreshTokens(authService, new AuthState.AuthStateAction() { authState.performActionWithFreshTokens(
@Override authService,
public void execute(String accessToken, String idToken, AuthorizationException error) { clientAuth,
if (error != null) new AuthState.AuthStateAction() {
holder.error = error; @Override
semaphore.release(); public void execute(String accessToken, String idToken, AuthorizationException error) {
} if (error != null)
}); holder.error = error;
semaphore.release();
}
});
semaphore.acquire(); semaphore.acquire();
Log.i("OAuth refreshed"); Log.i("OAuth refreshed");

@ -3,6 +3,7 @@
<provider <provider
name="Gmail" name="Gmail"
domain="gmail\\.com" domain="gmail\\.com"
id="gmail"
link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq6" link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq6"
order="1" order="1"
type="com.google"> type="com.google">
@ -28,6 +29,7 @@
<provider <provider
name="Outlook/Office365" name="Outlook/Office365"
domain="outlook\\..*,live\\..*,hotmail\\..*" domain="outlook\\..*,live\\..*,hotmail\\..*"
id="outlook"
link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq14" link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq14"
order="2" order="2"
partial="false"> partial="false">

Loading…
Cancel
Save