diff --git a/app/src/main/java/eu/faircode/email/EmailService.java b/app/src/main/java/eu/faircode/email/EmailService.java index d7c3a30eae..fa7c1eb658 100644 --- a/app/src/main/java/eu/faircode/email/EmailService.java +++ b/app/src/main/java/eu/faircode/email/EmailService.java @@ -306,8 +306,9 @@ public class EmailService implements AutoCloseable { this.listener = listener; } - public void connect(EntityAccount account) throws MessagingException { - connect( + @NonNull + public ServiceAuthenticator connect(EntityAccount account) throws MessagingException { + return connect( account.host, account.port, account.auth_type, account.provider, account.user, account.password, @@ -323,8 +324,9 @@ public class EmailService implements AutoCloseable { account.certificate_alias, account.fingerprint); } - public void connect(EntityIdentity identity) throws MessagingException { - connect( + @NonNull + public ServiceAuthenticator connect(EntityIdentity identity) throws MessagingException { + return connect( identity.host, identity.port, identity.auth_type, identity.provider, identity.user, identity.password, @@ -340,14 +342,16 @@ public class EmailService implements AutoCloseable { identity.certificate_alias, identity.fingerprint); } - public void connect( + @NonNull + public ServiceAuthenticator 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); + return connect(host, port, auth, provider, user, password, null, certificate, fingerprint); } - private void connect( + @NonNull + private ServiceAuthenticator connect( String host, int port, int auth, String provider, String user, String password, ServiceAuthenticator.IAuthenticated intf, @@ -391,11 +395,13 @@ public class EmailService implements AutoCloseable { properties.put("mail." + protocol + ".yahoo.guid", "FAIRMAIL_V1"); connect(host, port, auth, user, authenticator, factory); + return authenticator; } catch (AuthenticationFailedException ex) { if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH) { try { authenticator.refreshToken(true); connect(host, port, auth, user, authenticator, factory); + return authenticator; } catch (Exception ex1) { Log.e(ex1); throw new AuthenticationFailedException( diff --git a/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java b/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java index 2049e46c16..4a2ebebe95 100644 --- a/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java +++ b/app/src/main/java/eu/faircode/email/ServiceAuthenticator.java @@ -84,7 +84,8 @@ public class ServiceAuthenticator extends Authenticator { return new PasswordAuthentication(user, token); } - String refreshToken(boolean expire) throws AuthenticatorException, OperationCanceledException, IOException, JSONException, MessagingException { + String refreshToken(boolean expire) + throws AuthenticatorException, OperationCanceledException, IOException, JSONException, MessagingException { if (auth == AUTH_TYPE_GMAIL) { GmailState authState = GmailState.jsonDeserialize(password); authState.refresh(context, user, expire); @@ -119,6 +120,34 @@ public class ServiceAuthenticator extends Authenticator { return password; } + boolean isTokenValid(int minValidity) { + Long expiration = null; + long validUntil = new Date().getTime() + + Math.round(minValidity * 60 * 1000L * 1.1f); + + try { + if (auth == AUTH_TYPE_GMAIL) { + GmailState authState = GmailState.jsonDeserialize(password); + expiration = authState.getAccessTokenExpirationTime(); + } else if (auth == AUTH_TYPE_OAUTH) { + AuthState authState = AuthState.jsonDeserialize(password); + expiration = authState.getAccessTokenExpirationTime(); + } + } catch (JSONException ex) { + Log.e(ex); + } + + if (expiration != null && expiration < validUntil) { + EntityLog.log(context, user + " token" + + " expiration=" + new Date(expiration) + + " valid until" + new Date(validUntil) + + " min valid=" + minValidity + "m"); + return false; + } + + return true; + } + interface IAuthenticated { void onPasswordChanged(String newPassword); } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index b366aba5ff..f3624d8a22 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -1265,8 +1265,9 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences db.folder().setFolderStates(account.id, null); db.account().setAccountState(account.id, "connecting"); + ServiceAuthenticator authenticator; try { - iservice.connect(account); + authenticator = iservice.connect(account); } catch (Throwable ex) { // Immediately report auth errors if (ex instanceof AuthenticationFailedException) { @@ -1855,6 +1856,9 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences "Unrecoverable", new Exception(state.getUnrecoverable())); + if (!authenticator.isTokenValid(account.poll_interval)) + throw new StoreClosedException(iservice.getStore(), "Token invalidation"); + // Sends store NOOP if (EmailService.SEPARATE_STORE_CONNECTION) { EntityLog.log(this, account.name + " checking store" +