Skip to content

Commit

Permalink
Merge 273f8bd into a15d6f0
Browse files Browse the repository at this point in the history
  • Loading branch information
Anna Kocheshkova authored Feb 21, 2019
2 parents a15d6f0 + 273f8bd commit e136b77
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.microsoft.appcenter.identity.storage;

import android.content.Context;
import android.support.test.InstrumentationRegistry;

import com.microsoft.appcenter.utils.UUIDUtils;
import com.microsoft.appcenter.utils.storage.SharedPreferencesManager;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class TokenStorageTest {

@Test
public void testPreferenceTokenStorage() {

/* Mock token. */
Context context = InstrumentationRegistry.getTargetContext();
SharedPreferencesManager.initialize(context);
AuthTokenStorage tokenStorage = TokenStorageFactory.getTokenStorage(context);
String mockToken = UUIDUtils.randomUUID().toString();
String mockAccountId = UUIDUtils.randomUUID().toString();

/* Save the token into storage. */
tokenStorage.saveToken(mockToken, mockAccountId);

/* Assert that storage returns the same token.*/
assertEquals(mockToken, tokenStorage.getToken());

/* Remove the token from storage. */
tokenStorage.removeToken();

/* Assert that there's no token in storage. */
assertNull(tokenStorage.getToken());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
import com.microsoft.appcenter.http.HttpUtils;
import com.microsoft.appcenter.http.ServiceCall;
import com.microsoft.appcenter.http.ServiceCallback;
import com.microsoft.appcenter.identity.storage.AuthTokenStorage;
import com.microsoft.appcenter.identity.storage.TokenStorageFactory;
import com.microsoft.appcenter.utils.AppCenterLog;
import com.microsoft.appcenter.utils.HandlerUtils;
import com.microsoft.appcenter.utils.async.AppCenterFuture;
import com.microsoft.appcenter.utils.context.AuthTokenContext;
import com.microsoft.appcenter.utils.storage.FileManager;
import com.microsoft.appcenter.utils.storage.SharedPreferencesManager;
import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.exception.MsalException;
Expand Down Expand Up @@ -101,6 +103,11 @@ public class Identity extends AbstractAppCenterService {
*/
private boolean mLoginDelayed;

/**
* Instance of {@link AuthTokenStorage} to store token information.
*/
private AuthTokenStorage mTokenStorage;

/**
* Get shared instance.
*
Expand Down Expand Up @@ -152,6 +159,7 @@ public static void login() {
public synchronized void onStarted(@NonNull Context context, @NonNull Channel channel, String appSecret, String transmissionTargetToken, boolean startedFromApp) {
mContext = context;
mAppSecret = appSecret;
mTokenStorage = TokenStorageFactory.getTokenStorage(context);
super.onStarted(context, channel, appSecret, transmissionTargetToken, startedFromApp);
}

Expand All @@ -167,6 +175,9 @@ protected synchronized void applyEnabledState(boolean enabled) {
/* Load cached configuration in case APIs are called early. */
loadConfigurationFromCache();

/* Load the last stored token and cache it into token context. */
mTokenStorage.cacheToken();

/* Download the latest configuration in background. */
downloadConfiguration();
} else {
Expand All @@ -178,6 +189,7 @@ protected synchronized void applyEnabledState(boolean enabled) {
mIdentityScope = null;
mLoginDelayed = false;
clearCache();
mTokenStorage.removeToken();
}
}

Expand Down Expand Up @@ -381,7 +393,8 @@ public void onSuccess(final IAuthenticationResult authenticationResult) {
getInstance().post(new Runnable() {
@Override
public void run() {
AuthTokenContext.getInstance().setAuthToken(authenticationResult.getIdToken());
IAccount account = authenticationResult.getAccount();
mTokenStorage.saveToken(authenticationResult.getIdToken(), account.getHomeAccountIdentifier().getIdentifier());
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.microsoft.appcenter.identity.storage;

/**
* Interface for storage that works with token.
*/
public interface AuthTokenStorage {

/**
* Stores token value along with the corresponding account id.
*
* @param token auth token.
* @param homeAccountId unique identifier of user.
*/
void saveToken(String token, String homeAccountId);

/**
* Retrieves token value.
*
* @return auth token.
*/
String getToken();

/**
* Removes token value.
*/
void removeToken();

/**
* Gets token and the last account id from storage and caches it.
*/
void cacheToken();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.microsoft.appcenter.identity.storage;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;

import com.microsoft.appcenter.utils.context.AuthTokenContext;
import com.microsoft.appcenter.utils.crypto.CryptoUtils;
import com.microsoft.appcenter.utils.storage.SharedPreferencesManager;

/**
* Storage for tokens that uses {@link SharedPreferencesManager}. Handles saving and encryption.
*/
public class PreferenceTokenStorage implements AuthTokenStorage {

/**
* {@link Context} instance.
*/
private final Context mContext;

/**
* Default constructor.
*
* @param context {@link Context} instance.
*/
PreferenceTokenStorage(@NonNull Context context) {
mContext = context;
}

/**
* Used for authentication requests, string field for auth token.
*/
@VisibleForTesting
static final String PREFERENCE_KEY_AUTH_TOKEN = "AppCenter.auth_token";

/**
* Used for distinguishing users, string field for home account id.
*/
@VisibleForTesting
static final String PREFERENCE_KEY_HOME_ACCOUNT_ID = "AppCenter.account_id";

@Override
public void saveToken(String token, String homeAccountId) {
AuthTokenContext.getInstance().setAuthToken(token, homeAccountId);
String encryptedToken = CryptoUtils.getInstance(mContext).encrypt(token);
SharedPreferencesManager.putString(PREFERENCE_KEY_AUTH_TOKEN, encryptedToken);
SharedPreferencesManager.putString(PREFERENCE_KEY_HOME_ACCOUNT_ID, homeAccountId);
}

@Override
public String getToken() {
String encryptedToken = SharedPreferencesManager.getString(PREFERENCE_KEY_AUTH_TOKEN, null);
if (encryptedToken == null || encryptedToken.length() == 0) {
return null;
}
CryptoUtils.DecryptedData decryptedData = CryptoUtils.getInstance(mContext).decrypt(encryptedToken, false);
return decryptedData.getDecryptedData();
}

/**
* Retrieves unique user id.
*
* @return unique user id.
*/
private String getHomeAccountId() {
return SharedPreferencesManager.getString(PREFERENCE_KEY_HOME_ACCOUNT_ID, null);
}

@Override
public void cacheToken() {
String tokenFromStorage = getToken();
String accountId = getHomeAccountId();

/* We need to update Token context here. */
AuthTokenContext.getInstance().setAuthToken(tokenFromStorage, accountId);
}

@Override
public void removeToken() {
SharedPreferencesManager.remove(PREFERENCE_KEY_AUTH_TOKEN);
SharedPreferencesManager.remove(PREFERENCE_KEY_HOME_ACCOUNT_ID);
AuthTokenContext.getInstance().clearToken();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.microsoft.appcenter.identity.storage;

import android.content.Context;

/**
* Factory class to produce instance of {@link AuthTokenStorage}.
*/
public class TokenStorageFactory {

/**
* Instance of {@link AuthTokenStorage}.
*/
private static AuthTokenStorage sTokenStorageInstance;

/**
* Retrieves current implementation of {@link AuthTokenStorage}.
*
* @param context application context.
* @return instance of {@link AuthTokenStorage}.
*/
public static AuthTokenStorage getTokenStorage(Context context) {
if (sTokenStorageInstance == null) {
sTokenStorageInstance = new PreferenceTokenStorage(context);
}
return sTokenStorageInstance;
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
package com.microsoft.appcenter.identity;

import android.content.Context;
import android.os.SystemClock;
import android.util.Log;

import com.microsoft.appcenter.AppCenter;
import com.microsoft.appcenter.AppCenterHandler;
import com.microsoft.appcenter.http.HttpUtils;
import com.microsoft.appcenter.identity.storage.PreferenceTokenStorage;
import com.microsoft.appcenter.identity.storage.TokenStorageFactory;
import com.microsoft.appcenter.utils.AppCenterLog;
import com.microsoft.appcenter.utils.HandlerUtils;
import com.microsoft.appcenter.utils.PrefStorageConstants;
import com.microsoft.appcenter.utils.async.AppCenterFuture;
import com.microsoft.appcenter.utils.context.AuthTokenContext;
import com.microsoft.appcenter.utils.storage.FileManager;
import com.microsoft.appcenter.utils.storage.SharedPreferencesManager;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAccountIdentifier;
import com.microsoft.identity.client.IAuthenticationResult;

import org.junit.Before;
import org.junit.Rule;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;

Expand All @@ -40,7 +48,8 @@
AppCenter.class,
HandlerUtils.class,
HttpUtils.class,
AuthTokenContext.class
AuthTokenContext.class,
TokenStorageFactory.class
})
abstract public class AbstractIdentityTest {

Expand All @@ -58,8 +67,11 @@ abstract public class AbstractIdentityTest {
@Mock
protected AuthTokenContext mAuthTokenContext;

@Mock
protected PreferenceTokenStorage mPreferenceTokenStorage;

@Before
public void setUp() {
public void setUp() throws Exception {
Identity.unsetInstance();
mockStatic(SystemClock.class);
mockStatic(AppCenterLog.class);
Expand Down Expand Up @@ -107,5 +119,22 @@ public Object answer(InvocationOnMock invocation) {
/* Mock token context. */
mockStatic(AuthTokenContext.class);
when(AuthTokenContext.getInstance()).thenReturn(mAuthTokenContext);

/* Workaround for class definition coverage. Note: we can't make it final as it would prevent mocking. */
new TokenStorageFactory();
PowerMockito.mockStatic(TokenStorageFactory.class);
PowerMockito.when(TokenStorageFactory.getTokenStorage(any(Context.class))).thenReturn(mPreferenceTokenStorage);
}

IAuthenticationResult mockAuthResult(String mockIdToken, String mockAccountId) {
IAuthenticationResult mockResult = Mockito.mock(IAuthenticationResult.class);
when(mockResult.getAccessToken()).thenReturn("token");
when(mockResult.getIdToken()).thenReturn(mockIdToken);
IAccount mockAccount = Mockito.mock(IAccount.class);
IAccountIdentifier mockIdentifier = Mockito.mock(IAccountIdentifier.class);
when(mockIdentifier.getIdentifier()).thenReturn(mockAccountId);
when(mockAccount.getHomeAccountIdentifier()).thenReturn(mockIdentifier);
when(mockResult.getAccount()).thenReturn(mockAccount);
return mockResult;
}
}
Loading

0 comments on commit e136b77

Please sign in to comment.