Skip to content

Instantly share code, notes, and snippets.

@nsivabalan
Last active September 8, 2020 15:23
Show Gist options
  • Select an option

  • Save nsivabalan/3f9a7e77bfe8ebb41e597e3d8f1eef2d to your computer and use it in GitHub Desktop.

Select an option

Save nsivabalan/3f9a7e77bfe8ebb41e597e3d8f1eef2d to your computer and use it in GitHub Desktop.
package com.ubercab.core.oauth_token_manager;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.ubercab.experiment.DynamicExperiments;
import com.ubercab.experiment.model.ExperimentUpdate;
import com.ubercab.lumber.core.Lumber;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import rx.Subscription;
public class OAuthCore {
private static final String TAG = OAuthCore.class.getName();
private static OAuthCore singleInstance;
private AtomicBoolean asyncRetrievalInProgress = new AtomicBoolean(false);
private AtomicBoolean logout = new AtomicBoolean(false);
private OAuthTokenManager oAuthTokenManager;
private ExecutorService executorService;
@Nullable private Subscription experimentSubscription;
@Nullable private DynamicExperiments dynamicExperiments;
@VisibleForTesting
@Nullable private ExperimentUpdate oauthEnabled;
@VisibleForTesting @Nullable ExperimentUpdate oAuthLogoutStatusCodeKillSwitch;
private OAuthCore(OAuthTokenManager oAuthTokenManager, ExecutorService executorService) {
this.oAuthTokenManager = oAuthTokenManager;
this.executorService = executorService;
}
/**
* Singleton instantiation of {@link OAuthCore}.
*
* @param oAuthTokenManager instance of {@link OAuthTokenManager} to use.
* @return the singleton instance of {@link OAuthCore}.
*/
public synchronized static OAuthCore getInstance(OAuthTokenManager oAuthTokenManager,
ExecutorService executorService) {
if (singleInstance == null) {
singleInstance = new OAuthCore(oAuthTokenManager, executorService);
}
return singleInstance;
}
/**
* Resets all tracking variables.
*/
public void reset() {
asyncRetrievalInProgress.set(false);
logout.set(false);
}
/*
* @return true if logout is set, false otherwise.
*/
public boolean isLoggedout(){
return logout.get();
}
public boolean isRefreshTokenIsNull() {
return oAuthTokenManager.getRefreshToken() == null;
}
// Every new call should invoke this and only then should call other apis in this class.
public synchronized boolean shouldSkipOAuth() {
if (oAuthTokenManager.getRefreshToken() == null) {
logout.set(false);
if (experimentSubscription != null) {
experimentSubscription.unsubscribe();
experimentSubscription = null;
}
return false;
}
if (experimentSubscription == null) {
startSubscription(dynamicExperiments);
}
if (oauthEnabled == null || !oauthEnabled.isTreated()) {
return false;
}
return true;
}
/**
* Fetches access token to refresh the expired one. Caller is expected to check if access token is
* expired before calling this method, because this method will take a lock and wait for any
* other threads that are executing. So, unless the token is expired, its is better to avoid
* calling this method. There are guards in place to ensure only one network call is made, so
* correctness will be guaranteed eitherways.
*
* @return the token thus refreshed.
*/
public synchronized String retrieveAccessToken() {
if (!oAuthTokenManager.isAccessTokenExpired()) { // do we need to check for log out here.
return oAuthTokenManager.getAccessToken();
} else if (!logout.get()) {
// fetch access token in blocking manner with some configured number of retries.
// after fetching if log out -> set log out.
}
// if log out set, what to return ?
}
/**
* Check if the access token would expire in given amount of time.
*
* @param deltaMs The number of milliseconds to check if the token would expire
* @return true if will expire. false otherwise.
*/
public boolean willExpireInDeltaTime(long deltaMs) {
return oAuthTokenManager.willAccessTokenExpire(deltaMs);
}
/**
* Refresh tokens asynchronously. Only one request will be in flux irrespective of number of calls
* to this method. Rest are ignored.
*/
public void refreshTokenAsync() {
if (!asyncRetrievalInProgress.get()) {
// proceed only if async Retrieval not in progress.
synchronized (this) {
if (!asyncRetrievalInProgress.getAndSet(true)) {
// retrieve token async via thread and update value of accessToken in oauthTokenManager
executorService.execute(new RefreshTokenRunnable());
}
}
}
}
@SuppressWarnings("VisibleForTesting")
void startSubscription(@Nullable DynamicExperiments dynamicExperiments) {
if (dynamicExperiments == null) {
return;
}
experimentSubscription =
dynamicExperiments
.observe(OAuthExperimentName.AUTH_ISSUE_OAUTH_TOKENS)
.subscribe(
experimentUpdate -> oauthEnabled = experimentUpdate,
error -> Lumber.i(TAG, "Unable to get experiment update"));
dynamicExperiments
.observe(OAuthExperimentName.OAUTH_FORCE_LOGOUT_STATUS_CODE_KILL_SWITCH)
.subscribe(
experimentUpdate -> oAuthLogoutStatusCodeKillSwitch = experimentUpdate,
error ->
Lumber.i(TAG, "Unable to get OAUTH_FORCE_LOGOUT_STATUS_CODE_KILL_SWITCH update"));
}
/**
* Runnable Class to refresh token asynchronously.
*/
class RefreshTokenRunnable implements Runnable {
public void run() {
// fetch token
// oAuthTokenManager.setOAuthToken(oAuthTokens); // what is the expected behavior if this
// results in UNAUTHORIZED.
asyncRetrievalInProgress.set(false);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment