163 lines
6.8 KiB
Java
163 lines
6.8 KiB
Java
package com.android.email.mail.internet;
|
|
|
|
import android.content.Context;
|
|
import android.text.format.DateUtils;
|
|
|
|
import com.android.email.mail.internet.OAuthAuthenticator.AuthenticationResult;
|
|
import com.android.emailcommon.Logging;
|
|
import com.android.emailcommon.mail.AuthenticationFailedException;
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
import com.android.emailcommon.provider.Account;
|
|
import com.android.emailcommon.provider.Credential;
|
|
import com.android.emailcommon.provider.HostAuth;
|
|
import com.android.mail.utils.LogUtils;
|
|
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
public class AuthenticationCache {
|
|
private static AuthenticationCache sCache;
|
|
|
|
// Threshold for refreshing a token. If the token is expected to expire within this amount of
|
|
// time, we won't even bother attempting to use it and will simply force a refresh.
|
|
private static final long EXPIRATION_THRESHOLD = 5 * DateUtils.MINUTE_IN_MILLIS;
|
|
|
|
private final Map<Long, CacheEntry> mCache;
|
|
private final OAuthAuthenticator mAuthenticator;
|
|
|
|
private class CacheEntry {
|
|
CacheEntry(long accountId, String providerId, String accessToken, String refreshToken,
|
|
long expirationTime) {
|
|
mAccountId = accountId;
|
|
mProviderId = providerId;
|
|
mAccessToken = accessToken;
|
|
mRefreshToken = refreshToken;
|
|
mExpirationTime = expirationTime;
|
|
}
|
|
|
|
final long mAccountId;
|
|
String mProviderId;
|
|
String mAccessToken;
|
|
String mRefreshToken;
|
|
long mExpirationTime;
|
|
}
|
|
|
|
public static AuthenticationCache getInstance() {
|
|
synchronized (AuthenticationCache.class) {
|
|
if (sCache == null) {
|
|
sCache = new AuthenticationCache();
|
|
}
|
|
return sCache;
|
|
}
|
|
}
|
|
|
|
private AuthenticationCache() {
|
|
mCache = new HashMap<Long, CacheEntry>();
|
|
mAuthenticator = new OAuthAuthenticator();
|
|
}
|
|
|
|
// Gets an access token for the given account. This may be whatever is currently cached, or
|
|
// it may query the server to get a new one if the old one is expired or nearly expired.
|
|
public String retrieveAccessToken(Context context, Account account) throws
|
|
MessagingException, IOException {
|
|
// Currently, we always use the same OAuth info for both sending and receiving.
|
|
// If we start to allow different credential objects for sending and receiving, this
|
|
// will need to be updated.
|
|
CacheEntry entry = null;
|
|
synchronized (mCache) {
|
|
entry = getEntry(context, account);
|
|
}
|
|
synchronized (entry) {
|
|
final long actualExpiration = entry.mExpirationTime - EXPIRATION_THRESHOLD;
|
|
if (System.currentTimeMillis() > actualExpiration) {
|
|
// This access token is pretty close to end of life. Don't bother trying to use it,
|
|
// it might just time out while we're trying to sync. Go ahead and refresh it
|
|
// immediately.
|
|
refreshEntry(context, entry);
|
|
}
|
|
return entry.mAccessToken;
|
|
}
|
|
}
|
|
|
|
public String refreshAccessToken(Context context, Account account) throws
|
|
MessagingException, IOException {
|
|
CacheEntry entry = getEntry(context, account);
|
|
synchronized (entry) {
|
|
refreshEntry(context, entry);
|
|
return entry.mAccessToken;
|
|
}
|
|
}
|
|
|
|
private CacheEntry getEntry(Context context, Account account) {
|
|
CacheEntry entry;
|
|
if (account.isSaved() && !account.isTemporary()) {
|
|
entry = mCache.get(account.mId);
|
|
if (entry == null) {
|
|
LogUtils.d(Logging.LOG_TAG, "initializing entry from database");
|
|
final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
|
|
final Credential credential = hostAuth.getOrCreateCredential(context);
|
|
entry = new CacheEntry(account.mId, credential.mProviderId, credential.mAccessToken,
|
|
credential.mRefreshToken, credential.mExpiration);
|
|
mCache.put(account.mId, entry);
|
|
}
|
|
} else {
|
|
// This account is temporary, just create a temporary entry. Don't store
|
|
// it in the cache, it won't be findable because we don't yet have an account Id.
|
|
final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
|
|
final Credential credential = hostAuth.getCredential(context);
|
|
entry = new CacheEntry(account.mId, credential.mProviderId, credential.mAccessToken,
|
|
credential.mRefreshToken, credential.mExpiration);
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
private void refreshEntry(Context context, CacheEntry entry) throws
|
|
IOException, MessagingException {
|
|
LogUtils.d(Logging.LOG_TAG, "AuthenticationCache refreshEntry %d", entry.mAccountId);
|
|
try {
|
|
final AuthenticationResult result = mAuthenticator.requestRefresh(context,
|
|
entry.mProviderId, entry.mRefreshToken);
|
|
// Don't set the refresh token here, it's not returned by the refresh response,
|
|
// so setting it here would make it blank.
|
|
entry.mAccessToken = result.mAccessToken;
|
|
entry.mExpirationTime = result.mExpiresInSeconds * DateUtils.SECOND_IN_MILLIS +
|
|
System.currentTimeMillis();
|
|
saveEntry(context, entry);
|
|
} catch (AuthenticationFailedException e) {
|
|
// This is fatal. Clear the tokens and rethrow the exception.
|
|
LogUtils.d(Logging.LOG_TAG, "authentication failed, clearning");
|
|
clearEntry(context, entry);
|
|
throw e;
|
|
} catch (MessagingException e) {
|
|
LogUtils.d(Logging.LOG_TAG, "messaging exception");
|
|
throw e;
|
|
} catch (IOException e) {
|
|
LogUtils.d(Logging.LOG_TAG, "IO exception");
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private void saveEntry(Context context, CacheEntry entry) {
|
|
LogUtils.d(Logging.LOG_TAG, "saveEntry");
|
|
|
|
final Account account = Account.restoreAccountWithId(context, entry.mAccountId);
|
|
final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
|
|
final Credential cred = hostAuth.getOrCreateCredential(context);
|
|
cred.mProviderId = entry.mProviderId;
|
|
cred.mAccessToken = entry.mAccessToken;
|
|
cred.mRefreshToken = entry.mRefreshToken;
|
|
cred.mExpiration = entry.mExpirationTime;
|
|
cred.update(context, cred.toContentValues());
|
|
}
|
|
|
|
private void clearEntry(Context context, CacheEntry entry) {
|
|
LogUtils.d(Logging.LOG_TAG, "clearEntry");
|
|
entry.mAccessToken = "";
|
|
entry.mRefreshToken = "";
|
|
entry.mExpirationTime = 0;
|
|
saveEntry(context, entry);
|
|
mCache.remove(entry.mAccountId);
|
|
}
|
|
}
|