Android Open Source - azure-activedirectory-library-for-android Authentication Context






From Project

Back to project page azure-activedirectory-library-for-android.

License

The source code is released under:

Apache License

If you think the Android project azure-activedirectory-library-for-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

// Copyright  Microsoft Open Technologies, Inc.
//// ww  w.  j a  v  a2s  . c om
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
// ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
// PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache License, Version 2.0 for the specific language
// governing permissions and limitations under the License.

package com.microsoft.aad.adal;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.crypto.NoSuchPaddingException;

import com.microsoft.aad.adal.AuthenticationRequest.UserIdentifierType;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.util.SparseArray;

/**
 * ADAL context to get access token, refresh token, and lookup from cache.
 */
public class AuthenticationContext {

    private static final int EXCLUDE_INDEX = 8;

    private static final String TAG = "AuthenticationContext";

    private Context mContext;

    private String mAuthority;

    private boolean mValidateAuthority;

    private boolean mAuthorityValidated = false;

    private ITokenCacheStore mTokenCacheStore;

    private static final ReentrantReadWriteLock RWL = new ReentrantReadWriteLock();

    private static final Lock READ_LOCK = RWL.readLock();

    private static final Lock WRITE_LOCK = RWL.writeLock();

    /**
     * Delegate map is needed to handle activity recreate without asking
     * developer to handle context instance for config changes.
     */
    static SparseArray<AuthenticationRequestState> mDelegateMap = new SparseArray<AuthenticationRequestState>();

    /**
     * Last set authorization callback.
     */
    private AuthenticationCallback<AuthenticationResult> mAuthorizationCallback;

    /**
     * Instance validation related calls are serviced inside Discovery as a
     * module.
     */
    private IDiscovery mDiscovery = new Discovery();

    /**
     * Web request handler interface to test behaviors.
     */
    private IWebRequestHandler mWebRequest = new WebRequestHandler();

    /**
     * JWS message builder interface to test behaviors.
     */
    private IJWSBuilder mJWSBuilder;

    /**
     * Connection service interface to test different behaviors.
     */
    private IConnectionService mConnectionService = null;

    private IBrokerProxy mBrokerProxy = null;

    /**
     * CorrelationId set by user or generated by ADAL.
     */
    private UUID mRequestCorrelationId = null;

    /**
     * Constructs context to use with known authority to get the token. It uses
     * default cache that stores encrypted tokens.
     * 
     * @param appContext It needs to have handle to the {@link Context} to use
     *            the SharedPreferences as a Default cache storage. It does not
     *            need to be activity.
     * @param authority Authority url to send code and token requests
     * @param validateAuthority validate authority before sending token request
     * @throws NoSuchPaddingException Algorithm padding does not exist in the
     *             device
     * @throws NoSuchAlgorithmException Encryption Algorithm does not exist in
     *             the device. Please see the log record for details.
     */
    public AuthenticationContext(Context appContext, String authority, boolean validateAuthority)
            throws NoSuchAlgorithmException, NoSuchPaddingException {
        // Fixes are required for SDK 16-18
        // The fixes need to be applied before any use of Java Cryptography
        // Architecture primitives. Default cache uses encryption
        PRNGFixes.apply();
        initialize(appContext, authority, new DefaultTokenCacheStore(appContext),
                validateAuthority, true);
    }

    /**
     * Constructs context to use with known authority to get the token. It uses
     * provided cache.
     * 
     * @param appContext {@link Context}
     * @param authority Authority Url
     * @param validateAuthority true/false for validation
     * @param tokenCacheStore Set to null if you don't want cache.
     */
    public AuthenticationContext(Context appContext, String authority, boolean validateAuthority,
            ITokenCacheStore tokenCacheStore) {
        initialize(appContext, authority, tokenCacheStore, validateAuthority, false);
    }

    /**
     * It will verify the authority and use the given cache. If cache is null,
     * it will not use cache.
     * 
     * @param appContext {@link Context}
     * @param authority Authority Url
     * @param tokenCacheStore Cache {@link ITokenCacheStore} used to store
     *            tokens. Set to null if you don't want cache.
     */
    public AuthenticationContext(Context appContext, String authority,
            ITokenCacheStore tokenCacheStore) {
        initialize(appContext, authority, tokenCacheStore, true, false);
    }

    private void initialize(Context appContext, String authority, ITokenCacheStore tokenCacheStore,
            boolean validateAuthority, boolean defaultCache) {
        if (appContext == null) {
            throw new IllegalArgumentException("appContext");
        }
        if (authority == null) {
            throw new IllegalArgumentException("authority");
        }
        mBrokerProxy = new BrokerProxy(appContext);
        if (!defaultCache && !mBrokerProxy.canUseLocalCache()) {
            throw new UnsupportedOperationException("Local cache is not supported for broker usage");
        }
        mContext = appContext;
        mConnectionService = new DefaultConnectionService(mContext);
        checkInternetPermission();
        mAuthority = extractAuthority(authority);
        mValidateAuthority = validateAuthority;
        mTokenCacheStore = tokenCacheStore;
        mJWSBuilder = new JWSBuilder();
    }

    /**
     * Returns referenced cache. You can use default cache, which uses
     * SharedPreferences and handles synchronization by itself.
     * 
     * @return ITokenCacheStore Current cache used
     */
    public ITokenCacheStore getCache() {
        if (mBrokerProxy.canSwitchToBroker()) {
            // return cache implementation related to broker so that app can
            // clear tokens for related accounts
            return new ITokenCacheStore() {

                /**
                 * default serial #
                 */
                private static final long serialVersionUID = 1L;

                @Override
                public void setItem(String key, TokenCacheItem item) {
                    throw new UnsupportedOperationException(
                            "Broker cache does not support direct setItem operation");
                }

                @Override
                public void removeItem(String key) {
                    throw new UnsupportedOperationException(
                            "Broker cache does not support direct removeItem operation");
                }

                @Override
                public void removeAll() {
                    mBrokerProxy.removeAccounts();
                }

                @Override
                public TokenCacheItem getItem(String key) {
                    throw new UnsupportedOperationException(
                            "Broker cache does not support direct getItem operation");
                }

                @Override
                public boolean contains(String key) {
                    throw new UnsupportedOperationException(
                            "Broker cache does not support contains operation");
                }
            };
        }
        return mTokenCacheStore;
    }

    /**
     * Gets authority that is used for this object of AuthenticationContext.
     * 
     * @return Authority
     */
    public String getAuthority() {
        return mAuthority;
    }

    /**
     * @return True when authority is valid
     */
    public boolean getValidateAuthority() {
        return mValidateAuthority;
    }

    /**
     * Gets username for current broker user.
     * 
     * @return Username
     */
    public String getBrokerUser() {
        if (mBrokerProxy != null) {
            return mBrokerProxy.getCurrentUser();
        }

        return null;
    }

    /**
     * Get expected redirect Uri for your app to use in broker. You need to
     * register this redirectUri in order to get token from Broker.
     * 
     * @return RedirectUri string to use for broker requests.
     */
    public String getRedirectUriForBroker() {
        PackageHelper helper = new PackageHelper(mContext);
        String packageName = mContext.getPackageName();

        // First available signature. Applications can be signed with multiple
        // signatures.
        String signatureDigest = helper.getCurrentSignatureForPackage(packageName);
        String redirectUri = PackageHelper.getBrokerRedirectUrl(packageName, signatureDigest);
        Logger.v(TAG, "Broker redirectUri:" + redirectUri + " packagename:" + packageName
                + " signatureDigest:" + signatureDigest);
        return redirectUri;
    }

    /**
     * acquire Token will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, it will remove
     * this refresh token from cache and start authentication.
     * 
     * @param activity required to launch authentication activity.
     * @param resource required resource identifier.
     * @param clientId required client identifier
     * @param redirectUri Optional. It will use package name info if not
     *            provided.
     * @param loginHint Optional login hint
     * @param callback required
     */
    public void acquireToken(Activity activity, String resource, String clientId,
            String redirectUri, String loginHint,
            AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, PromptBehavior.Auto,
                callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, loginHint, PromptBehavior.Auto, null,
                getRequestCorrelationId());
        request.setUserIdentifierType(UserIdentifierType.LoginHint);
        acquireTokenLocal(wrapActivity(activity), false, request, callback);
    }

    /**
     * acquire Token will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use the refresh
     * token if available. If it fails to get token with refresh token, it will
     * remove this refresh token from cache and fall back on the UI.
     * 
     * @param activity Calling activity
     * @param resource required resource identifier.
     * @param clientId required client identifier
     * @param redirectUri Optional. It will use packagename and provided suffix
     *            for this.
     * @param loginHint Optional. This parameter will be used to pre-populate
     *            the username field in the authentication form. Please note
     *            that the end user can still edit the username field and
     *            authenticate as a different user. This parameter can be null.
     * @param extraQueryParameters Optional. This parameter will be appended as
     *            is to the query string in the HTTP authentication request to
     *            the authority. The parameter can be null.
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
            String redirectUri, String loginHint, String extraQueryParameters,
            AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, PromptBehavior.Auto,
                callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, loginHint, PromptBehavior.Auto, extraQueryParameters,
                getRequestCorrelationId());
        request.setUserIdentifierType(UserIdentifierType.LoginHint);
        acquireTokenLocal(wrapActivity(activity), false, request, callback);
    }

    /**
     * acquire Token will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, behavior will
     * depend on options. If {@link PromptBehavior} is AUTO, it will remove this
     * refresh token from cache and fall back on the UI. Default is AUTO. if
     * {@link PromptBehavior} is Always, it will display prompt screen.
     * 
     * @param activity Calling activity
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param redirectUri Optional. It will use packagename and provided suffix
     *            for this.
     * @param prompt Optional. {@link PromptBehavior} added as query parameter
     *            to authorization url
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
            String redirectUri, PromptBehavior prompt,
            AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, prompt, callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, null, prompt, null, getRequestCorrelationId());

        acquireTokenLocal(wrapActivity(activity), false, request, callback);
    }

    /**
     * acquire Token will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, behavior will
     * depend on options. If promptbehavior is AUTO, it will remove this refresh
     * token from cache and fall back on the UI if activitycontext is not null.
     * Default is AUTO.
     * 
     * @param activity Calling activity
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param redirectUri Optional. It will use packagename and provided suffix
     *            for this.
     * @param prompt Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
            String redirectUri, PromptBehavior prompt, String extraQueryParameters,
            AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, prompt, callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, null, prompt, extraQueryParameters,
                getRequestCorrelationId());
        
        acquireTokenLocal(wrapActivity(activity), false, request, callback);
    }

    /**
     * acquire Token will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, behavior will
     * depend on options. If promptbehavior is AUTO, it will remove this refresh
     * token from cache and fall back on the UI if activitycontext is not null.
     * Default is AUTO.
     * 
     * @param activity Calling activity
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param redirectUri Optional. It will use packagename and provided suffix
     *            for this.
     * @param loginHint Optional. It is used for cache and as a loginhint at
     *            authentication.
     * @param prompt Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
            String redirectUri, String loginHint, PromptBehavior prompt,
            String extraQueryParameters, AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, prompt, callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                getRequestCorrelationId());
        request.setUserIdentifierType(UserIdentifierType.LoginHint);
        acquireTokenLocal(wrapActivity(activity), false, request, callback);
    }

    /**
     * It will start interactive flow if needed. It checks the cache to return
     * existing result if not expired. It tries to use refresh token if
     * available. If it fails to get token with refresh token, behavior will
     * depend on options. If promptbehavior is AUTO, it will remove this refresh
     * token from cache and fall back on the UI. Default is AUTO.
     * 
     * @param fragment It accepts both type of fragments.
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param redirectUri Optional. It will use packagename and provided suffix
     *            for this.
     * @param loginHint Optional. It is used for cache and as a loginhint at
     *            authentication.
     * @param prompt Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     */
    public void acquireToken(IWindowComponent fragment, String resource, String clientId,
            String redirectUri, String loginHint, PromptBehavior prompt,
            String extraQueryParameters, AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, prompt, callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                getRequestCorrelationId());
        request.setUserIdentifierType(UserIdentifierType.LoginHint);
        acquireTokenLocal(fragment, false, request, callback);
    }

    /**
     * This uses new dialog based prompt. It will create a handler to run the
     * dialog related code. It will start interactive flow if needed. It checks
     * the cache to return existing result if not expired. It tries to use
     * refresh token if available. If it fails to get token with refresh token,
     * behavior will depend on options. If promptbehavior is AUTO, it will
     * remove this refresh token from cache and fall back on the UI. Default is
     * AUTO.
     * 
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param redirectUri Optional. It will use packagename and provided suffix
     *            for this.
     * @param loginHint Optional. It is used for cache and as a loginhint at
     *            authentication.
     * @param prompt Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     */
    public void acquireToken(String resource, String clientId, String redirectUri,
            String loginHint, PromptBehavior prompt, String extraQueryParameters,
            AuthenticationCallback<AuthenticationResult> callback) {

        redirectUri = checkInputParameters(resource, clientId, redirectUri, prompt, callback);

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                getRequestCorrelationId());
        request.setUserIdentifierType(UserIdentifierType.LoginHint);
        acquireTokenLocal(null, true, request, callback);
    }

    private IWindowComponent wrapActivity(final Activity activity) {
        return new IWindowComponent() {
            Activity refActivity = activity;

            @Override
            public void startActivityForResult(Intent intent, int requestCode) {
                refActivity.startActivityForResult(intent, requestCode);
            }
        };
    }

    private String checkInputParameters(String resource, String clientId, String redirectUri,
            PromptBehavior behavior, AuthenticationCallback<AuthenticationResult> callback) {
        if (mContext == null) {
            throw new AuthenticationException(ADALError.DEVELOPER_CONTEXT_IS_NOT_PROVIDED);
        }

        if (StringExtensions.IsNullOrBlank(resource)) {
            throw new IllegalArgumentException("resource");
        }

        if (StringExtensions.IsNullOrBlank(clientId)) {
            throw new IllegalArgumentException("clientId");
        }

        if (callback == null) {
            throw new IllegalArgumentException("callback");
        }

        if (StringExtensions.IsNullOrBlank(redirectUri)) {
            redirectUri = getRedirectFromPackage();
        }

        return redirectUri;
    }

    /**
     * This is sync function. It will first look at the cache and automatically
     * checks for the token expiration. Additionally, if no suitable access
     * token is found in the cache, but refresh token is available, the function
     * will use the refresh token automatically. This method will not show UI
     * for the user. If prompt is needed, the method will return an exception
     * 
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param userId UserID obtained from
     *            {@link AuthenticationResult #getUserInfo()}
     * @return A {@link Future} object representing the
     *         {@link AuthenticationResult} of the call. It contains Access
     *         Token,the Access Token's expiration time, Refresh token, and
     *         {@link UserInfo}.
     */
    public AuthenticationResult acquireTokenSilentSync(String resource, String clientId,
            String userId) {
        Future<AuthenticationResult> futureResult = acquireTokenSilent(resource, clientId, userId,
                null);
        try {
            return futureResult.get();
        } catch (InterruptedException e) {
            convertExceptionForSync(e);
        } catch (ExecutionException e) {
            convertExceptionForSync(e);
        }

        return null;
    }

    private void convertExceptionForSync(Exception e) {
        // change to unchecked exception
        if (e.getCause() != null) {

            if (e.getCause() instanceof AuthenticationException) {
                throw (AuthenticationException)e.getCause();
            } else if (e.getCause() instanceof IllegalArgumentException) {
                throw (IllegalArgumentException)e.getCause();
            } else {
                throw new AuthenticationException(ADALError.ERROR_SILENT_REQUEST, e.getCause()
                        .getMessage(), e.getCause());
            }
        }

        throw new AuthenticationException(ADALError.ERROR_SILENT_REQUEST, e.getMessage(), e);
    }

    /**
     * The function will first look at the cache and automatically checks for
     * the token expiration. Additionally, if no suitable access token is found
     * in the cache, but refresh token is available, the function will use the
     * refresh token automatically. This method will not show UI for the user.
     * If prompt is needed, the method will return an exception
     * 
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param userId UserId obtained from {@link UserInfo} inside
     *            {@link AuthenticationResult}
     * @param callback required {@link AuthenticationCallback} object for async
     *            call.
     * @return A {@link Future} object representing the
     *         {@link AuthenticationResult} of the call. It contains Access
     *         Token,the Access Token's expiration time, Refresh token, and
     *         {@link UserInfo}.
     */
    public Future<AuthenticationResult> acquireTokenSilent(String resource, String clientId,
            String userId, AuthenticationCallback<AuthenticationResult> callback) {
        if (StringExtensions.IsNullOrBlank(resource)) {
            throw new IllegalArgumentException("resource");
        }
        if (StringExtensions.IsNullOrBlank(clientId)) {
            throw new IllegalArgumentException("clientId");
        }

        final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                clientId, userId, getRequestCorrelationId());
        request.setSilent(true);
        request.setPrompt(PromptBehavior.Auto);
        request.setUserIdentifierType(UserIdentifierType.UniqueId);
        return acquireTokenLocal(null, false, request, callback);
    }

    /**
     * acquire token using refresh token if cache is not used. Otherwise, use
     * acquireToken to let the ADAL handle the cache lookup and refresh token
     * request.
     * 
     * @param refreshToken Required.
     * @param clientId Required.
     * @param callback Required
     */
    public void acquireTokenByRefreshToken(String refreshToken, String clientId,
            AuthenticationCallback<AuthenticationResult> callback) {
        // Authenticator is not supported if user is managing the cache
        refreshTokenWithoutCache(refreshToken, clientId, null, callback);
    }

    /**
     * acquire token using refresh token if cache is not used. Otherwise, use
     * acquireToken to let the ADAL handle the cache lookup and refresh token
     * request.
     * 
     * @param refreshToken Required.
     * @param clientId Required.
     * @param resource Required resource identifier.
     * @param callback Required
     */
    public void acquireTokenByRefreshToken(String refreshToken, String clientId, String resource,
            AuthenticationCallback<AuthenticationResult> callback) {
        // Authenticator is not supported if user is managing the cache
        refreshTokenWithoutCache(refreshToken, clientId, resource, callback);
    }

    /**
     * This method wraps the implementation for onActivityResult at the related
     * Activity class. This method is called at UI thread.
     * 
     * @param requestCode Request code provided at the start of the activity.
     * @param resultCode Result code set from the activity.
     * @param data {@link Intent}
     */
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // This is called at UI thread when Activity sets result back.
        // ResultCode is set back from AuthenticationActivity. RequestCode is
        // set when we start the activity for result.
        if (requestCode == AuthenticationConstants.UIRequest.BROWSER_FLOW) {
            if (data == null) {
                // If data is null, RequestId is unknown. It could not find
                // callback to respond to this request.
                Logger.e(TAG, "onActivityResult BROWSER_FLOW data is null.", "",
                        ADALError.ON_ACTIVITY_RESULT_INTENT_NULL);
            } else {
                Bundle extras = data.getExtras();
                final int requestId = extras.getInt(AuthenticationConstants.Browser.REQUEST_ID);
                final AuthenticationRequestState waitingRequest = getWaitingRequest(requestId);
                if (waitingRequest != null) {
                    Logger.v(TAG, "onActivityResult RequestId:" + requestId);
                } else {
                    Logger.e(TAG, "onActivityResult did not find waiting request for RequestId:"
                            + requestId, "", ADALError.ON_ACTIVITY_RESULT_INTENT_NULL);
                    // There is no matching callback to send error
                    return;
                }

                // Cancel or browser error can use recorded request to figure
                // out original correlationId send with request.
                String correlationInfo = getCorrelationInfoFromWaitingRequest(waitingRequest);
                if (resultCode == AuthenticationConstants.UIResponse.TOKEN_BROKER_RESPONSE) {
                    String accessToken = data
                            .getStringExtra(AuthenticationConstants.Broker.ACCOUNT_ACCESS_TOKEN);
                    String accountName = data
                            .getStringExtra(AuthenticationConstants.Broker.ACCOUNT_NAME);
                    mBrokerProxy.saveAccount(accountName);
                    long expireTime = data.getLongExtra(
                            AuthenticationConstants.Broker.ACCOUNT_EXPIREDATE, 0);
                    Date expire = new Date(expireTime);
                    UserInfo userinfo = UserInfo.getUserInfoFromBrokerResult(data.getExtras());
                    AuthenticationResult brokerResult = new AuthenticationResult(accessToken, null,
                            expire, false, userinfo, "", "");
                    if (brokerResult != null && brokerResult.getAccessToken() != null) {
                        waitingRequest.mDelagete.onSuccess(brokerResult);
                        return;
                    }
                } else if (resultCode == AuthenticationConstants.UIResponse.BROWSER_CODE_CANCEL) {
                    // User cancelled the flow by clicking back button or
                    // activating another activity
                    Logger.v(TAG, "User cancelled the flow RequestId:" + requestId
                            + correlationInfo);
                    waitingRequestOnError(waitingRequest, requestId, new AuthenticationCancelError(
                            "User cancelled the flow RequestId:" + requestId + correlationInfo));
                } else if (resultCode == AuthenticationConstants.UIResponse.BROWSER_CODE_AUTHENTICATION_EXCEPTION) {
                    Serializable authException = extras
                            .getSerializable(AuthenticationConstants.Browser.RESPONSE_AUTHENTICATION_EXCEPTION);
                    if (authException != null && authException instanceof AuthenticationException) {
                        AuthenticationException exception = (AuthenticationException)authException;
                        Logger.w(TAG, "Webview returned exception", exception.getMessage(),
                                ADALError.WEBVIEW_RETURNED_AUTHENTICATION_EXCEPTION);
                        waitingRequestOnError(waitingRequest, requestId, exception);
                    } else {
                        waitingRequestOnError(
                                waitingRequest,
                                requestId,
                                new AuthenticationException(
                                        ADALError.WEBVIEW_RETURNED_INVALID_AUTHENTICATION_EXCEPTION));
                    }
                } else if (resultCode == AuthenticationConstants.UIResponse.BROWSER_CODE_ERROR) {
                    String errCode = extras
                            .getString(AuthenticationConstants.Browser.RESPONSE_ERROR_CODE);
                    String errMessage = extras
                            .getString(AuthenticationConstants.Browser.RESPONSE_ERROR_MESSAGE);
                    Logger.v(TAG, "Error info:" + errCode + " " + errMessage + " for requestId: "
                            + requestId + correlationInfo);
                    waitingRequestOnError(waitingRequest, requestId, new AuthenticationException(
                            ADALError.SERVER_INVALID_REQUEST, errCode + " " + errMessage));
                } else if (resultCode == AuthenticationConstants.UIResponse.BROWSER_CODE_COMPLETE) {
                    final AuthenticationRequest authenticationRequest = (AuthenticationRequest)extras
                            .getSerializable(AuthenticationConstants.Browser.RESPONSE_REQUEST_INFO);
                    final String endingUrl = extras
                            .getString(AuthenticationConstants.Browser.RESPONSE_FINAL_URL);
                    if (endingUrl.isEmpty()) {
                        AuthenticationException e = new AuthenticationException(
                                ADALError.WEBVIEW_RETURNED_EMPTY_REDIRECT_URL,
                                "Webview did not reach the redirectUrl. "
                                        + authenticationRequest.getLogInfo());
                        Logger.e(TAG, e.getMessage(), "", e.getCode());
                        waitingRequestOnError(waitingRequest, requestId, e);
                    } else {
                        // Browser has the url and it will exchange auth code
                        // for token
                        final CallbackHandler callbackHandle = new CallbackHandler(mHandler,
                                waitingRequest.mDelagete);

                        // Executes all the calls inside the Runnable to return
                        // immediately to
                        // UI thread. All UI
                        // related actions will be performed using the Handler.
                        sThreadExecutor.submit(new Runnable() {

                            @Override
                            public void run() {
                                Logger.v(
                                        TAG,
                                        "Processing url for token. "
                                                + authenticationRequest.getLogInfo());
                                Oauth2 oauthRequest = new Oauth2(authenticationRequest, mWebRequest);
                                AuthenticationResult result = null;
                                try {
                                    result = oauthRequest.getToken(endingUrl);
                                    Logger.v(TAG, "OnActivityResult processed the result. "
                                            + authenticationRequest.getLogInfo());
                                } catch (Exception exc) {
                                    String msg = "Error in processing code to get token. "
                                            + authenticationRequest.getLogInfo();
                                    Logger.e(TAG, msg,
                                            ExceptionExtensions.getExceptionMessage(exc),
                                            ADALError.AUTHORIZATION_CODE_NOT_EXCHANGED_FOR_TOKEN,
                                            exc);

                                    // Call error at UI thread
                                    waitingRequestOnError(
                                            callbackHandle,
                                            waitingRequest,
                                            requestId,
                                            new AuthenticationException(
                                                    ADALError.AUTHORIZATION_CODE_NOT_EXCHANGED_FOR_TOKEN,
                                                    msg, exc));
                                    return;
                                }

                                try {
                                    if (result != null) {
                                        Logger.v(TAG,
                                                "OnActivityResult is setting the token to cache. "
                                                        + authenticationRequest.getLogInfo());

                                        if (!StringExtensions.IsNullOrBlank(result.getAccessToken())) {
                                            setItemToCache(authenticationRequest, result, true);
                                        }

                                        if (waitingRequest != null
                                                && waitingRequest.mDelagete != null) {
                                            Logger.v(TAG, "Sending result to callback. "
                                                    + authenticationRequest.getLogInfo());
                                            callbackHandle.onSuccess(result);
                                        }
                                    } else {
                                        callbackHandle
                                                .onError(new AuthenticationException(
                                                        ADALError.AUTHORIZATION_CODE_NOT_EXCHANGED_FOR_TOKEN));
                                    }
                                } finally {
                                    removeWaitingRequest(requestId);
                                }
                            }
                        });
                    }
                }
            }
        }
    }

    private static boolean isUserMisMatch(final AuthenticationRequest request,
            final AuthenticationResult result) {
        if (result.getUserInfo() != null
                && !StringExtensions.IsNullOrBlank(result.getUserInfo().getUserId())
                && !StringExtensions.IsNullOrBlank(request.getUserId())) {
            // Verify if IdToken is present and userid is specified
            return !request.getUserId().equalsIgnoreCase(result.getUserInfo().getUserId());
        }

        // it should verify loginhint as well if specified
        if (result.getUserInfo() != null
                && !StringExtensions.IsNullOrBlank(result.getUserInfo().getDisplayableId())
                && !StringExtensions.IsNullOrBlank(request.getLoginHint())) {
            // Verify if IdToken is present and userid is specified
            return !request.getLoginHint()
                    .equalsIgnoreCase(result.getUserInfo().getDisplayableId());
        }

        return false;
    }

    /**
     * If request has correlationID, ADAL should report that instead of current
     * CorrelationId.
     * 
     * @param waitingRequest
     * @return
     */
    private String getCorrelationInfoFromWaitingRequest(
            final AuthenticationRequestState waitingRequest) {
        UUID requestCorrelationID = getRequestCorrelationId();
        if (waitingRequest.mRequest != null) {
            requestCorrelationID = waitingRequest.mRequest.getCorrelationId();
        }

        String correlationInfo = String.format(" CorrelationId: %s",
                requestCorrelationID.toString());
        return correlationInfo;
    }

    private void waitingRequestOnError(final AuthenticationRequestState waitingRequest,
            int requestId, AuthenticationException exc) {

        if (waitingRequest != null && waitingRequest.mDelagete != null) {
            Logger.v(TAG, "Sending error to callback"
                    + getCorrelationInfoFromWaitingRequest(waitingRequest));
            waitingRequest.mDelagete.onError(exc);
        }
        if (exc != null && exc.getCode() != ADALError.AUTH_FAILED_CANCELLED) {
            removeWaitingRequest(requestId);
        }
    }

    private void waitingRequestOnError(CallbackHandler handler,
            final AuthenticationRequestState waitingRequest, int requestId,
            final AuthenticationException exc) {

        if (waitingRequest != null && waitingRequest.mDelagete != null) {
            Logger.v(TAG, "Sending error to callback"
                    + getCorrelationInfoFromWaitingRequest(waitingRequest));
            handler.onError(exc);
        }
        if (exc != null && exc.getCode() != ADALError.AUTH_FAILED_CANCELLED) {
            removeWaitingRequest(requestId);
        }
    }

    private void removeWaitingRequest(int requestId) {
        Logger.v(TAG, "Remove waiting request: " + requestId);

        WRITE_LOCK.lock();
        try {
            mDelegateMap.remove(requestId);
        } finally {
            WRITE_LOCK.unlock();
        }
    }

    private AuthenticationRequestState getWaitingRequest(int requestId) {
        Logger.v(TAG, "Get waiting request: " + requestId);
        AuthenticationRequestState request = null;

        READ_LOCK.lock();
        try {
            request = mDelegateMap.get(requestId);
        } finally {
            READ_LOCK.unlock();
        }

        if (request == null && mAuthorizationCallback != null
                && requestId == mAuthorizationCallback.hashCode()) {
            // it does not have the caller callback. It will check the last
            // callback if set
            Logger.e(TAG, "Request callback is not available for requestid:" + requestId
                    + ". It will use last callback.", "", ADALError.CALLBACK_IS_NOT_FOUND);
            request = new AuthenticationRequestState(0, null, mAuthorizationCallback);
        }

        return request;
    }

    private void putWaitingRequest(int requestId, AuthenticationRequestState requestState) {
        Logger.v(TAG, "Put waiting request: " + requestId
                + getCorrelationInfoFromWaitingRequest(requestState));
        if (requestState != null) {
            WRITE_LOCK.lock();

            try {
                mDelegateMap.put(requestId, requestState);
            } finally {
                WRITE_LOCK.unlock();
            }
        }
    }

    /**
     * Active authentication activity can be cancelled if it exists. It may not
     * be cancelled if activity is not launched yet. RequestId is the hashcode
     * of your AuthenticationCallback.
     * 
     * @param requestId Hash code value of your callback to cancel activity
     *            launch
     * @return true: if there is a valid waiting request and cancel message send
     *         successfully. false: Request does not exist or cancel message not
     *         send
     */
    public boolean cancelAuthenticationActivity(int requestId) {

        AuthenticationRequestState request = getWaitingRequest(requestId);

        if (request == null || request.mDelagete == null) {
            // there is not any waiting callback
            Logger.v(TAG, "Current callback is empty. There is not any active authentication.");
            return true;
        }

        String currentCorrelationInfo = getCorrelationInfoFromWaitingRequest(request);
        Logger.v(TAG, "Current callback is not empty. There is an active authentication Activity."
                + currentCorrelationInfo);

        // intent to cancel. Authentication activity registers for this message
        // at onCreate event.
        final Intent intent = new Intent(AuthenticationConstants.Browser.ACTION_CANCEL);
        final Bundle extras = new Bundle();
        intent.putExtras(extras);
        intent.putExtra(AuthenticationConstants.Browser.REQUEST_ID, requestId);
        // send intent to cancel any active authentication activity.
        // it may not cancel it, if activity takes some time to launch.

        boolean cancelResult = LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
        if (cancelResult) {
            // clear callback if broadcast message was successful
            Logger.v(TAG, "Cancel broadcast message was successful." + currentCorrelationInfo);
            request.mCancelled = true;
            request.mDelagete.onError(new AuthenticationCancelError(
                    "Cancel broadcast message was successful."));
        } else {
            // Activity is not launched yet or receiver is not registered
            Logger.w(TAG, "Cancel broadcast message was not successful." + currentCorrelationInfo,
                    "", ADALError.BROADCAST_CANCEL_NOT_SUCCESSFUL);
        }

        return cancelResult;
    }

    /**
     * Singled threaded Executor for async work.
     */
    private static ExecutorService sThreadExecutor = Executors.newSingleThreadExecutor();

    private Handler mHandler;

    class CallbackHandler {
        private Handler mRefHandler;

        private AuthenticationCallback<AuthenticationResult> callback;

        public CallbackHandler(Handler ref, AuthenticationCallback<AuthenticationResult> callbackExt) {
            mRefHandler = ref;
            callback = callbackExt;
        }

        public void onError(final AuthenticationException e) {
            if (mRefHandler != null && callback != null) {
                mRefHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.onError(e);
                        return;
                    }
                });
            } else {
                throw e;
            }
        }

        public void onSuccess(final AuthenticationResult result) {
            if (mRefHandler != null && callback != null) {
                mRefHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.onSuccess(result);
                        return;
                    }
                });
            }
        }
    }

    private Future<AuthenticationResult> acquireTokenLocal(final IWindowComponent activity,
            final boolean useDialog, final AuthenticationRequest request,
            final AuthenticationCallback<AuthenticationResult> externalCall) {
        getHandler();
        final CallbackHandler callbackHandle = new CallbackHandler(mHandler, externalCall);

        // Executes all the calls inside the Runnable to return immediately to
        // user. All UI
        // related actions will be performed using Handler.
        Logger.setCorrelationId(getRequestCorrelationId());
        Logger.v(TAG, "Sending async task from thread:" + android.os.Process.myTid());
        return sThreadExecutor.submit(new Callable<AuthenticationResult>() {

            @Override
            public AuthenticationResult call() {
                Logger.v(TAG, "Running task in thread:" + android.os.Process.myTid());
                return acquireTokenLocalCall(callbackHandle, activity, useDialog, request);
            }
        });
    }

    /**
     * Only gets token from activity defined in this package.
     * 
     * @param activity
     * @param request
     * @param prompt
     * @param callback
     * @return
     */
    private AuthenticationResult acquireTokenLocalCall(final CallbackHandler callbackHandle,
            final IWindowComponent activity, final boolean useDialog,
            final AuthenticationRequest request) {
        URL authorityUrl = StringExtensions.getUrl(mAuthority);
        if (authorityUrl == null) {
            callbackHandle.onError(new AuthenticationException(
                    ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_URL));
            return null;
        }

        if (mValidateAuthority && !mAuthorityValidated) {
            try {
                final URL authorityUrlInCallback = authorityUrl;
                // Discovery call creates an Async Task to send
                // Web Requests
                // using a handler
                boolean result = validateAuthority(authorityUrl);
                if (result) {
                    mAuthorityValidated = true;
                    Logger.v(TAG, "Authority is validated: " + authorityUrlInCallback.toString());
                } else {
                    Logger.v(TAG, "Call external callback since instance is invalid"
                            + authorityUrlInCallback.toString());
                    callbackHandle.onError(new AuthenticationException(
                            ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_INSTANCE));
                    return null;
                }
            } catch (Exception exc) {
                Logger.e(TAG, "Authority validation has an error.", "",
                        ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_INSTANCE, exc);
                callbackHandle.onError(new AuthenticationException(
                        ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_INSTANCE));
                return null;
            }
        }

        // Validated the authority or skipped the validation
        return acquireTokenAfterValidation(callbackHandle, activity, useDialog, request);
    }

    private boolean promptUser(PromptBehavior prompt) {
        return prompt == PromptBehavior.Always || prompt == PromptBehavior.REFRESH_SESSION;
    }

    private AuthenticationResult acquireTokenAfterValidation(CallbackHandler callbackHandle,
            final IWindowComponent activity, final boolean useDialog,
            final AuthenticationRequest request) {
        Logger.v(TAG, "Token request started");

        // BROKER flow intercepts here
        // cache and refresh call happens through the authenticator service
        if (mBrokerProxy.canSwitchToBroker()) {
            Logger.v(TAG, "It switched to broker for context: " + mContext.getPackageName());
            AuthenticationResult result = null;
            request.setVersion(getVersionName());
            // Don't send background request, if prompt flag is always or
            // refresh_session
            if (!promptUser(request.getPrompt())) {
                try {
                    result = mBrokerProxy.getAuthTokenInBackground(request);
                } catch (AuthenticationException ex) {
                    // pass back to caller for known exceptions such as failure
                    // to encrypt
                    if (callbackHandle.callback != null) {
                        callbackHandle.onError(ex);
                        return null;
                    } else {
                        throw ex;
                    }
                }
            }

            if (result != null && result.getAccessToken() != null
                    && !result.getAccessToken().isEmpty()) {
                Logger.v(TAG, "Token is returned from background call ");
                if (callbackHandle.callback != null) {
                    callbackHandle.onSuccess(result);
                }
                return result;
            }

            // Launch broker activity
            // if cache and refresh request is not handled.
            // Initial request to authenticator needs to launch activity to
            // record calling uid for the account. This happens for Prompt auto
            // or always behavior.
            if (!request.isSilent() && callbackHandle.callback != null && activity != null) {

                // Only happens with callback since silent call does not show UI
                Logger.v(TAG, "Launch activity for Authenticator");
                mAuthorizationCallback = callbackHandle.callback;
                request.setRequestId(callbackHandle.callback.hashCode());
                Logger.v(TAG, "Starting Authentication Activity with callback:"
                        + callbackHandle.callback.hashCode());
                putWaitingRequest(callbackHandle.callback.hashCode(),
                        new AuthenticationRequestState(callbackHandle.callback.hashCode(), request,
                                callbackHandle.callback));
                if (result != null && result.isInitialRequest()) {
                    Logger.v(TAG, "Initial request to authenticator");
                    // Record the initial request but not force a prompt
                }

                // onActivityResult will receive the response
                // Activity needs to launch to record calling app for this
                // account
                Intent brokerIntent = mBrokerProxy.getIntentForBrokerActivity(request);
                if (brokerIntent != null) {
                    try {
                        Logger.v(TAG, "Calling activity pid:" + android.os.Process.myPid()
                                + " tid:" + android.os.Process.myTid() + "uid:"
                                + android.os.Process.myUid());
                        activity.startActivityForResult(brokerIntent,
                                AuthenticationConstants.UIRequest.BROWSER_FLOW);
                    } catch (ActivityNotFoundException e) {
                        Logger.e(TAG, "Activity login is not found after resolving intent", "",
                                ADALError.DEVELOPER_ACTIVITY_IS_NOT_RESOLVED, e);
                        callbackHandle.onError(new AuthenticationException(
                                ADALError.BROKER_ACTIVITY_IS_NOT_RESOLVED));
                    }
                } else {
                    callbackHandle.onError(new AuthenticationException(
                            ADALError.DEVELOPER_ACTIVITY_IS_NOT_RESOLVED));
                }
            } else {

                // User does not want to launch activity
                String msg = "Prompt is not allowed and failed to get token:";
                Logger.e(TAG, msg, "", ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED);
                callbackHandle.onError(new AuthenticationException(
                        ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED, msg));
            }

            // It will start activity if callback is provided. Return null here.
            return null;
        } else {
            return localFlow(callbackHandle, activity, useDialog, request);
        }
    }

    private AuthenticationResult localFlow(CallbackHandler callbackHandle,
            final IWindowComponent activity, final boolean useDialog,
            final AuthenticationRequest request) {
        // Lookup access token from cache
        AuthenticationResult cachedItem = getItemFromCache(request);
        if (cachedItem != null && isUserMisMatch(request, cachedItem)) {
            if (callbackHandle.callback != null) {
                callbackHandle.onError(new AuthenticationException(
                        ADALError.AUTH_FAILED_USER_MISMATCH));
                return null;
            } else {
                throw new AuthenticationException(ADALError.AUTH_FAILED_USER_MISMATCH);
            }
        }

        if (!promptUser(request.getPrompt()) && isValidCache(cachedItem)) {
            Logger.v(TAG, "Token is returned from cache");
            if (callbackHandle.callback != null) {
                callbackHandle.onSuccess(cachedItem);
            }
            return cachedItem;
        }

        Logger.v(TAG, "Checking refresh tokens");
        RefreshItem refreshItem = getRefreshToken(request);
        if (!promptUser(request.getPrompt()) && refreshItem != null
                && !StringExtensions.IsNullOrBlank(refreshItem.mRefreshToken)) {
            Logger.v(TAG, "Refresh token is available and it will attempt to refresh token");
            return refreshToken(callbackHandle, activity, useDialog, request, refreshItem, true);
        } else {
            Logger.v(TAG, "Refresh token is not available");
            if (!request.isSilent() && callbackHandle.callback != null
                    && (activity != null || useDialog)) {
                // start activity if other options are not available
                // delegate map is used to remember callback if another
                // instance of authenticationContext is created for config
                // change or similar at client app.
                mAuthorizationCallback = callbackHandle.callback;
                request.setRequestId(callbackHandle.callback.hashCode());
                Logger.v(TAG, "Starting Authentication Activity with callback:"
                        + callbackHandle.callback.hashCode());
                putWaitingRequest(callbackHandle.callback.hashCode(),
                        new AuthenticationRequestState(callbackHandle.callback.hashCode(), request,
                                callbackHandle.callback));

                if (useDialog) {
                    AuthenticationDialog dialog = new AuthenticationDialog(mHandler, mContext,
                            this, request);
                    dialog.show();
                } else {
                    // onActivityResult will receive the response
                    if (!startAuthenticationActivity(activity, request)) {
                        callbackHandle.onError(new AuthenticationException(
                                ADALError.DEVELOPER_ACTIVITY_IS_NOT_RESOLVED));
                    }
                }
            } else {

                // User does not want to launch activity
                Logger.e(TAG, "Prompt is not allowed and failed to get token:", "",
                        ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED);
                callbackHandle.onError(new AuthenticationException(
                        ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED));
            }
        }

        return null;
    }

    protected boolean isRefreshable(AuthenticationResult cachedItem) {
        return cachedItem != null && !StringExtensions.IsNullOrBlank(cachedItem.getRefreshToken());
    }

    private boolean isValidCache(AuthenticationResult cachedItem) {
        if (cachedItem != null && !StringExtensions.IsNullOrBlank(cachedItem.getAccessToken())
                && !cachedItem.isExpired()) {
            return true;
        }

        return false;
    }

    /**
     * get token from cache to return it, if not expired.
     * 
     * @param request
     * @return AuthenticationResult
     */
    private AuthenticationResult getItemFromCache(final AuthenticationRequest request) {
        if (mTokenCacheStore != null) {
            
            // get token if resourceid matches to cache key.
            TokenCacheItem item = null;
            if(request.getUserIdentifierType() == UserIdentifierType.LoginHint){
                item = mTokenCacheStore.getItem(CacheKey.createCacheKey(request,
                        request.getLoginHint()));
            }
            
            if(request.getUserIdentifierType() == UserIdentifierType.UniqueId){
                item = mTokenCacheStore.getItem(CacheKey.createCacheKey(request,
                        request.getUserId()));
            }
            
            if(request.getUserIdentifierType() == UserIdentifierType.NoUser){
                item = mTokenCacheStore.getItem(CacheKey.createCacheKey(request,
                        null));
            }
            
            if (item != null) {
                Logger.v(TAG,
                        "getItemFromCache accessTokenId:" + getTokenHash(item.getAccessToken())
                                + " refreshTokenId:" + getTokenHash(item.getRefreshToken()));
                return AuthenticationResult.createResult(item);
            }
        }
        return null;
    }

    private String getTokenHash(String token) {
        try {
            return StringExtensions.createHash(token);
        } catch (NoSuchAlgorithmException e) {
            Logger.e(TAG, "Digest error", "", ADALError.DEVICE_NO_SUCH_ALGORITHM, e);
        } catch (UnsupportedEncodingException e) {
            Logger.e(TAG, "Digest error", "", ADALError.ENCODING_IS_NOT_SUPPORTED, e);
        }

        return "";
    }

    /**
     * If refresh token fails, this needs to be removed from cache to not use
     * this again for next try. Error in refreshToken call will result in
     * another call to acquireToken. It may try multi resource refresh token for
     * second attempt.
     */
    private class RefreshItem {
        String mRefreshToken;

        String mKey;

        boolean mMultiResource;

        UserInfo mUserInfo;

        String mRawIdToken;

        String mKeyWithUserId;

        String mKeyWithDisplayableId;

        public RefreshItem(String keyInCache, AuthenticationRequest request, TokenCacheItem item,
                boolean multiResource) {
            mKey = keyInCache;
            mMultiResource = multiResource;

            if (item != null) {
                mRefreshToken = item.getRefreshToken();
                mUserInfo = item.getUserInfo();
                mRawIdToken = item.getRawIdToken();
                if (item.getUserInfo() != null) {
                    mKeyWithUserId = CacheKey.createCacheKey(request, item.getUserInfo()
                            .getUserId());
                    mKeyWithDisplayableId = CacheKey.createCacheKey(request, item.getUserInfo()
                            .getDisplayableId());
                }
            }
        }

        public RefreshItem(String refreshToken) {
            mMultiResource = false;
            mRefreshToken = refreshToken;
        }
    }

    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");
            String userId = request.getUserId();
            if (StringExtensions.IsNullOrBlank(userId)) {
                // acquireTokenSilent expects userid field from UserInfo
                userId = request.getLoginHint();
            }
            String keyUsed = CacheKey.createCacheKey(request, userId);
            TokenCacheItem item = mTokenCacheStore.getItem(keyUsed);
            if (item == null || StringExtensions.IsNullOrBlank(item.getRefreshToken())) {
                // if not present, check multiResource item in cache. Cache key
                // will not include resourceId in the cache key string.
                Logger.v(TAG, "Looking for Multi Resource Refresh token");
                keyUsed = CacheKey.createMultiResourceRefreshTokenKey(request, userId);
                item = mTokenCacheStore.getItem(keyUsed);
                multiResource = true;
            }

            if (item != null && !StringExtensions.IsNullOrBlank(item.getRefreshToken())) {
                String refreshTokenHash = getTokenHash(item.getRefreshToken());

                Logger.v(TAG, "Refresh token is available and id:" + refreshTokenHash
                        + " Key used:" + keyUsed);
                refreshItem = new RefreshItem(keyUsed, request, item, multiResource);
            }
        }

        return refreshItem;
    }

    private void setItemToCache(final AuthenticationRequest request, AuthenticationResult result,
            boolean afterPrompt) throws AuthenticationException {
        if (mTokenCacheStore != null) {

            // User can ask for token without login hint. Next call from same
            // method should use token from cache.
            Logger.v(TAG, "Setting item to cache");

            // Calculate token hashcode
            logReturnedToken(request, result);

            // acquireTokenSilent uses userid to request items
            String userKey = request.getUserId();

            if (afterPrompt) {
                // User can change the username and enter a different one at
                // prompt. Use idtoken if present instead of loginhint after
                // prompt.
                if (result.getUserInfo() != null
                        && !StringExtensions.IsNullOrBlank(result.getUserInfo().getDisplayableId())) {
                    Logger.v(TAG, "Updating cache for username:"
                            + result.getUserInfo().getDisplayableId());
                    setItemToCacheForUser(request, result, result.getUserInfo().getDisplayableId());
                }
            } else if (StringExtensions.IsNullOrBlank(userKey)) {
                userKey = request.getLoginHint();
            }

            // It will store in the cache for empty idtokens as well
            setItemToCacheForUser(request, result, userKey);

            // Set item with userid if idtoken is present.
            if (result.getUserInfo() != null
                    && !StringExtensions.IsNullOrBlank(result.getUserInfo().getUserId())) {
                Logger.v(TAG, "Updating userId:" + result.getUserInfo().getUserId());
                setItemToCacheForUser(request, result, result.getUserInfo().getUserId());
            }
        }
    }

    private void setItemToCacheForUser(final AuthenticationRequest request,
            AuthenticationResult result, String userId) {
        mTokenCacheStore.setItem(CacheKey.createCacheKey(request, userId), new TokenCacheItem(
                request, result, false));

        // Store broad refresh token if available
        if (result.getIsMultiResourceRefreshToken()) {
            Logger.v(TAG, "Setting Multi Resource Refresh token to cache");
            mTokenCacheStore.setItem(CacheKey.createMultiResourceRefreshTokenKey(request, userId),
                    new TokenCacheItem(request, result, true));
        }
    }

    /**
     * Calculate hash for accessToken and log that.
     * 
     * @param request
     * @param result
     */
    private void logReturnedToken(final AuthenticationRequest request,
            final AuthenticationResult result) {
        if (result != null && result.getAccessToken() != null) {
            String accessTokenHash = getTokenHash(result.getAccessToken());
            String refreshTokenHash = getTokenHash(result.getRefreshToken());
            Logger.v(TAG, String.format(
                    "Access TokenID %s and Refresh TokenID %s returned. CorrelationId: %s",
                    accessTokenHash, refreshTokenHash, request.getCorrelationId()));
        }
    }

    private void setItemToCacheFromRefresh(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);
            logReturnedToken(request, result);

            // Update for cache key
            mTokenCacheStore.setItem(refreshItem.mKey, new TokenCacheItem(request, result,
                    refreshItem.mMultiResource));

            setItemToCache(request, result, false);
        }
    }

    private void removeItemFromCache(final RefreshItem refreshItem) throws AuthenticationException {
        if (mTokenCacheStore != null) {
            Logger.v(TAG, "Remove refresh item from cache:" + refreshItem.mKey);
            mTokenCacheStore.removeItem(refreshItem.mKey);
            // clean up keys related to userid/displayableid for same request
            mTokenCacheStore.removeItem(refreshItem.mKeyWithUserId);
            mTokenCacheStore.removeItem(refreshItem.mKeyWithDisplayableId);
        }
    }

    /**
     * refresh token if possible. if it fails, it calls acquire token after
     * removing refresh token from cache.
     * 
     * @param callbackHandle
     * @param activity Activity to use in case refresh token does not succeed
     *            and prompt is not set to never.
     * @param request incoming request
     * @param refreshItem refresh item info to remove this refresh token from
     *            cache
     * @param useCache refresh request can be explicit without cache usage.
     *            Error message should return without trying prompt.
     * @param externalCallback
     * @return
     */
    private AuthenticationResult refreshToken(final CallbackHandler callbackHandle,
            final IWindowComponent activity, final boolean useDialog,
            final AuthenticationRequest request, final RefreshItem refreshItem,
            final boolean useCache) {

        Logger.v(TAG, "Process refreshToken for " + request.getLogInfo() + " refreshTokenId:"
                + getTokenHash(refreshItem.mRefreshToken));

        // Removes refresh token from cache, when this call is complete. Request
        // may be interrupted, if app is shutdown by user. Detect connection
        // state to not remove refresh token if user turned Airplane mode or
        // similar.
        if (!mConnectionService.isConnectionAvailable()) {
            AuthenticationException exc = new AuthenticationException(
                    ADALError.DEVICE_CONNECTION_IS_NOT_AVAILABLE,
                    "Connection is not available to refresh token");
            Logger.w(TAG, "Connection is not available to refresh token", request.getLogInfo(),
                    ADALError.DEVICE_CONNECTION_IS_NOT_AVAILABLE);
            callbackHandle.onError(exc);
            return null;
        }

        AuthenticationResult result = null;
        try {
            Oauth2 oauthRequest = new Oauth2(request, mWebRequest, mJWSBuilder);
            result = oauthRequest.refreshToken(refreshItem.mRefreshToken);
            if (result != null && StringExtensions.IsNullOrBlank(result.getRefreshToken())) {
                Logger.v(TAG, "Refresh token is not returned or empty");
                result.setRefreshToken(refreshItem.mRefreshToken);
            }
        } catch (Exception exc) {
            // Server side error or similar
            Logger.e(TAG, "Error in refresh token for request:" + request.getLogInfo(),
                    ExceptionExtensions.getExceptionMessage(exc), ADALError.AUTH_FAILED_NO_TOKEN,
                    exc);

            AuthenticationException authException = new AuthenticationException(
                    ADALError.AUTH_FAILED_NO_TOKEN, ExceptionExtensions.getExceptionMessage(exc),
                    exc);
            callbackHandle.onError(authException);
            return null;
        }

        if (useCache) {
            if (result == null || StringExtensions.IsNullOrBlank(result.getAccessToken())) {
                String errLogInfo = result == null ? "" : result.getErrorLogInfo();
                Logger.w(TAG, "Refresh token did not return accesstoken.", request.getLogInfo()
                        + errLogInfo, ADALError.AUTH_FAILED_NO_TOKEN);

                // remove item from cache to avoid same usage of
                // refresh token in next acquireToken call
                removeItemFromCache(refreshItem);
                return acquireTokenLocalCall(callbackHandle, activity, useDialog, request);
            } else {
                Logger.v(TAG, "It finished refresh token request:" + request.getLogInfo());
                if (result.getUserInfo() == null && refreshItem.mUserInfo != null) {
                    Logger.v(TAG, "UserInfo is updated from cached result:" + request.getLogInfo());
                    result.setUserInfo(refreshItem.mUserInfo);
                    result.setIdToken(refreshItem.mRawIdToken);
                }

                // it replaces multi resource refresh token as
                // well with the new one since it is not stored
                // with resource.
                Logger.v(TAG, "Cache is used. It will set item to cache" + request.getLogInfo());
                setItemToCacheFromRefresh(refreshItem, request, result);

                // return result obj which has error code and
                // error description that is returned from
                // server response
                if (callbackHandle.callback != null) {
                    callbackHandle.onSuccess(result);
                }
                return result;
            }
        } else {

            // User is not using cache and explicitly
            // calling with refresh token. User should received
            // error code and error description in
            // Authentication result for Oauth errors
            Logger.v(TAG, "Cache is not used for Request:" + request.getLogInfo());
            if (callbackHandle.callback != null) {
                callbackHandle.onSuccess(result);
            }
            return result;
        }
    }

    private boolean validateAuthority(final URL authorityUrl) {

        // This is not calling outer callback. It is using
        // authenticationCallback, so handler is not needed here
        if (mDiscovery != null) {
            Logger.v(TAG, "Start validating authority");

            // Set CorrelationId for Instance Discovery
            mDiscovery.setCorrelationId(getRequestCorrelationId());
            try {
                boolean result = mDiscovery.isValidAuthority(authorityUrl);
                Logger.v(TAG, "Finish validating authority:" + authorityUrl + " result:" + result);
                return result;
            } catch (Exception exc) {
                Logger.e(TAG, "Instance validation returned error", "",
                        ADALError.DEVELOPER_AUTHORITY_CAN_NOT_BE_VALIDED, exc);

            }
        }
        return false;
    }

    private String getRedirectFromPackage() {
        return mContext.getApplicationContext().getPackageName();
    }

    /**
     * @param activity
     * @param request
     * @return false: if intent is not resolved or error in starting. true: if
     *         intent is sent to start the activity.
     */
    private boolean startAuthenticationActivity(final IWindowComponent activity,
            AuthenticationRequest request) {
        Intent intent = getAuthenticationActivityIntent(activity, request);

        if (!resolveIntent(intent)) {
            Logger.e(TAG, "Intent is not resolved", "",
                    ADALError.DEVELOPER_ACTIVITY_IS_NOT_RESOLVED);
            return false;
        }

        try {
            // Start activity from callers context so that caller can intercept
            // when it is done
            activity.startActivityForResult(intent, AuthenticationConstants.UIRequest.BROWSER_FLOW);
        } catch (ActivityNotFoundException e) {
            Logger.e(TAG, "Activity login is not found after resolving intent", "",
                    ADALError.DEVELOPER_ACTIVITY_IS_NOT_RESOLVED, e);
            return false;
        }

        return true;
    }

    /**
     * Resolve activity from the package. If developer did not declare the
     * activity, it will not resolve.
     * 
     * @param intent
     * @return true if activity is defined in the package.
     */
    private final boolean resolveIntent(Intent intent) {
        ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(intent, 0);
        if (resolveInfo == null) {
            return false;
        }

        return true;
    }

    /**
     * Get intent to start authentication activity.
     * 
     * @param request
     * @return intent for authentication activity
     */
    private final Intent getAuthenticationActivityIntent(IWindowComponent activity,
            AuthenticationRequest request) {
        Intent intent = new Intent();
        if (AuthenticationSettings.INSTANCE.getActivityPackageName() != null) {
            // This will use the activity from another given package.
            intent.setClassName(AuthenticationSettings.INSTANCE.getActivityPackageName(),
                    AuthenticationActivity.class.getName());
        } else {
            // This will lookup the authentication activity within this context
            intent.setClass(mContext, AuthenticationActivity.class);
        }

        intent.putExtra(AuthenticationConstants.Browser.REQUEST_MESSAGE, request);
        return intent;
    }

    /**
     * Get the CorrelationId set by user.
     * 
     * @return UUID
     */
    public UUID getRequestCorrelationId() {
        if (mRequestCorrelationId == null) {
            return UUID.randomUUID();
        }

        return mRequestCorrelationId;
    }

    /**
     * set CorrelationId to requests.
     * 
     * @param mRequestCorrelationId
     */
    public void setRequestCorrelationId(UUID requestCorrelationId) {
        this.mRequestCorrelationId = requestCorrelationId;
        Logger.setCorrelationId(requestCorrelationId);
    }

    /**
     * Developer is using refresh token call to do refresh without cache usage.
     * App context or activity is not needed. Async requests are created,so this
     * needs to be called at UI thread.
     */
    private void refreshTokenWithoutCache(final String refreshToken, final String clientId,
            final String resource,
            final AuthenticationCallback<AuthenticationResult> externalCallback) {
        Logger.setCorrelationId(getRequestCorrelationId());
        Logger.v(TAG, "Refresh token without cache");

        if (StringExtensions.IsNullOrBlank(refreshToken)) {
            throw new IllegalArgumentException("Refresh token is not provided");
        }

        if (StringExtensions.IsNullOrBlank(clientId)) {
            throw new IllegalArgumentException("ClientId is not provided");
        }

        if (externalCallback == null) {
            throw new IllegalArgumentException("Callback is not provided");
        }

        final CallbackHandler callbackHandle = new CallbackHandler(getHandler(), externalCallback);

        // Execute all the calls inside Runnable to return immediately. All UI
        // related actions will be performed using Handler.
        sThreadExecutor.submit(new Runnable() {
            @Override
            public void run() {
                final URL authorityUrl = StringExtensions.getUrl(mAuthority);
                if (authorityUrl == null) {
                    callbackHandle.onError(new AuthenticationException(
                            ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_URL));

                    return;
                }

                final AuthenticationRequest request = new AuthenticationRequest(mAuthority,
                        resource, clientId, getRequestCorrelationId());

                // It is not using cache and refresh is not expected to
                // show authentication activity.
                request.setSilent(true);
                final RefreshItem refreshItem = new RefreshItem(refreshToken);

                if (mValidateAuthority) {
                    Logger.v(TAG, "Validating authority");

                    try {
                        if (validateAuthority(authorityUrl)) {
                            Logger.v(TAG, "Authority is validated" + authorityUrl.toString());
                        } else {
                            Logger.v(
                                    TAG,
                                    "Call callback since instance is invalid:"
                                            + authorityUrl.toString());
                            callbackHandle.onError(new AuthenticationException(
                                    ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_INSTANCE));
                            return;
                        }
                    } catch (Exception exc) {
                        Logger.e(TAG, "Authority validation is failed",
                                ExceptionExtensions.getExceptionMessage(exc),
                                ADALError.SERVER_INVALID_REQUEST, exc);
                        callbackHandle
                                .onError(new AuthenticationException(
                                        ADALError.SERVER_INVALID_REQUEST,
                                        "Authority validation is failed"));
                        return;
                    }
                }

                // Follow refresh logic now. Authority is valid or
                // skipped validation
                refreshToken(callbackHandle, null, false, request, refreshItem, false);
            }
        });
    }

    private synchronized Handler getHandler() {
        if (mHandler == null) {
            // Use current main looper
            mHandler = new Handler(mContext.getMainLooper());
        }

        return mHandler;
    }

    private static String extractAuthority(String authority) {
        if (!StringExtensions.IsNullOrBlank(authority)) {

            // excluding the starting https:// or http://
            int thirdSlash = authority.indexOf("/", EXCLUDE_INDEX);

            // third slash is not the last character
            if (thirdSlash >= 0 && thirdSlash != (authority.length() - 1)) {
                int fourthSlash = authority.indexOf("/", thirdSlash + 1);
                if (fourthSlash < 0 || fourthSlash > thirdSlash + 1) {
                    if (fourthSlash >= 0) {
                        return authority.substring(0, fourthSlash);
                    }

                    return authority;
                }
            }
        }

        throw new IllegalArgumentException("authority");
    }

    private void checkInternetPermission() {
        PackageManager pm = mContext.getPackageManager();
        if (PackageManager.PERMISSION_GRANTED != pm.checkPermission("android.permission.INTERNET",
                mContext.getPackageName())) {
            throw new AuthenticationException(ADALError.DEVELOPER_INTERNET_PERMISSION_MISSING);
        }
    }

    class DefaultConnectionService implements IConnectionService {

        private Context mConnectionContext;

        DefaultConnectionService(Context ctx) {
            mConnectionContext = ctx;
        }

        public boolean isConnectionAvailable() {
            ConnectivityManager connectivityManager = (ConnectivityManager)mConnectionContext
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
            boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
            return isConnected;
        }
    }

    /**
     * Version name for ADAL not for the app itself.
     * 
     * @return Version
     */
    public static String getVersionName() {
        // Package manager does not report for ADAL
        // AndroidManifest files are not merged, so it is returning hard coded
        // value
        return "1.0.9";
    }
}




Java Source Code List

com.microsoft.aad.adal.ADALError.java
com.microsoft.aad.adal.AuthenticationActivity.java
com.microsoft.aad.adal.AuthenticationCallback.java
com.microsoft.aad.adal.AuthenticationCancelError.java
com.microsoft.aad.adal.AuthenticationConstants.java
com.microsoft.aad.adal.AuthenticationContext.java
com.microsoft.aad.adal.AuthenticationDialog.java
com.microsoft.aad.adal.AuthenticationException.java
com.microsoft.aad.adal.AuthenticationParameters.java
com.microsoft.aad.adal.AuthenticationRequestState.java
com.microsoft.aad.adal.AuthenticationRequest.java
com.microsoft.aad.adal.AuthenticationResult.java
com.microsoft.aad.adal.AuthenticationSettings.java
com.microsoft.aad.adal.BasicWebViewClient.java
com.microsoft.aad.adal.BrokerProxy.java
com.microsoft.aad.adal.CacheKey.java
com.microsoft.aad.adal.ChallangeResponseBuilder.java
com.microsoft.aad.adal.ClientMetrics.java
com.microsoft.aad.adal.DefaultTokenCacheStore.java
com.microsoft.aad.adal.Discovery.java
com.microsoft.aad.adal.ExceptionExtensions.java
com.microsoft.aad.adal.FileTokenCacheStore.java
com.microsoft.aad.adal.HashMapExtensions.java
com.microsoft.aad.adal.HttpWebRequest.java
com.microsoft.aad.adal.HttpWebResponse.java
com.microsoft.aad.adal.IBrokerProxy.java
com.microsoft.aad.adal.IConnectionService.java
com.microsoft.aad.adal.IDeviceCertificate.java
com.microsoft.aad.adal.IDiscovery.java
com.microsoft.aad.adal.IJWSBuilder.java
com.microsoft.aad.adal.ITokenCacheStore.java
com.microsoft.aad.adal.ITokenStoreQuery.java
com.microsoft.aad.adal.IWebRequestHandler.java
com.microsoft.aad.adal.IWindowComponent.java
com.microsoft.aad.adal.IdToken.java
com.microsoft.aad.adal.JWSBuilder.java
com.microsoft.aad.adal.Logger.java
com.microsoft.aad.adal.MemoryTokenCacheStore.java
com.microsoft.aad.adal.Oauth2.java
com.microsoft.aad.adal.PRNGFixes.java
com.microsoft.aad.adal.PackageHelper.java
com.microsoft.aad.adal.PromptBehavior.java
com.microsoft.aad.adal.StorageHelper.java
com.microsoft.aad.adal.StringExtensions.java
com.microsoft.aad.adal.TokenCacheItem.java
com.microsoft.aad.adal.UrlExtensions.java
com.microsoft.aad.adal.UserInfo.java
com.microsoft.aad.adal.WebRequestHandler.java
com.microsoft.aad.adal.WebviewHelper.java
com.microsoft.aad.adal.hello.Constants.java
com.microsoft.aad.adal.hello.FragmentHolderActivity.java
com.microsoft.aad.adal.hello.LoginFragment.java
com.microsoft.aad.adal.hello.MainActivity.java
com.microsoft.aad.adal.hello.Utils.java
com.microsoft.aad.adal.package-info.java