Added Gmail state

pull/187/head
M66B 4 years ago
parent bacc2c1024
commit 8c8d3c8ddf

@ -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.Dialog;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@ -103,7 +101,6 @@ 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;
@ -777,16 +774,8 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
EntityAccount account = EntityAccount.fromJSON(jaccount);
if (account.auth_type == AUTH_TYPE_GMAIL) {
AccountManager am = AccountManager.get(context);
boolean found = false;
for (Account google : am.getAccountsByType(TYPE_GOOGLE))
if (account.user.equals(google.name)) {
found = true;
break;
}
if (!found) {
Log.i("Google account not found email=" + account.user);
if (GmailState.getAccount(context, account.user) == null) {
Log.i("Google account not found user=" + account.user);
continue;
}
}

@ -364,10 +364,9 @@ public class EmailService implements AutoCloseable {
connect(host, port, auth, user, authenticator, factory);
} catch (AuthenticationFailedException ex) {
// Refresh token
if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH) {
try {
authenticator.expire();
authenticator.refreshToken(true);
connect(host, port, auth, user, authenticator, factory);
} catch (Exception ex1) {
Log.e(ex1);

@ -1334,7 +1334,9 @@ public class FragmentAccount extends FragmentBase {
if (account == null)
return null;
return ServiceAuthenticator.getGmailToken(context, account.user);
GmailState state = GmailState.jsonDeserialize(account.password);
state.refresh(context, account.user, true);
return state.jsonSerializeString();
}
@Override

@ -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.GmailState.TYPE_GOOGLE;
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
public class FragmentGmail extends FragmentBase {
@ -75,8 +76,6 @@ public class FragmentGmail extends FragmentBase {
private Group grpError;
private static String TYPE_GOOGLE = "com.google";
@Override
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -318,11 +317,13 @@ public class FragmentGmail extends FragmentBase {
}
}
private void onAuthorized(String user, String password) {
private void onAuthorized(String user, String token) {
GmailState state = GmailState.jsonDeserialize(token);
Bundle args = new Bundle();
args.putString("name", etName.getText().toString().trim());
args.putString("user", user);
args.putString("password", password);
args.putString("password", state.jsonSerializeString());
new SimpleTask<Void>() {
@Override

@ -987,7 +987,9 @@ public class FragmentIdentity extends FragmentBase {
if (identity == null)
return null;
return ServiceAuthenticator.getGmailToken(context, identity.user);
GmailState state = GmailState.jsonDeserialize(identity.password);
state.refresh(context, identity.user, true);
return state.jsonSerializeString();
}
@Override

@ -0,0 +1,118 @@
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 androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;
public class GmailState {
private String token;
private long acquired;
static final String TYPE_GOOGLE = "com.google";
private static final long TOKEN_LIFETIME = 3600 * 1000L; // milliseconds
private GmailState(String token, long acquired) {
this.token = token;
this.acquired = acquired;
}
@NonNull
String getAccessToken() throws AuthenticatorException {
if (token == null)
throw new AuthenticatorException("no token");
return token;
}
void refresh(@NonNull Context context, @NonNull String user, boolean expired) throws AuthenticatorException, OperationCanceledException, IOException {
if (expired || acquired + TOKEN_LIFETIME < new Date().getTime())
try {
if (token != null) {
EntityLog.log(context, "Invalidating token user=" + user);
AccountManager am = AccountManager.get(context);
am.invalidateAuthToken(TYPE_GOOGLE, token);
}
token = null;
acquired = 0;
} catch (Throwable ex) {
Log.e(ex);
}
Account account = getAccount(context, user);
if (account == null)
throw new AuthenticatorException("Account not found for " + user);
EntityLog.log(context, "Getting token user=" + user);
AccountManager am = AccountManager.get(context);
String newToken = am.blockingGetAuthToken(account, ServiceAuthenticator.getAuthTokenType(TYPE_GOOGLE), true);
if (newToken != null && !newToken.equals(token)) {
token = newToken;
acquired = new Date().getTime();
}
if (token == null)
throw new AuthenticatorException("No token for " + user);
}
static Account getAccount(Context context, String user) {
AccountManager am = AccountManager.get(context);
Account[] accounts = am.getAccountsByType(TYPE_GOOGLE);
for (Account account : accounts)
if (Objects.equals(account.name, user))
return account;
return null;
}
public String jsonSerializeString() {
try {
JSONObject jobject = new JSONObject();
jobject.put("token", token);
jobject.put("acquired", acquired);
return jobject.toString();
} catch (JSONException ex) {
Log.e(ex);
return null;
}
}
static GmailState jsonDeserialize(@NonNull String password) {
try {
JSONObject jobject = new JSONObject(password);
String token = jobject.getString("token");
long acquired = jobject.getLong("acquired");
return new GmailState(token, acquired);
} catch (JSONException ex) {
return new GmailState(password, new Date().getTime());
}
}
}

@ -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.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
@ -32,8 +30,9 @@ import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretPost;
import net.openid.appauth.NoClientAuthentication;
import org.json.JSONException;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.Semaphore;
@ -41,22 +40,20 @@ import javax.mail.Authenticator;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
class ServiceAuthenticator extends Authenticator {
import static eu.faircode.email.GmailState.TYPE_GOOGLE;
public class ServiceAuthenticator extends Authenticator {
private Context context;
private int auth;
private String provider;
private String user;
private String password;
private IAuthenticated intf;
private long refreshed;
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";
private static final long GMAIL_EXPIRY = 3600 * 1000L;
ServiceAuthenticator(
Context context,
int auth, String provider,
@ -68,76 +65,52 @@ class ServiceAuthenticator extends Authenticator {
this.user = user;
this.password = password;
this.intf = intf;
this.refreshed = new Date().getTime();
}
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) {
long now = new Date().getTime();
if (now - refreshed > GMAIL_EXPIRY)
expire();
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);
}
token = refreshToken(false);
} catch (Throwable ex) {
Log.e(ex);
}
Log.i(user + " returning password");
return new PasswordAuthentication(user, token);
}
interface IAuthenticated {
void onPasswordChanged(String newPassword);
}
String refreshToken(boolean expired) throws AuthenticatorException, OperationCanceledException, IOException, JSONException, MessagingException {
if (auth == AUTH_TYPE_GMAIL) {
GmailState gmailState = GmailState.jsonDeserialize(password);
gmailState.refresh(context, user, expired);
String newPassword = gmailState.jsonSerializeString();
if (!Objects.equals(password, newPassword)) {
password = newPassword;
if (intf != null)
intf.onPasswordChanged(password);
}
return gmailState.getAccessToken();
} else if (auth == AUTH_TYPE_OAUTH) {
AuthState authState = AuthState.jsonDeserialize(password);
OAuthRefresh(context, provider, authState);
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;
String newPassword = authState.jsonSerializeString();
if (!Objects.equals(password, newPassword)) {
password = newPassword;
if (intf != null)
intf.onPasswordChanged(password);
}
throw new AuthenticatorException("Account not found for " + user);
return authState.getAccessToken();
} else
return password;
}
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);
}
interface IAuthenticated {
void onPasswordChanged(String newPassword);
}
private static void OAuthRefresh(Context context, String id, AuthState authState) throws MessagingException {
@ -178,7 +151,7 @@ class ServiceAuthenticator extends Authenticator {
static String getAuthTokenType(String type) {
// https://developers.google.com/gmail/imap/xoauth2-protocol
if ("com.google".equals(type))
if (TYPE_GOOGLE.equals(type))
return "oauth2:https://mail.google.com/";
return null;
}

Loading…
Cancel
Save