Skip to content

Commit

Permalink
Merge pull request #31 from MSOpenTech/refreshTokenCacheFix
Browse files Browse the repository at this point in the history
token cache issue fix
  • Loading branch information
omercs committed Jan 21, 2014
2 parents 4251d94 + 6c27bdb commit 77f65ab
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 58 deletions.
2 changes: 1 addition & 1 deletion adal/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.adal"
android:versionCode="1"
android:versionName="1.0" >
android:versionName="0.1.1" >

<uses-sdk
android:minSdkVersion="10"
Expand Down
39 changes: 34 additions & 5 deletions adal/src/com/microsoft/adal/AuthenticationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -749,16 +749,20 @@ private class RefreshItem {

String mKey;

public RefreshItem(String keyInCache, String refreshTokenValue) {
boolean mMultiResource;

public RefreshItem(String keyInCache, String refreshTokenValue, boolean multiResource) {
this.mKey = keyInCache;
this.mRefreshToken = refreshTokenValue;
this.mMultiResource = multiResource;
}
}

private RefreshItem getRefreshToken(final AuthenticationRequest request) {
RefreshItem refreshItem = null;

if (mTokenCacheStore != null) {
boolean multiResource = false;
// target refreshToken for this resource first. CacheKey will
// include the resourceId in the cachekey
Logger.v(TAG, "Looking for regular refresh token");
Expand All @@ -771,11 +775,12 @@ private RefreshItem getRefreshToken(final AuthenticationRequest request) {
Logger.v(TAG, "Looking for Multi Resource Refresh token");
keyUsed = CacheKey.createMultiResourceRefreshTokenKey(request);
item = mTokenCacheStore.getItem(keyUsed);
multiResource = true;
}

if (item != null && !StringExtensions.IsNullOrBlank(item.getRefreshToken())) {
Logger.v(TAG, "Refresh token is available. Key used:" + keyUsed);
refreshItem = new RefreshItem(keyUsed, item.getRefreshToken());
refreshItem = new RefreshItem(keyUsed, item.getRefreshToken(), multiResource);
}
}

Expand All @@ -799,6 +804,31 @@ private void setItemToCache(final AuthenticationRequest request, AuthenticationR
}
}

private void setRefreshItemToCache(final RefreshItem refreshItem,
final AuthenticationRequest request, AuthenticationResult result)
throws AuthenticationException {
if (mTokenCacheStore != null) {
// Use same key to store refreshed result. This key may belong to normal token or MRRT token.
Logger.v(TAG, "Setting refresh item to cache for key:" + refreshItem.mKey);
mTokenCacheStore.setItem(refreshItem.mKey, new TokenCacheItem(request, result,
refreshItem.mMultiResource));

if(refreshItem.mMultiResource){
// update normal token result as well to avoid refreshing again for next request
mTokenCacheStore.setItem(CacheKey.createCacheKey(request), new TokenCacheItem(request,
result, false));
}else{
// update MRRT token as well if result is MRRT
if (result.getIsMultiResourceRefreshToken()) {
Logger.v(TAG, "Setting Multi Resource Refresh token to cache");
mTokenCacheStore.setItem(CacheKey.createMultiResourceRefreshTokenKey(request),
new TokenCacheItem(request, result, true));
}
}

}
}

private void removeItemFromCache(final RefreshItem refreshItem) throws AuthenticationException {
if (mTokenCacheStore != null) {
Logger.v(TAG, "Remove refresh item from cache:" + refreshItem.mKey);
Expand Down Expand Up @@ -867,8 +897,7 @@ public void onSuccess(AuthenticationResult result) {
TAG,
"Cache is used. It will set item to cache"
+ request.getLogInfo());
setItemToCache(request, result);

setRefreshItemToCache(refreshItem, request, result);
// return result obj which has error code and
// error description that is returned from
// server response
Expand Down Expand Up @@ -1043,7 +1072,7 @@ private void refreshTokenWithoutCache(final String refreshToken, String clientId
// It is not using cache and refresh is not expected to show
// authentication activity.
request.setPrompt(PromptBehavior.Never);
final RefreshItem refreshItem = new RefreshItem("", refreshToken);
final RefreshItem refreshItem = new RefreshItem("", refreshToken, false);

if (mValidateAuthority) {
Logger.v(TAG, "Validating authority");
Expand Down
12 changes: 5 additions & 7 deletions adal/src/com/microsoft/adal/Oauth2.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ public String getCodeRequestUrl() throws UnsupportedEncodingException {
AuthenticationConstants.AAD.ADAL_ID_VERSION, URLEncoder.encode(
AuthenticationContext.getVersionName(),
AuthenticationConstants.ENCODING_UTF8));
requestUrl = String.format("%s&%s=%s", requestUrl, AuthenticationConstants.AAD.ADAL_ID_OS_VER,
URLEncoder
.encode("" + Build.VERSION.SDK_INT, AuthenticationConstants.ENCODING_UTF8));
requestUrl = String.format("%s&%s=%s", requestUrl,
AuthenticationConstants.AAD.ADAL_ID_OS_VER, URLEncoder.encode(""
+ Build.VERSION.SDK_INT, AuthenticationConstants.ENCODING_UTF8));
requestUrl = String.format("%s&%s=%s", requestUrl, AuthenticationConstants.AAD.ADAL_ID_DM,
URLEncoder.encode("" + android.os.Build.MODEL,
AuthenticationConstants.ENCODING_UTF8));

// Setting prompt behavior to always will skip the cookies for webview.
// It is added to authorization url.
if (mRequest.getPrompt() == PromptBehavior.Always) {
Expand All @@ -97,7 +97,7 @@ public String getCodeRequestUrl() throws UnsupportedEncodingException {
AuthenticationConstants.AAD.QUERY_PROMPT_VALUE,
AuthenticationConstants.ENCODING_UTF8));
}

if (!StringExtensions.IsNullOrBlank(mRequest.getExtraQueryParamsAuthentication())) {
String params = mRequest.getExtraQueryParamsAuthentication();
if (!params.startsWith("&")) {
Expand Down Expand Up @@ -313,8 +313,6 @@ public void refreshToken(String refreshToken,
return;
}

Logger.v(TAG, "Refresh token request message:" + requestMessage);

postMessage(requestMessage, authenticationCallback);
}

Expand Down
65 changes: 50 additions & 15 deletions samples/testapp/src/com/microsoft/adal/testapp/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.UUID;

import javax.crypto.NoSuchPaddingException;
Expand Down Expand Up @@ -41,6 +44,7 @@
import com.microsoft.adal.AuthenticationException;
import com.microsoft.adal.AuthenticationResult;
import com.microsoft.adal.CacheKey;
import com.microsoft.adal.DefaultTokenCacheStore;
import com.microsoft.adal.ITokenCacheStore;
import com.microsoft.adal.Logger;
import com.microsoft.adal.Logger.ILogger;
Expand Down Expand Up @@ -101,16 +105,16 @@ public class MainActivity extends Activity {
public Handler getTestAppHandler() {
return handler;
}

class AdalCallback implements AuthenticationCallback<AuthenticationResult> {


private UUID mId;
public AdalCallback(){

public AdalCallback() {
mId = UUID.randomUUID();
}

@Override
public void onError(Exception exc) {
Log.d(TAG, "Callback returned error");
Expand Down Expand Up @@ -269,7 +273,8 @@ private void getTokenByRefreshToken() {
clientId = CLIENT_ID;
}
mContext.setRequestCorrelationId(mRequestCorrelationId);
mContext.acquireTokenByRefreshToken(mResult.getRefreshToken(), clientId, new AdalCallback());
mContext.acquireTokenByRefreshToken(mResult.getRefreshToken(), clientId,
new AdalCallback());
} else {
textViewStatus.setText(FAILED);
}
Expand Down Expand Up @@ -306,9 +311,9 @@ private void getToken() {
String redirect = mRedirect.getText().toString();
mResult = null;
mContext.setRequestCorrelationId(mRequestCorrelationId);
mContext.acquireToken(MainActivity.this, resource, clientId, redirect, userid, prompt, mExtraQueryParam,
new AdalCallback());
}
mContext.acquireToken(MainActivity.this, resource, clientId, redirect, userid, prompt,
mExtraQueryParam, new AdalCallback());
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Expand Down Expand Up @@ -352,23 +357,53 @@ private void resetToken() {
*/
private void setTokenExpired() {
Log.d(TAG, "Setting item to expire...");

Calendar calendar = new GregorianCalendar();
calendar.add(Calendar.MINUTE, -30);
Date date = calendar.getTime();
ITokenCacheStore cache = mContext.getCache();
String cacheKey = CacheKey.createCacheKey(mAuthority.getText().toString(), mResource
.getText().toString(), mClientId.getText().toString(), false, mUserid.getText()
.toString());
Log.d(TAG, "CacheKey:" + cacheKey);
TokenCacheItem item = cache.getItem(cacheKey);
String key = CacheKey.createCacheKey(mAuthority.getText().toString(), mResource.getText()
.toString(), mClientId.getText().toString(), false, mUserid.getText().toString());
TokenCacheItem item = cache.getItem(key);
setTime(cache, date, key, item);

key = CacheKey.createCacheKey(mAuthority.getText().toString(), mResource.getText()
.toString(), mClientId.getText().toString(), true, mUserid.getText().toString());
item = cache.getItem(key);
setTime(cache, date, key, item);
}

private void setTime(ITokenCacheStore cache, Date date, String key, TokenCacheItem item) {
if (item != null) {
Calendar calendar = new GregorianCalendar();
calendar.add(Calendar.MINUTE, -30);
item.setExpiresOn(calendar.getTime());
cache.setItem(cacheKey, item);
Log.d(TAG, "Item is set to expire");

cache.setItem(key, item);
Log.d(TAG, "Item is set to expire for key:" + key);
} else {
Log.d(TAG, "item is null: setTokenExpired");
}
}

/**
* set all expired
*/
public ArrayList<TokenCacheItem> getTokens() {
Log.d(TAG, "Setting item to expire...");
ArrayList<TokenCacheItem> items = new ArrayList<TokenCacheItem>();
DefaultTokenCacheStore cache = (DefaultTokenCacheStore)mContext.getCache();
Iterator<TokenCacheItem> iterator = cache.getAll();
while (iterator.hasNext()) {
TokenCacheItem item = iterator.next();
if (item != null) {
items.add(item);
}
}

return items;
}

/**
* send token in the header with async task
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,19 +593,20 @@ public void testAcquireTokenByRefreshTokenPositive() throws IllegalArgumentExcep
setConnectionAvailable(context, true);
final MockActivity testActivity = new MockActivity();
final CountDownLatch signal = new CountDownLatch(1);
String expectedAccessToken = "TokenFortestAcquireToken" + UUID.randomUUID().toString();
String id = UUID.randomUUID().toString();
String expectedAccessToken = "accessToken" + id;
String expectedClientId = "client" + UUID.randomUUID().toString();
String exptedResource = "resource" + UUID.randomUUID().toString();
testActivity.mSignal = signal;
MockAuthenticationCallback callback = new MockAuthenticationCallback(signal);

MockWebRequestHandler mockWebRequest = setMockWebRequest(context, expectedAccessToken);
MockWebRequestHandler mockWebRequest = setMockWebRequest(context, id);

context.acquireTokenByRefreshToken("refreshTokenSending", expectedClientId, callback);

// Verify that new refresh token is matching to mock response
assertEquals("Same token", expectedAccessToken, callback.mResult.getAccessToken());
assertEquals("Same refresh token", "refreshToken=", callback.mResult.getRefreshToken());
assertEquals("Same refresh token", "refreshToken" + id, callback.mResult.getRefreshToken());
assertTrue("Content has client in the message", mockWebRequest.getRequestContent()
.contains(expectedClientId));
assertFalse("Content does not have resource in the message", mockWebRequest
Expand All @@ -616,19 +617,20 @@ public void testAcquireTokenByRefreshTokenPositive() throws IllegalArgumentExcep

// Verify that new refresh token is matching to mock response
assertEquals("Same token", expectedAccessToken, callback.mResult.getAccessToken());
assertEquals("Same refresh token", "refreshToken=", callback.mResult.getRefreshToken());
assertEquals("Same refresh token", "refreshToken" + id, callback.mResult.getRefreshToken());
assertTrue("Content has client in the message", mockWebRequest.getRequestContent()
.contains(expectedClientId));
assertTrue("Content has resource in the message", mockWebRequest.getRequestContent()
.contains(exptedResource));
}

private MockWebRequestHandler setMockWebRequest(final AuthenticationContext context,
String expectedAccessToken) throws NoSuchFieldException, IllegalAccessException {
private MockWebRequestHandler setMockWebRequest(final AuthenticationContext context, String id)
throws NoSuchFieldException, IllegalAccessException {
MockWebRequestHandler mockWebRequest = new MockWebRequestHandler();
String json = "{\"access_token\":\""
+ expectedAccessToken
+ "\",\"token_type\":\"Bearer\",\"expires_in\":\"29344\",\"expires_on\":\"1368768616\",\"refresh_token\":\"refreshToken=\",\"scope\":\"*\"}";
String json = "{\"access_token\":\"accessToken"
+ id
+ "\",\"token_type\":\"Bearer\",\"expires_in\":\"29344\",\"expires_on\":\"1368768616\",\"refresh_token\":\"refreshToken"
+ id + "\",\"scope\":\"*\"}";
mockWebRequest.setReturnResponse(new HttpWebResponse(200, json.getBytes(Charset
.defaultCharset()), null));
ReflectionUtils.setFieldValue(context, "mWebRequest", mockWebRequest);
Expand Down Expand Up @@ -940,7 +942,8 @@ public void testAcquireTokenMultiResourceToken_UserId() throws InterruptedExcept

FileMockContext mockContext = new FileMockContext(getContext());
String tokenToTest = "accessToken=" + UUID.randomUUID();
String tokenWithRefreshToken = "accessToken=" + UUID.randomUUID();
String tokenId = "id" + UUID.randomUUID().toString().replace("-", "");
String tokenInfo = "accessToken" + tokenId;
String resource = "Resource" + UUID.randomUUID();

ITokenCacheStore mockCache = new DefaultTokenCacheStore(mockContext);
Expand All @@ -949,12 +952,11 @@ public void testAcquireTokenMultiResourceToken_UserId() throws InterruptedExcept
"ClienTid", "userid", false);
addItemToCache(mockCache, "", "refreshTokenMultiResource", VALID_AUTHORITY, resource,
"ClienTid", "userid", true);
addItemToCache(mockCache, "", "refreshTokenMultiResource2", VALID_AUTHORITY,
"dummyResource2", "ClienTid", "userid", true);
// only one MRRT for same user, client, authority
final AuthenticationContext context = new AuthenticationContext(mockContext,
VALID_AUTHORITY, false, mockCache);
setConnectionAvailable(context, true);
MockWebRequestHandler mockWebRequest = setMockWebRequest(context, tokenWithRefreshToken);
MockWebRequestHandler mockWebRequest = setMockWebRequest(context, tokenId);

CountDownLatch signal = new CountDownLatch(1);
MockActivity testActivity = new MockActivity(signal);
Expand All @@ -980,36 +982,33 @@ public void testAcquireTokenMultiResourceToken_UserId() throws InterruptedExcept
signal.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

assertNull("Error is null", callback.mException);
assertEquals("Same token as refresh token result", tokenWithRefreshToken,
assertEquals("Same token as refresh token result", tokenInfo,
callback.mResult.getAccessToken());

// -----------Different resource with same
// userid--------------------------
// Different resource with same userid
signal = new CountDownLatch(1);
testActivity = new MockActivity(signal);
callback = new MockAuthenticationCallback(signal);
context.acquireToken(testActivity, "anotherResource123", "ClienTid", "redirectUri",
"userid", callback);
signal.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

assertEquals("Token is returned from refresh token request", tokenWithRefreshToken,
assertEquals("Token is returned from refresh token request", tokenInfo,
callback.mResult.getAccessToken());
assertFalse("Multiresource is not set in the mocked response",
callback.mResult.getIsMultiResourceRefreshToken());
assertTrue("Request to get token uses broad refresh token", mockWebRequest
.getRequestContent().contains("refreshTokenMultiResource"));
.getRequestContent().contains(tokenId));

// ----------Same call again to use it from
// cache----------------------------
// Same call again to use it from cache
callback.mResult = null;
removeMockWebRequest(context);
context.acquireToken(testActivity, "anotherResource123", "ClienTid", "redirectUri",
"userid", callback);
assertEquals("Same token in response as in cache for same call", tokenWithRefreshToken,
assertEquals("Same token in response as in cache for same call", tokenInfo,
callback.mResult.getAccessToken());

// -----------Empty userid will
// prompt---------------------------------------
// Empty userid will prompt.
// Items are linked to userid. If it is not there, it can't use for
// refresh or access token.
signal = new CountDownLatch(1);
Expand Down
Loading

0 comments on commit 77f65ab

Please sign in to comment.