From 480eaa18f679b36f9fe09b94c338a4ca7b82888e Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 25 Oct 2020 22:20:48 +0100 Subject: [PATCH] Use service authenticator --- FAQ.md | 2 +- .../java/eu/faircode/email/ActivitySetup.java | 7 +- .../eu/faircode/email/AdapterAccount.java | 4 +- .../eu/faircode/email/AdapterIdentity.java | 4 +- app/src/main/java/eu/faircode/email/DB.java | 6 +- .../java/eu/faircode/email/EmailService.java | 197 +++++++----------- .../eu/faircode/email/FragmentAccount.java | 28 +-- .../eu/faircode/email/FragmentAccounts.java | 4 +- .../java/eu/faircode/email/FragmentGmail.java | 11 +- .../eu/faircode/email/FragmentIdentity.java | 34 ++- .../java/eu/faircode/email/FragmentOAuth.java | 11 +- .../java/eu/faircode/email/FragmentPop.java | 5 +- .../eu/faircode/email/FragmentQuickSetup.java | 14 +- .../faircode/email/ServiceAuthenticator.java | 179 ++++++++++++++++ 14 files changed, 314 insertions(+), 192 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/ServiceAuthenticator.java diff --git a/FAQ.md b/FAQ.md index d8c1f27464..1e85bf6f3b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -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 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 ...* means that the Android account manager was not able to refresh the authorization of a Gmail account due to problems with the internet connection diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index 199b2ddf34..aaa6a664ed 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -102,6 +102,9 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; 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 { private View view; private DrawerLayout drawerLayout; @@ -773,10 +776,10 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac JSONObject jaccount = (JSONObject) jaccounts.get(a); 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); boolean found = false; - for (Account google : am.getAccountsByType(EmailService.TYPE_GOOGLE)) + for (Account google : am.getAccountsByType(TYPE_GOOGLE)) if (account.user.equals(google.name)) { found = true; break; diff --git a/app/src/main/java/eu/faircode/email/AdapterAccount.java b/app/src/main/java/eu/faircode/email/AdapterAccount.java index 221428a43c..805bbc2880 100644 --- a/app/src/main/java/eu/faircode/email/AdapterAccount.java +++ b/app/src/main/java/eu/faircode/email/AdapterAccount.java @@ -64,6 +64,8 @@ import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD; + public class AdapterAccount extends RecyclerView.Adapter { private Fragment parentFragment; private boolean settings; @@ -152,7 +154,7 @@ public class AdapterAccount extends RecyclerView.Adapter { private Fragment parentFragment; private Context context; @@ -123,7 +125,7 @@ public class AdapterIdentity extends RecyclerView.Adapter getFolders() throws MessagingException { List folders = new ArrayList<>(); diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index b157f710bd..8f7567c407 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -19,8 +19,6 @@ package eu.faircode.email; Copyright 2018-2020 by Marcel Bokhorst (M66B) */ -import android.accounts.Account; -import android.accounts.AccountManager; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; @@ -74,6 +72,9 @@ import javax.mail.Folder; 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_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 { private ViewGroup view; @@ -153,7 +154,7 @@ public class FragmentAccount extends FragmentBase { private long id = -1; private long copy = -1; - private int auth = EmailService.AUTH_TYPE_PASSWORD; + private int auth = AUTH_TYPE_PASSWORD; private String provider = null; private String certificate = null; private boolean saving = false; @@ -261,7 +262,7 @@ public class FragmentAccount extends FragmentBase { public void onItemSelected(AdapterView adapterView, View view, int position, long itemid) { EmailProvider provider = (EmailProvider) adapterView.getSelectedItem(); tvGmailHint.setVisibility( - auth == EmailService.AUTH_TYPE_PASSWORD && "gmail".equals(provider.id) + auth == AUTH_TYPE_PASSWORD && "gmail".equals(provider.id) ? View.VISIBLE : View.GONE); grpServer.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) return null; - AccountManager am = AccountManager.get(context); - 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; + return ServiceAuthenticator.getGmailToken(context, account.user); } @Override @@ -1449,7 +1441,7 @@ public class FragmentAccount extends FragmentBase { boolean found = false; for (int pos = 2; pos < providers.size(); 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.port == account.port && provider.imap.starttls == (account.encryption == EmailService.ENCRYPTION_STARTTLS)) { @@ -1514,7 +1506,7 @@ public class FragmentAccount extends FragmentBase { else 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); new SimpleTask() { @@ -1550,13 +1542,13 @@ public class FragmentAccount extends FragmentBase { Helper.setViewsEnabled(view, true); - if (auth != EmailService.AUTH_TYPE_PASSWORD) { + if (auth != AUTH_TYPE_PASSWORD) { etUser.setEnabled(false); tilPassword.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)); cbOnDemand.setEnabled(cbSynchronize.isChecked()); diff --git a/app/src/main/java/eu/faircode/email/FragmentAccounts.java b/app/src/main/java/eu/faircode/email/FragmentAccounts.java index 4d3b3c5720..c0d80c71c7 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccounts.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccounts.java @@ -56,6 +56,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD; + public class FragmentAccounts extends FragmentBase { private boolean settings; @@ -267,7 +269,7 @@ public class FragmentAccounts extends FragmentBase { boolean authorized = true; for (TupleAccountEx account : accounts) - if (account.auth_type != EmailService.AUTH_TYPE_PASSWORD && + if (account.auth_type != AUTH_TYPE_PASSWORD && !Helper.hasPermissions(getContext(), Helper.getOAuthPermissions())) { authorized = false; } diff --git a/app/src/main/java/eu/faircode/email/FragmentGmail.java b/app/src/main/java/eu/faircode/email/FragmentGmail.java index 8078b250f5..874d2d121e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentGmail.java +++ b/app/src/main/java/eu/faircode/email/FragmentGmail.java @@ -58,6 +58,7 @@ import java.util.Map; import static android.accounts.AccountManager.newChooseAccountIntent; import static android.app.Activity.RESULT_OK; +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL; public class FragmentGmail extends FragmentBase { private ViewGroup view; @@ -264,7 +265,7 @@ public class FragmentGmail extends FragmentBase { Log.i("Requesting token name=" + account.name); am.getAuthToken( account, - EmailService.getAuthTokenType(type), + ServiceAuthenticator.getAuthTokenType(type), new Bundle(), getActivity(), new AccountManagerCallback() { @@ -369,7 +370,7 @@ public class FragmentGmail extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( provider.imap.host, provider.imap.port, - EmailService.AUTH_TYPE_GMAIL, null, + AUTH_TYPE_GMAIL, null, user, password, null, null); @@ -384,7 +385,7 @@ public class FragmentGmail extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( provider.smtp.host, provider.smtp.port, - EmailService.AUTH_TYPE_GMAIL, null, + AUTH_TYPE_GMAIL, null, user, password, null, null); max_size = iservice.getMaxSize(); @@ -402,7 +403,7 @@ public class FragmentGmail extends FragmentBase { account.host = provider.imap.host; account.encryption = aencryption; account.port = provider.imap.port; - account.auth_type = EmailService.AUTH_TYPE_GMAIL; + account.auth_type = AUTH_TYPE_GMAIL; account.user = user; account.password = password; @@ -451,7 +452,7 @@ public class FragmentGmail extends FragmentBase { identity.host = provider.smtp.host; identity.encryption = iencryption; identity.port = provider.smtp.port; - identity.auth_type = EmailService.AUTH_TYPE_GMAIL; + identity.auth_type = AUTH_TYPE_GMAIL; identity.user = user; identity.password = password; identity.synchronize = true; diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 84d8f91d99..766e43a417 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -19,8 +19,6 @@ package eu.faircode.email; Copyright 2018-2020 by Marcel Bokhorst (M66B) */ -import android.accounts.Account; -import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; import android.graphics.Color; @@ -68,6 +66,9 @@ import javax.mail.internet.InternetAddress; 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_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 { private ViewGroup view; @@ -131,7 +132,7 @@ public class FragmentIdentity extends FragmentBase { private long id = -1; private long copy = -1; private long account = -1; - private int auth = EmailService.AUTH_TYPE_PASSWORD; + private int auth = AUTH_TYPE_PASSWORD; private String provider = null; private String certificate = null; private String signature = null; @@ -498,9 +499,9 @@ public class FragmentIdentity extends FragmentBase { etRealm.setText(account.realm); cbTrust.setChecked(false); - etUser.setEnabled(auth == EmailService.AUTH_TYPE_PASSWORD); - tilPassword.setEnabled(auth == EmailService.AUTH_TYPE_PASSWORD); - btnCertificate.setEnabled(auth == EmailService.AUTH_TYPE_PASSWORD); + etUser.setEnabled(auth == AUTH_TYPE_PASSWORD); + tilPassword.setEnabled(auth == AUTH_TYPE_PASSWORD); + btnCertificate.setEnabled(auth == AUTH_TYPE_PASSWORD); } private void setProvider(EmailProvider provider) { @@ -986,16 +987,7 @@ public class FragmentIdentity extends FragmentBase { if (identity == null) return null; - AccountManager am = AccountManager.get(context); - 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; + return ServiceAuthenticator.getGmailToken(context, identity.user); } @Override @@ -1137,7 +1129,7 @@ public class FragmentIdentity extends FragmentBase { etBcc.setText(identity == null ? null : identity.bcc); 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); if (identity == null || copy > 0) @@ -1171,13 +1163,13 @@ public class FragmentIdentity extends FragmentBase { Helper.setViewsEnabled(view, true); - if (auth != EmailService.AUTH_TYPE_PASSWORD) { + if (auth != AUTH_TYPE_PASSWORD) { etUser.setEnabled(false); tilPassword.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); cbPrimary.setEnabled(cbSynchronize.isChecked()); @@ -1197,7 +1189,7 @@ public class FragmentIdentity extends FragmentBase { if (identity != null) for (int pos = 1; pos < providers.size(); 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.port == identity.port && provider.smtp.starttls == (identity.encryption == EmailService.ENCRYPTION_STARTTLS)) { @@ -1226,7 +1218,7 @@ public class FragmentIdentity extends FragmentBase { EntityAccount unselected = new EntityAccount(); unselected.id = -1L; - unselected.auth_type = EmailService.AUTH_TYPE_PASSWORD; + unselected.auth_type = AUTH_TYPE_PASSWORD; unselected.name = getString(R.string.title_select); unselected.primary = false; accounts.add(0, unselected); diff --git a/app/src/main/java/eu/faircode/email/FragmentOAuth.java b/app/src/main/java/eu/faircode/email/FragmentOAuth.java index c2860f13f3..6f48f0ca20 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOAuth.java +++ b/app/src/main/java/eu/faircode/email/FragmentOAuth.java @@ -77,6 +77,7 @@ import java.util.Map; import javax.mail.AuthenticationFailedException; import static android.app.Activity.RESULT_OK; +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_OAUTH; public class FragmentOAuth extends FragmentBase { private String id; @@ -434,7 +435,7 @@ public class FragmentOAuth extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( provider.imap.host, provider.imap.port, - EmailService.AUTH_TYPE_OAUTH, provider.id, + AUTH_TYPE_OAUTH, provider.id, unique_name, state, null, null); username = unique_name; @@ -486,7 +487,7 @@ public class FragmentOAuth extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( provider.imap.host, provider.imap.port, - EmailService.AUTH_TYPE_OAUTH, provider.id, + AUTH_TYPE_OAUTH, provider.id, username, state, null, null); @@ -503,7 +504,7 @@ public class FragmentOAuth extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( provider.smtp.host, provider.smtp.port, - EmailService.AUTH_TYPE_OAUTH, provider.id, + AUTH_TYPE_OAUTH, provider.id, username, state, null, null); max_size = iservice.getMaxSize(); @@ -523,7 +524,7 @@ public class FragmentOAuth extends FragmentBase { account.host = provider.imap.host; account.encryption = aencryption; account.port = provider.imap.port; - account.auth_type = EmailService.AUTH_TYPE_OAUTH; + account.auth_type = AUTH_TYPE_OAUTH; account.provider = provider.id; account.user = username; account.password = state; @@ -579,7 +580,7 @@ public class FragmentOAuth extends FragmentBase { ident.host = provider.smtp.host; ident.encryption = iencryption; ident.port = provider.smtp.port; - ident.auth_type = EmailService.AUTH_TYPE_OAUTH; + ident.auth_type = AUTH_TYPE_OAUTH; ident.provider = provider.id; ident.user = username; ident.password = state; diff --git a/app/src/main/java/eu/faircode/email/FragmentPop.java b/app/src/main/java/eu/faircode/email/FragmentPop.java index 506b1162f8..c1398c3f30 100644 --- a/app/src/main/java/eu/faircode/email/FragmentPop.java +++ b/app/src/main/java/eu/faircode/email/FragmentPop.java @@ -61,6 +61,7 @@ import java.util.Objects; 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_PASSWORD_TOGGLE; +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD; public class FragmentPop extends FragmentBase { private ViewGroup view; @@ -383,7 +384,7 @@ public class FragmentPop extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( host, Integer.parseInt(port), - EmailService.AUTH_TYPE_PASSWORD, null, + AUTH_TYPE_PASSWORD, null, user, password, null, null); } @@ -408,7 +409,7 @@ public class FragmentPop extends FragmentBase { account.encryption = encryption; account.insecure = insecure; account.port = Integer.parseInt(port); - account.auth_type = EmailService.AUTH_TYPE_PASSWORD; + account.auth_type = AUTH_TYPE_PASSWORD; account.user = user; account.password = password; diff --git a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java index b383233ef6..51d2da5ab2 100644 --- a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java +++ b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java @@ -54,6 +54,8 @@ import java.util.List; import javax.mail.AuthenticationFailedException; +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD; + public class FragmentQuickSetup extends FragmentBase { private ViewGroup view; private ScrollView scroll; @@ -303,7 +305,7 @@ public class FragmentQuickSetup extends FragmentBase { try { iservice.connect( provider.imap.host, provider.imap.port, - EmailService.AUTH_TYPE_PASSWORD, null, + AUTH_TYPE_PASSWORD, null, user, password, null, imap_fingerprint); } catch (EmailService.UntrustedException ex) { @@ -311,7 +313,7 @@ public class FragmentQuickSetup extends FragmentBase { imap_fingerprint = ex.getFingerprint(); iservice.connect( provider.imap.host, provider.imap.port, - EmailService.AUTH_TYPE_PASSWORD, null, + AUTH_TYPE_PASSWORD, null, user, password, null, imap_fingerprint); } else @@ -328,7 +330,7 @@ public class FragmentQuickSetup extends FragmentBase { Log.i("Retry with user=" + user); iservice.connect( provider.imap.host, provider.imap.port, - EmailService.AUTH_TYPE_PASSWORD, null, + AUTH_TYPE_PASSWORD, null, user, password, null, null); } catch (Throwable ex1) { @@ -353,7 +355,7 @@ public class FragmentQuickSetup extends FragmentBase { iservice.setUseIp(provider.useip, null); iservice.connect( provider.smtp.host, provider.smtp.port, - EmailService.AUTH_TYPE_PASSWORD, null, + AUTH_TYPE_PASSWORD, null, user, password, null, smtp_fingerprint); max_size = iservice.getMaxSize(); @@ -382,7 +384,7 @@ public class FragmentQuickSetup extends FragmentBase { account.host = provider.imap.host; account.encryption = aencryption; account.port = provider.imap.port; - account.auth_type = EmailService.AUTH_TYPE_PASSWORD; + account.auth_type = AUTH_TYPE_PASSWORD; account.user = user; account.password = password; account.fingerprint = imap_fingerprint; @@ -432,7 +434,7 @@ public class FragmentQuickSetup extends FragmentBase { identity.host = provider.smtp.host; identity.encryption = iencryption; identity.port = provider.smtp.port; - identity.auth_type = EmailService.AUTH_TYPE_PASSWORD; + identity.auth_type = AUTH_TYPE_PASSWORD; identity.user = user; identity.password = password; identity.fingerprint = smtp_fingerprint; diff --git a/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java b/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java new file mode 100644 index 0000000000..a9e2e43545 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java @@ -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 . + + 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; + } +}