Use service authenticator

pull/187/head
M66B 5 years ago
parent b5443dd4f9
commit 480eaa18f6

@ -1080,7 +1080,7 @@ This requires contact/account permissions and internet connectivity.
The error *... Authentication failed ... Account not found ...* means that a previously authorized Gmail account was removed from the device. The error *... Authentication failed ... Account not found ...* means that a previously authorized Gmail account was removed from the device.
The errors *... Authentication failed ... No token on refresh ...* means that the Android account manager failed to refresh the authorization of a Gmail account. The errors *... Authentication failed ... No token ...* means that the Android account manager failed to refresh the authorization of a Gmail account.
The error *... Authentication failed ... Invalid credentials ... network error ...* The error *... Authentication failed ... Invalid credentials ... network error ...*
means that the Android account manager was not able to refresh the authorization of a Gmail account due to problems with the internet connection means that the Android account manager was not able to refresh the authorization of a Gmail account due to problems with the internet connection

@ -102,6 +102,9 @@ import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEKeySpec;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
import static eu.faircode.email.ServiceAuthenticator.TYPE_GOOGLE;
public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener { public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
private View view; private View view;
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
@ -773,10 +776,10 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
JSONObject jaccount = (JSONObject) jaccounts.get(a); JSONObject jaccount = (JSONObject) jaccounts.get(a);
EntityAccount account = EntityAccount.fromJSON(jaccount); EntityAccount account = EntityAccount.fromJSON(jaccount);
if (account.auth_type == EmailService.AUTH_TYPE_GMAIL) { if (account.auth_type == AUTH_TYPE_GMAIL) {
AccountManager am = AccountManager.get(context); AccountManager am = AccountManager.get(context);
boolean found = false; boolean found = false;
for (Account google : am.getAccountsByType(EmailService.TYPE_GOOGLE)) for (Account google : am.getAccountsByType(TYPE_GOOGLE))
if (account.user.equals(google.name)) { if (account.user.equals(google.name)) {
found = true; found = true;
break; break;

@ -64,6 +64,8 @@ import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHolder> { public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHolder> {
private Fragment parentFragment; private Fragment parentFragment;
private boolean settings; private boolean settings;
@ -152,7 +154,7 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
ivSync.setContentDescription(context.getString(account.synchronize ? R.string.title_legend_synchronize_on : R.string.title_legend_synchronize_off)); ivSync.setContentDescription(context.getString(account.synchronize ? R.string.title_legend_synchronize_on : R.string.title_legend_synchronize_off));
ivOAuth.setVisibility( ivOAuth.setVisibility(
settings && account.auth_type != EmailService.AUTH_TYPE_PASSWORD ? View.VISIBLE : View.GONE); settings && account.auth_type != AUTH_TYPE_PASSWORD ? View.VISIBLE : View.GONE);
ivPrimary.setVisibility(account.primary ? View.VISIBLE : View.GONE); ivPrimary.setVisibility(account.primary ? View.VISIBLE : View.GONE);
ivNotify.setVisibility(account.notify ? View.VISIBLE : View.GONE); ivNotify.setVisibility(account.notify ? View.VISIBLE : View.GONE);

@ -55,6 +55,8 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHolder> { public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHolder> {
private Fragment parentFragment; private Fragment parentFragment;
private Context context; private Context context;
@ -123,7 +125,7 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
ivSync.setImageResource(identity.synchronize ? R.drawable.twotone_sync_24 : R.drawable.twotone_sync_disabled_24); ivSync.setImageResource(identity.synchronize ? R.drawable.twotone_sync_24 : R.drawable.twotone_sync_disabled_24);
ivSync.setContentDescription(context.getString(identity.synchronize ? R.string.title_legend_synchronize_on : R.string.title_legend_synchronize_off)); ivSync.setContentDescription(context.getString(identity.synchronize ? R.string.title_legend_synchronize_on : R.string.title_legend_synchronize_off));
ivOAuth.setVisibility(identity.auth_type == EmailService.AUTH_TYPE_PASSWORD ? View.GONE : View.VISIBLE); ivOAuth.setVisibility(identity.auth_type == AUTH_TYPE_PASSWORD ? View.GONE : View.VISIBLE);
ivPrimary.setVisibility(identity.primary ? View.VISIBLE : View.GONE); ivPrimary.setVisibility(identity.primary ? View.VISIBLE : View.GONE);
ivGroup.setVisibility(identity.self ? View.GONE : View.VISIBLE); ivGroup.setVisibility(identity.self ? View.GONE : View.VISIBLE);
tvName.setText(identity.getDisplayName()); tvName.setText(identity.getDisplayName());

@ -40,6 +40,8 @@ import javax.mail.internet.InternetAddress;
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory; import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
import io.requery.android.database.sqlite.SQLiteDatabase; import io.requery.android.database.sqlite.SQLiteDatabase;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
/* /*
This file is part of FairEmail. This file is part of FairEmail.
@ -1343,8 +1345,8 @@ public abstract class DB extends RoomDatabase {
int previous_version = prefs.getInt("previous_version", -1); int previous_version = prefs.getInt("previous_version", -1);
if (previous_version <= 848 && Helper.isPlayStoreInstall()) { if (previous_version <= 848 && Helper.isPlayStoreInstall()) {
// JavaMail didn't check server certificates // JavaMail didn't check server certificates
db.execSQL("UPDATE account SET insecure = 1 WHERE auth_type = " + EmailService.AUTH_TYPE_PASSWORD); db.execSQL("UPDATE account SET insecure = 1 WHERE auth_type = " + AUTH_TYPE_PASSWORD);
db.execSQL("UPDATE identity SET insecure = 1 WHERE auth_type = " + EmailService.AUTH_TYPE_PASSWORD); db.execSQL("UPDATE identity SET insecure = 1 WHERE auth_type = " + AUTH_TYPE_PASSWORD);
} }
} }
}) })

@ -1,9 +1,24 @@
package eu.faircode.email; package eu.faircode.email;
import android.accounts.Account; /*
import android.accounts.AccountManager; This file is part of FairEmail.
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException; FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2020 by Marcel Bokhorst (M66B)
*/
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.security.KeyChain; import android.security.KeyChain;
@ -19,13 +34,6 @@ import com.sun.mail.smtp.SMTPTransport;
import com.sun.mail.util.MailConnectException; import com.sun.mail.util.MailConnectException;
import com.sun.mail.util.SocketConnectException; import com.sun.mail.util.SocketConnectException;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretPost;
import net.openid.appauth.NoClientAuthentication;
import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralName;
@ -63,10 +71,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.mail.AuthenticationFailedException; import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.Folder; import javax.mail.Folder;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException; import javax.mail.NoSuchProviderException;
@ -86,6 +94,9 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_OAUTH;
// IMAP standards: https://imapwiki.org/Specs // IMAP standards: https://imapwiki.org/Specs
public class EmailService implements AutoCloseable { public class EmailService implements AutoCloseable {
@ -105,12 +116,6 @@ public class EmailService implements AutoCloseable {
private ExecutorService executor = Helper.getBackgroundExecutor(0, "mail"); private ExecutorService executor = Helper.getBackgroundExecutor(0, "mail");
static final String TYPE_GOOGLE = "com.google";
static final int AUTH_TYPE_PASSWORD = 1;
static final int AUTH_TYPE_GMAIL = 2;
static final int AUTH_TYPE_OAUTH = 3;
static final int PURPOSE_CHECK = 1; static final int PURPOSE_CHECK = 1;
static final int PURPOSE_USE = 2; static final int PURPOSE_USE = 2;
static final int PURPOSE_SEARCH = 3; static final int PURPOSE_SEARCH = 3;
@ -278,34 +283,49 @@ public class EmailService implements AutoCloseable {
} }
public void connect(EntityAccount account) throws MessagingException { public void connect(EntityAccount account) throws MessagingException {
String password = connect( connect(
account.host, account.port, account.host, account.port,
account.auth_type, account.provider, account.auth_type, account.provider,
account.user, account.password, account.user, account.password,
account.certificate_alias, account.fingerprint); new ServiceAuthenticator.IAuthenticated() {
if (password != null) { @Override
public void onPasswordChanged(String newPassword) {
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
int count = db.account().setAccountPassword(account.id, password); int count = db.account().setAccountPassword(account.id, newPassword);
Log.i(account.name + " token refreshed=" + count); EntityLog.log(context, account.name + " token refreshed=" + count);
} }
},
account.certificate_alias, account.fingerprint);
} }
public void connect(EntityIdentity identity) throws MessagingException { public void connect(EntityIdentity identity) throws MessagingException {
String password = connect( connect(
identity.host, identity.port, identity.host, identity.port,
identity.auth_type, identity.provider, identity.auth_type, identity.provider,
identity.user, identity.password, identity.user, identity.password,
identity.certificate_alias, identity.fingerprint); new ServiceAuthenticator.IAuthenticated() {
if (password != null) { @Override
public void onPasswordChanged(String newPassword) {
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
int count = db.identity().setIdentityPassword(identity.id, password); int count = db.identity().setIdentityPassword(identity.id, newPassword);
Log.i(identity.email + " token refreshed=" + count); EntityLog.log(context, identity.email + " token refreshed=" + count);
}
},
identity.certificate_alias, identity.fingerprint);
} }
public void connect(
String host, int port,
int auth, String provider, String user, String password,
String certificate, String fingerprint) throws MessagingException {
connect(host, port, auth, provider, user, password, null, certificate, fingerprint);
} }
public String connect( private void connect(
String host, int port, String host, int port,
int auth, String provider, String user, String password, int auth, String provider, String user, String password,
ServiceAuthenticator.IAuthenticated intf,
String certificate, String fingerprint) throws MessagingException { String certificate, String fingerprint) throws MessagingException {
SSLSocketFactoryService factory = null; SSLSocketFactoryService factory = null;
try { try {
@ -332,35 +352,27 @@ public class EmailService implements AutoCloseable {
Log.e("Trust issues", ex); Log.e("Trust issues", ex);
} }
properties.put("mail." + protocol + ".forcepasswordrefresh", "true");
ServiceAuthenticator authenticator = new ServiceAuthenticator(context, auth, provider, user, password, intf);
try { try {
if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH) if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH)
properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2"); properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2");
if (auth == AUTH_TYPE_OAUTH) { if (auth == AUTH_TYPE_OAUTH && "imap.mail.yahoo.com".equals(host))
if ("imap.mail.yahoo.com".equals(host))
properties.put("mail." + protocol + ".yahoo.guid", "FAIRMAIL_V1"); properties.put("mail." + protocol + ".yahoo.guid", "FAIRMAIL_V1");
AuthState authState = OAuthRefresh(context, provider, password);
connect(host, port, auth, user, authState.getAccessToken(), factory); connect(host, port, auth, user, authenticator, factory);
return authState.jsonSerializeString();
} else {
connect(host, port, auth, user, password, factory);
return null;
}
} catch (AuthenticationFailedException ex) { } catch (AuthenticationFailedException ex) {
// Refresh token // Refresh token
if (auth == AUTH_TYPE_GMAIL) if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH) {
try { try {
String token = GmailRefresh(context, user, password); authenticator.expire();
connect(host, port, auth, user, token, factory); connect(host, port, auth, user, authenticator, factory);
return token;
} catch (Exception ex1) { } catch (Exception ex1) {
Log.e(ex1); Log.e(ex1);
throw new AuthenticationFailedException(ex.getMessage(), ex1); throw new AuthenticationFailedException(ex.getMessage(), ex1);
} }
else if (auth == AUTH_TYPE_OAUTH) {
AuthState authState = OAuthRefresh(context, provider, password);
connect(host, port, auth, user, authState.getAccessToken(), factory);
return authState.jsonSerializeString();
} else if (purpose == PURPOSE_CHECK) { } else if (purpose == PURPOSE_CHECK) {
String msg = ex.getMessage(); String msg = ex.getMessage();
if (msg != null) if (msg != null)
@ -407,7 +419,7 @@ public class EmailService implements AutoCloseable {
private void connect( private void connect(
String host, int port, int auth, String host, int port, int auth,
String user, String password, String user, Authenticator authenticator,
SSLSocketFactoryService factory) throws MessagingException { SSLSocketFactoryService factory) throws MessagingException {
InetAddress main = null; InetAddress main = null;
boolean require_id = (purpose == PURPOSE_CHECK && boolean require_id = (purpose == PURPOSE_CHECK &&
@ -421,7 +433,7 @@ public class EmailService implements AutoCloseable {
main = InetAddress.getByName(host); main = InetAddress.getByName(host);
EntityLog.log(context, "Connecting to " + main); EntityLog.log(context, "Connecting to " + main);
_connect(main, port, require_id, user, password, factory); _connect(main, port, require_id, user, authenticator, factory);
} catch (UnknownHostException ex) { } catch (UnknownHostException ex) {
throw new MessagingException(ex.getMessage(), ex); throw new MessagingException(ex.getMessage(), ex);
} catch (MessagingException ex) { } catch (MessagingException ex) {
@ -486,7 +498,7 @@ public class EmailService implements AutoCloseable {
try { try {
EntityLog.log(context, "Falling back to " + iaddr); EntityLog.log(context, "Falling back to " + iaddr);
_connect(iaddr, port, require_id, user, password, factory); _connect(iaddr, port, require_id, user, authenticator, factory);
return; return;
} catch (MessagingException ex1) { } catch (MessagingException ex1) {
ex = ex1; ex = ex1;
@ -505,9 +517,9 @@ public class EmailService implements AutoCloseable {
private void _connect( private void _connect(
InetAddress address, int port, boolean require_id, InetAddress address, int port, boolean require_id,
String user, String password, String user, Authenticator authenticator,
SSLSocketFactoryService factory) throws MessagingException { SSLSocketFactoryService factory) throws MessagingException {
isession = Session.getInstance(properties, null); isession = Session.getInstance(properties, authenticator);
isession.setDebug(debug || log); isession.setDebug(debug || log);
if (debug || log) if (debug || log)
@ -538,13 +550,13 @@ public class EmailService implements AutoCloseable {
if ("pop3".equals(protocol) || "pop3s".equals(protocol)) { if ("pop3".equals(protocol) || "pop3s".equals(protocol)) {
iservice = isession.getStore(protocol); iservice = isession.getStore(protocol);
iservice.connect(address.getHostAddress(), port, user, password); iservice.connect(address.getHostAddress(), port, user, null);
} else if ("imap".equals(protocol) || "imaps".equals(protocol) || "gimaps".equals(protocol)) { } else if ("imap".equals(protocol) || "imaps".equals(protocol) || "gimaps".equals(protocol)) {
iservice = isession.getStore(protocol); iservice = isession.getStore(protocol);
if (listener != null) if (listener != null)
((IMAPStore) iservice).addStoreListener(listener); ((IMAPStore) iservice).addStoreListener(listener);
iservice.connect(address.getHostAddress(), port, user, password); iservice.connect(address.getHostAddress(), port, user, null);
// https://www.ietf.org/rfc/rfc2971.txt // https://www.ietf.org/rfc/rfc2971.txt
IMAPStore istore = (IMAPStore) getStore(); IMAPStore istore = (IMAPStore) getStore();
@ -580,13 +592,13 @@ public class EmailService implements AutoCloseable {
iservice = isession.getTransport(protocol); iservice = isession.getTransport(protocol);
try { try {
iservice.connect(address.getHostAddress(), port, user, password); iservice.connect(address.getHostAddress(), port, user, null);
} catch (MessagingException ex) { } catch (MessagingException ex) {
if (ehlo == null && ConnectionHelper.isSyntacticallyInvalid(ex)) { if (ehlo == null && ConnectionHelper.isSyntacticallyInvalid(ex)) {
properties.put("mail." + protocol + ".localhost", useip ? hdomain : haddr); properties.put("mail." + protocol + ".localhost", useip ? hdomain : haddr);
Log.i("Fallback localhost=" + properties.getProperty("mail." + protocol + ".localhost")); Log.i("Fallback localhost=" + properties.getProperty("mail." + protocol + ".localhost"));
try { try {
iservice.connect(address.getHostAddress(), port, user, password); iservice.connect(address.getHostAddress(), port, user, null);
} catch (MessagingException ex1) { } catch (MessagingException ex1) {
if (ConnectionHelper.isSyntacticallyInvalid(ex1)) if (ConnectionHelper.isSyntacticallyInvalid(ex1))
Log.e("Used localhost=" + haddr + "/" + hdomain); Log.e("Used localhost=" + haddr + "/" + hdomain);
@ -605,75 +617,6 @@ public class EmailService implements AutoCloseable {
return TextUtils.join(".", c); return TextUtils.join(".", c);
} }
private static class ErrorHolder {
AuthorizationException error;
}
private static String GmailRefresh(Context context, String user, String password) throws AuthenticatorException, OperationCanceledException, IOException {
AccountManager am = AccountManager.get(context);
Account[] accounts = am.getAccountsByType(TYPE_GOOGLE);
for (Account account : accounts)
if (user.equals(account.name)) {
Log.i("Refreshing token user=" + user);
if (password != null)
am.invalidateAuthToken(TYPE_GOOGLE, password);
String token = am.blockingGetAuthToken(account, getAuthTokenType(TYPE_GOOGLE), true);
if (token == null)
throw new AuthenticatorException("No token on refresh for " + user);
return token;
}
throw new AuthenticatorException("Account not found for " + user);
}
private static AuthState OAuthRefresh(Context context, String id, String json) throws MessagingException {
try {
AuthState authState = AuthState.jsonDeserialize(json);
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();
Semaphore semaphore = new Semaphore(0);
Log.i("OAuth refresh");
AuthorizationService authService = new AuthorizationService(context);
authState.performActionWithFreshTokens(
authService,
clientAuth,
new AuthState.AuthStateAction() {
@Override
public void execute(String accessToken, String idToken, AuthorizationException error) {
if (error != null)
holder.error = error;
semaphore.release();
}
});
semaphore.acquire();
Log.i("OAuth refreshed");
if (holder.error != null)
throw holder.error;
return authState;
} catch (Exception ex) {
throw new MessagingException("OAuth refresh", ex);
}
}
static String getAuthTokenType(String type) {
// https://developers.google.com/gmail/imap/xoauth2-protocol
if ("com.google".equals(type))
return "oauth2:https://mail.google.com/";
return null;
}
List<EntityFolder> getFolders() throws MessagingException { List<EntityFolder> getFolders() throws MessagingException {
List<EntityFolder> folders = new ArrayList<>(); List<EntityFolder> folders = new ArrayList<>();

@ -19,8 +19,6 @@ package eu.faircode.email;
Copyright 2018-2020 by Marcel Bokhorst (M66B) Copyright 2018-2020 by Marcel Bokhorst (M66B)
*/ */
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -74,6 +72,9 @@ import javax.mail.Folder;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE; import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE;
import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE; import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_OAUTH;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class FragmentAccount extends FragmentBase { public class FragmentAccount extends FragmentBase {
private ViewGroup view; private ViewGroup view;
@ -153,7 +154,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 = EmailService.AUTH_TYPE_PASSWORD; private int auth = AUTH_TYPE_PASSWORD;
private String provider = null; private String provider = null;
private String certificate = null; private String certificate = null;
private boolean saving = false; private boolean saving = false;
@ -261,7 +262,7 @@ public class FragmentAccount extends FragmentBase {
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long itemid) { public void onItemSelected(AdapterView<?> adapterView, View view, int position, long itemid) {
EmailProvider provider = (EmailProvider) adapterView.getSelectedItem(); EmailProvider provider = (EmailProvider) adapterView.getSelectedItem();
tvGmailHint.setVisibility( tvGmailHint.setVisibility(
auth == EmailService.AUTH_TYPE_PASSWORD && "gmail".equals(provider.id) auth == AUTH_TYPE_PASSWORD && "gmail".equals(provider.id)
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
grpServer.setVisibility(position > 0 ? View.VISIBLE : View.GONE); grpServer.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
grpAuthorize.setVisibility(position > 0 ? View.VISIBLE : View.GONE); grpAuthorize.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
@ -1333,16 +1334,7 @@ public class FragmentAccount extends FragmentBase {
if (account == null) if (account == null)
return null; return null;
AccountManager am = AccountManager.get(context); return ServiceAuthenticator.getGmailToken(context, account.user);
Account[] accounts = am.getAccountsByType(EmailService.TYPE_GOOGLE);
for (Account google : accounts)
if (account.user.equals(google.name))
return am.blockingGetAuthToken(
google,
EmailService.getAuthTokenType(EmailService.TYPE_GOOGLE),
true);
return null;
} }
@Override @Override
@ -1449,7 +1441,7 @@ public class FragmentAccount extends FragmentBase {
boolean found = false; boolean found = false;
for (int pos = 2; pos < providers.size(); pos++) { for (int pos = 2; pos < providers.size(); pos++) {
EmailProvider provider = providers.get(pos); EmailProvider provider = providers.get(pos);
if ((provider.oauth != null) == (account.auth_type == EmailService.AUTH_TYPE_OAUTH) && if ((provider.oauth != null) == (account.auth_type == AUTH_TYPE_OAUTH) &&
provider.imap.host.equals(account.host) && provider.imap.host.equals(account.host) &&
provider.imap.port == account.port && provider.imap.port == account.port &&
provider.imap.starttls == (account.encryption == EmailService.ENCRYPTION_STARTTLS)) { provider.imap.starttls == (account.encryption == EmailService.ENCRYPTION_STARTTLS)) {
@ -1514,7 +1506,7 @@ public class FragmentAccount extends FragmentBase {
else else
rgDate.check(R.id.radio_server_time); rgDate.check(R.id.radio_server_time);
auth = (account == null ? EmailService.AUTH_TYPE_PASSWORD : account.auth_type); auth = (account == null ? AUTH_TYPE_PASSWORD : account.auth_type);
provider = (account == null ? null : account.provider); provider = (account == null ? null : account.provider);
new SimpleTask<EntityAccount>() { new SimpleTask<EntityAccount>() {
@ -1550,13 +1542,13 @@ public class FragmentAccount extends FragmentBase {
Helper.setViewsEnabled(view, true); Helper.setViewsEnabled(view, true);
if (auth != EmailService.AUTH_TYPE_PASSWORD) { if (auth != AUTH_TYPE_PASSWORD) {
etUser.setEnabled(false); etUser.setEnabled(false);
tilPassword.setEnabled(false); tilPassword.setEnabled(false);
btnCertificate.setEnabled(false); btnCertificate.setEnabled(false);
} }
if (account == null || account.auth_type != EmailService.AUTH_TYPE_GMAIL) if (account == null || account.auth_type != AUTH_TYPE_GMAIL)
Helper.hide((btnOAuth)); Helper.hide((btnOAuth));
cbOnDemand.setEnabled(cbSynchronize.isChecked()); cbOnDemand.setEnabled(cbSynchronize.isChecked());

@ -56,6 +56,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class FragmentAccounts extends FragmentBase { public class FragmentAccounts extends FragmentBase {
private boolean settings; private boolean settings;
@ -267,7 +269,7 @@ public class FragmentAccounts extends FragmentBase {
boolean authorized = true; boolean authorized = true;
for (TupleAccountEx account : accounts) for (TupleAccountEx account : accounts)
if (account.auth_type != EmailService.AUTH_TYPE_PASSWORD && if (account.auth_type != AUTH_TYPE_PASSWORD &&
!Helper.hasPermissions(getContext(), Helper.getOAuthPermissions())) { !Helper.hasPermissions(getContext(), Helper.getOAuthPermissions())) {
authorized = false; authorized = false;
} }

@ -58,6 +58,7 @@ import java.util.Map;
import static android.accounts.AccountManager.newChooseAccountIntent; import static android.accounts.AccountManager.newChooseAccountIntent;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
public class FragmentGmail extends FragmentBase { public class FragmentGmail extends FragmentBase {
private ViewGroup view; private ViewGroup view;
@ -264,7 +265,7 @@ public class FragmentGmail extends FragmentBase {
Log.i("Requesting token name=" + account.name); Log.i("Requesting token name=" + account.name);
am.getAuthToken( am.getAuthToken(
account, account,
EmailService.getAuthTokenType(type), ServiceAuthenticator.getAuthTokenType(type),
new Bundle(), new Bundle(),
getActivity(), getActivity(),
new AccountManagerCallback<Bundle>() { new AccountManagerCallback<Bundle>() {
@ -369,7 +370,7 @@ public class FragmentGmail extends FragmentBase {
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.imap.host, provider.imap.port, provider.imap.host, provider.imap.port,
EmailService.AUTH_TYPE_GMAIL, null, AUTH_TYPE_GMAIL, null,
user, password, user, password,
null, null); null, null);
@ -384,7 +385,7 @@ public class FragmentGmail extends FragmentBase {
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.smtp.host, provider.smtp.port, provider.smtp.host, provider.smtp.port,
EmailService.AUTH_TYPE_GMAIL, null, AUTH_TYPE_GMAIL, null,
user, password, user, password,
null, null); null, null);
max_size = iservice.getMaxSize(); max_size = iservice.getMaxSize();
@ -402,7 +403,7 @@ public class FragmentGmail extends FragmentBase {
account.host = provider.imap.host; account.host = provider.imap.host;
account.encryption = aencryption; account.encryption = aencryption;
account.port = provider.imap.port; account.port = provider.imap.port;
account.auth_type = EmailService.AUTH_TYPE_GMAIL; account.auth_type = AUTH_TYPE_GMAIL;
account.user = user; account.user = user;
account.password = password; account.password = password;
@ -451,7 +452,7 @@ public class FragmentGmail extends FragmentBase {
identity.host = provider.smtp.host; identity.host = provider.smtp.host;
identity.encryption = iencryption; identity.encryption = iencryption;
identity.port = provider.smtp.port; identity.port = provider.smtp.port;
identity.auth_type = EmailService.AUTH_TYPE_GMAIL; identity.auth_type = AUTH_TYPE_GMAIL;
identity.user = user; identity.user = user;
identity.password = password; identity.password = password;
identity.synchronize = true; identity.synchronize = true;

@ -19,8 +19,6 @@ package eu.faircode.email;
Copyright 2018-2020 by Marcel Bokhorst (M66B) Copyright 2018-2020 by Marcel Bokhorst (M66B)
*/ */
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
@ -68,6 +66,9 @@ import javax.mail.internet.InternetAddress;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE; import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE;
import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE; import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_OAUTH;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class FragmentIdentity extends FragmentBase { public class FragmentIdentity extends FragmentBase {
private ViewGroup view; private ViewGroup view;
@ -131,7 +132,7 @@ public class FragmentIdentity extends FragmentBase {
private long id = -1; private long id = -1;
private long copy = -1; private long copy = -1;
private long account = -1; private long account = -1;
private int auth = EmailService.AUTH_TYPE_PASSWORD; private int auth = AUTH_TYPE_PASSWORD;
private String provider = null; private String provider = null;
private String certificate = null; private String certificate = null;
private String signature = null; private String signature = null;
@ -498,9 +499,9 @@ public class FragmentIdentity extends FragmentBase {
etRealm.setText(account.realm); etRealm.setText(account.realm);
cbTrust.setChecked(false); cbTrust.setChecked(false);
etUser.setEnabled(auth == EmailService.AUTH_TYPE_PASSWORD); etUser.setEnabled(auth == AUTH_TYPE_PASSWORD);
tilPassword.setEnabled(auth == EmailService.AUTH_TYPE_PASSWORD); tilPassword.setEnabled(auth == AUTH_TYPE_PASSWORD);
btnCertificate.setEnabled(auth == EmailService.AUTH_TYPE_PASSWORD); btnCertificate.setEnabled(auth == AUTH_TYPE_PASSWORD);
} }
private void setProvider(EmailProvider provider) { private void setProvider(EmailProvider provider) {
@ -986,16 +987,7 @@ public class FragmentIdentity extends FragmentBase {
if (identity == null) if (identity == null)
return null; return null;
AccountManager am = AccountManager.get(context); return ServiceAuthenticator.getGmailToken(context, identity.user);
Account[] accounts = am.getAccountsByType(EmailService.TYPE_GOOGLE);
for (Account google : accounts)
if (identity.user.equals(google.name))
return am.blockingGetAuthToken(
google,
EmailService.getAuthTokenType(EmailService.TYPE_GOOGLE),
true);
return null;
} }
@Override @Override
@ -1137,7 +1129,7 @@ public class FragmentIdentity extends FragmentBase {
etBcc.setText(identity == null ? null : identity.bcc); etBcc.setText(identity == null ? null : identity.bcc);
cbUnicode.setChecked(identity != null && identity.unicode); cbUnicode.setChecked(identity != null && identity.unicode);
auth = (identity == null ? EmailService.AUTH_TYPE_PASSWORD : identity.auth_type); auth = (identity == null ? AUTH_TYPE_PASSWORD : identity.auth_type);
provider = (identity == null ? null : identity.provider); provider = (identity == null ? null : identity.provider);
if (identity == null || copy > 0) if (identity == null || copy > 0)
@ -1171,13 +1163,13 @@ public class FragmentIdentity extends FragmentBase {
Helper.setViewsEnabled(view, true); Helper.setViewsEnabled(view, true);
if (auth != EmailService.AUTH_TYPE_PASSWORD) { if (auth != AUTH_TYPE_PASSWORD) {
etUser.setEnabled(false); etUser.setEnabled(false);
tilPassword.setEnabled(false); tilPassword.setEnabled(false);
btnCertificate.setEnabled(false); btnCertificate.setEnabled(false);
} }
if (identity == null || identity.auth_type != EmailService.AUTH_TYPE_GMAIL) if (identity == null || identity.auth_type != AUTH_TYPE_GMAIL)
Helper.hide(btnOAuth); Helper.hide(btnOAuth);
cbPrimary.setEnabled(cbSynchronize.isChecked()); cbPrimary.setEnabled(cbSynchronize.isChecked());
@ -1197,7 +1189,7 @@ public class FragmentIdentity extends FragmentBase {
if (identity != null) if (identity != null)
for (int pos = 1; pos < providers.size(); pos++) { for (int pos = 1; pos < providers.size(); pos++) {
EmailProvider provider = providers.get(pos); EmailProvider provider = providers.get(pos);
if ((provider.oauth != null) == (identity.auth_type == EmailService.AUTH_TYPE_OAUTH) && if ((provider.oauth != null) == (identity.auth_type == AUTH_TYPE_OAUTH) &&
provider.smtp.host.equals(identity.host) && provider.smtp.host.equals(identity.host) &&
provider.smtp.port == identity.port && provider.smtp.port == identity.port &&
provider.smtp.starttls == (identity.encryption == EmailService.ENCRYPTION_STARTTLS)) { provider.smtp.starttls == (identity.encryption == EmailService.ENCRYPTION_STARTTLS)) {
@ -1226,7 +1218,7 @@ public class FragmentIdentity extends FragmentBase {
EntityAccount unselected = new EntityAccount(); EntityAccount unselected = new EntityAccount();
unselected.id = -1L; unselected.id = -1L;
unselected.auth_type = EmailService.AUTH_TYPE_PASSWORD; unselected.auth_type = AUTH_TYPE_PASSWORD;
unselected.name = getString(R.string.title_select); unselected.name = getString(R.string.title_select);
unselected.primary = false; unselected.primary = false;
accounts.add(0, unselected); accounts.add(0, unselected);

@ -77,6 +77,7 @@ import java.util.Map;
import javax.mail.AuthenticationFailedException; import javax.mail.AuthenticationFailedException;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_OAUTH;
public class FragmentOAuth extends FragmentBase { public class FragmentOAuth extends FragmentBase {
private String id; private String id;
@ -434,7 +435,7 @@ public class FragmentOAuth extends FragmentBase {
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.imap.host, provider.imap.port, provider.imap.host, provider.imap.port,
EmailService.AUTH_TYPE_OAUTH, provider.id, AUTH_TYPE_OAUTH, provider.id,
unique_name, state, unique_name, state,
null, null); null, null);
username = unique_name; username = unique_name;
@ -486,7 +487,7 @@ public class FragmentOAuth extends FragmentBase {
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.imap.host, provider.imap.port, provider.imap.host, provider.imap.port,
EmailService.AUTH_TYPE_OAUTH, provider.id, AUTH_TYPE_OAUTH, provider.id,
username, state, username, state,
null, null); null, null);
@ -503,7 +504,7 @@ public class FragmentOAuth extends FragmentBase {
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
provider.smtp.host, provider.smtp.port, provider.smtp.host, provider.smtp.port,
EmailService.AUTH_TYPE_OAUTH, provider.id, AUTH_TYPE_OAUTH, provider.id,
username, state, username, state,
null, null); null, null);
max_size = iservice.getMaxSize(); max_size = iservice.getMaxSize();
@ -523,7 +524,7 @@ public class FragmentOAuth extends FragmentBase {
account.host = provider.imap.host; account.host = provider.imap.host;
account.encryption = aencryption; account.encryption = aencryption;
account.port = provider.imap.port; account.port = provider.imap.port;
account.auth_type = EmailService.AUTH_TYPE_OAUTH; account.auth_type = AUTH_TYPE_OAUTH;
account.provider = provider.id; account.provider = provider.id;
account.user = username; account.user = username;
account.password = state; account.password = state;
@ -579,7 +580,7 @@ public class FragmentOAuth extends FragmentBase {
ident.host = provider.smtp.host; ident.host = provider.smtp.host;
ident.encryption = iencryption; ident.encryption = iencryption;
ident.port = provider.smtp.port; ident.port = provider.smtp.port;
ident.auth_type = EmailService.AUTH_TYPE_OAUTH; ident.auth_type = AUTH_TYPE_OAUTH;
ident.provider = provider.id; ident.provider = provider.id;
ident.user = username; ident.user = username;
ident.password = state; ident.password = state;

@ -61,6 +61,7 @@ import java.util.Objects;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE; import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE;
import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE; import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class FragmentPop extends FragmentBase { public class FragmentPop extends FragmentBase {
private ViewGroup view; private ViewGroup view;
@ -383,7 +384,7 @@ public class FragmentPop extends FragmentBase {
EmailService.PURPOSE_CHECK, true)) { EmailService.PURPOSE_CHECK, true)) {
iservice.connect( iservice.connect(
host, Integer.parseInt(port), host, Integer.parseInt(port),
EmailService.AUTH_TYPE_PASSWORD, null, AUTH_TYPE_PASSWORD, null,
user, password, user, password,
null, null); null, null);
} }
@ -408,7 +409,7 @@ public class FragmentPop extends FragmentBase {
account.encryption = encryption; account.encryption = encryption;
account.insecure = insecure; account.insecure = insecure;
account.port = Integer.parseInt(port); account.port = Integer.parseInt(port);
account.auth_type = EmailService.AUTH_TYPE_PASSWORD; account.auth_type = AUTH_TYPE_PASSWORD;
account.user = user; account.user = user;
account.password = password; account.password = password;

@ -54,6 +54,8 @@ import java.util.List;
import javax.mail.AuthenticationFailedException; import javax.mail.AuthenticationFailedException;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD;
public class FragmentQuickSetup extends FragmentBase { public class FragmentQuickSetup extends FragmentBase {
private ViewGroup view; private ViewGroup view;
private ScrollView scroll; private ScrollView scroll;
@ -303,7 +305,7 @@ public class FragmentQuickSetup extends FragmentBase {
try { try {
iservice.connect( iservice.connect(
provider.imap.host, provider.imap.port, provider.imap.host, provider.imap.port,
EmailService.AUTH_TYPE_PASSWORD, null, AUTH_TYPE_PASSWORD, null,
user, password, user, password,
null, imap_fingerprint); null, imap_fingerprint);
} catch (EmailService.UntrustedException ex) { } catch (EmailService.UntrustedException ex) {
@ -311,7 +313,7 @@ public class FragmentQuickSetup extends FragmentBase {
imap_fingerprint = ex.getFingerprint(); imap_fingerprint = ex.getFingerprint();
iservice.connect( iservice.connect(
provider.imap.host, provider.imap.port, provider.imap.host, provider.imap.port,
EmailService.AUTH_TYPE_PASSWORD, null, AUTH_TYPE_PASSWORD, null,
user, password, user, password,
null, imap_fingerprint); null, imap_fingerprint);
} else } else
@ -328,7 +330,7 @@ public class FragmentQuickSetup extends FragmentBase {
Log.i("Retry with user=" + user); Log.i("Retry with user=" + user);
iservice.connect( iservice.connect(
provider.imap.host, provider.imap.port, provider.imap.host, provider.imap.port,
EmailService.AUTH_TYPE_PASSWORD, null, AUTH_TYPE_PASSWORD, null,
user, password, user, password,
null, null); null, null);
} catch (Throwable ex1) { } catch (Throwable ex1) {
@ -353,7 +355,7 @@ public class FragmentQuickSetup extends FragmentBase {
iservice.setUseIp(provider.useip, null); iservice.setUseIp(provider.useip, null);
iservice.connect( iservice.connect(
provider.smtp.host, provider.smtp.port, provider.smtp.host, provider.smtp.port,
EmailService.AUTH_TYPE_PASSWORD, null, AUTH_TYPE_PASSWORD, null,
user, password, user, password,
null, smtp_fingerprint); null, smtp_fingerprint);
max_size = iservice.getMaxSize(); max_size = iservice.getMaxSize();
@ -382,7 +384,7 @@ public class FragmentQuickSetup extends FragmentBase {
account.host = provider.imap.host; account.host = provider.imap.host;
account.encryption = aencryption; account.encryption = aencryption;
account.port = provider.imap.port; account.port = provider.imap.port;
account.auth_type = EmailService.AUTH_TYPE_PASSWORD; account.auth_type = AUTH_TYPE_PASSWORD;
account.user = user; account.user = user;
account.password = password; account.password = password;
account.fingerprint = imap_fingerprint; account.fingerprint = imap_fingerprint;
@ -432,7 +434,7 @@ public class FragmentQuickSetup extends FragmentBase {
identity.host = provider.smtp.host; identity.host = provider.smtp.host;
identity.encryption = iencryption; identity.encryption = iencryption;
identity.port = provider.smtp.port; identity.port = provider.smtp.port;
identity.auth_type = EmailService.AUTH_TYPE_PASSWORD; identity.auth_type = AUTH_TYPE_PASSWORD;
identity.user = user; identity.user = user;
identity.password = password; identity.password = password;
identity.fingerprint = smtp_fingerprint; identity.fingerprint = smtp_fingerprint;

@ -0,0 +1,179 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2020 by Marcel Bokhorst (M66B)
*/
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretPost;
import net.openid.appauth.NoClientAuthentication;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.Semaphore;
import javax.mail.Authenticator;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
class ServiceAuthenticator extends Authenticator {
private Context context;
private int auth;
private String provider;
private String user;
private String password;
private IAuthenticated intf;
static final int AUTH_TYPE_PASSWORD = 1;
static final int AUTH_TYPE_GMAIL = 2;
static final int AUTH_TYPE_OAUTH = 3;
static final String TYPE_GOOGLE = "com.google";
ServiceAuthenticator(
Context context,
int auth, String provider,
String user, String password,
IAuthenticated intf) {
this.context = context.getApplicationContext();
this.auth = auth;
this.provider = provider;
this.user = user;
this.password = password;
this.intf = intf;
}
void expire() {
if (auth == AUTH_TYPE_GMAIL) {
EntityLog.log(context, user + " token expired");
expireGmailToken(context, password);
password = null;
}
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
String token = password;
try {
if (auth == AUTH_TYPE_GMAIL) {
String oldToken = password;
token = getGmailToken(context, user);
password = token;
if (intf != null && !Objects.equals(oldToken, token))
intf.onPasswordChanged(password);
} else if (auth == AUTH_TYPE_OAUTH) {
AuthState authState = AuthState.jsonDeserialize(password);
String oldToken = authState.getAccessToken();
OAuthRefresh(context, provider, authState);
token = authState.getAccessToken();
password = authState.jsonSerializeString();
if (intf != null && !Objects.equals(oldToken, token))
intf.onPasswordChanged(password);
}
} catch (Throwable ex) {
Log.e(ex);
}
Log.i(user + " returning password");
return new PasswordAuthentication(user, token);
}
interface IAuthenticated {
void onPasswordChanged(String newPassword);
}
static String getGmailToken(Context context, String user) throws AuthenticatorException, OperationCanceledException, IOException {
AccountManager am = AccountManager.get(context);
Account[] accounts = am.getAccountsByType(TYPE_GOOGLE);
for (Account account : accounts)
if (user.equals(account.name)) {
Log.i("Getting token user=" + user);
String token = am.blockingGetAuthToken(account, getAuthTokenType(TYPE_GOOGLE), true);
if (token == null)
throw new AuthenticatorException("No token for " + user);
return token;
}
throw new AuthenticatorException("Account not found for " + user);
}
private static void expireGmailToken(Context context, String token) {
try {
AccountManager am = AccountManager.get(context);
am.invalidateAuthToken(TYPE_GOOGLE, token);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void OAuthRefresh(Context context, String id, AuthState authState) throws MessagingException {
try {
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();
Semaphore semaphore = new Semaphore(0);
Log.i("OAuth refresh");
AuthorizationService authService = new AuthorizationService(context);
authState.performActionWithFreshTokens(
authService,
clientAuth,
new AuthState.AuthStateAction() {
@Override
public void execute(String accessToken, String idToken, AuthorizationException error) {
if (error != null)
holder.error = error;
semaphore.release();
}
});
semaphore.acquire();
Log.i("OAuth refreshed");
if (holder.error != null)
throw holder.error;
} catch (Exception ex) {
throw new MessagingException("OAuth refresh", ex);
}
}
static String getAuthTokenType(String type) {
// https://developers.google.com/gmail/imap/xoauth2-protocol
if ("com.google".equals(type))
return "oauth2:https://mail.google.com/";
return null;
}
private static class ErrorHolder {
AuthorizationException error;
}
}
Loading…
Cancel
Save