Android Open Source - azure-activedirectory-library-for-android Discovery






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.
///*w  w  w.  j  a  v  a  2  s. co m*/
// 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.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;

import org.json.JSONException;

import android.net.Uri;

/**
 * Instance and Tenant discovery. It takes authorization endpoint and sends
 * query to known hard coded instances to get tenant discovery endpoint. If
 * instance is valid, it will return tenant discovery endpoint info. Instance
 * discovery endpoint does not verify tenant info, so Discovery implementation
 * sends common as a tenant name. Discovery checks only authorization endpoint.
 * It does not do tenant verification. Initialize and call from UI thread.
 */
final class Discovery implements IDiscovery {

    private static final String TAG = "Discovery";

    private static final String API_VERSION_KEY = "api-version";

    private static final String API_VERSION_VALUE = "1.0";

    private static final String AUTHORIZATION_ENDPOINT_KEY = "authorization_endpoint";

    private static final String INSTANCE_DISCOVERY_SUFFIX = "common/discovery/instance";

    private static final String AUTHORIZATION_COMMON_ENDPOINT = "/common/oauth2/authorize";

    private static final String TENANT_DISCOVERY_ENDPOINT = "tenant_discovery_endpoint";

    /**
     * Sync set of valid hosts to skip query to server if host was verified
     * before.
     */
    private static final Set<String> sValidHosts = Collections
            .synchronizedSet(new HashSet<String>());

    /**
     * Discovery query will go to the prod only for now.
     */
    private static final String TRUSTED_QUERY_INSTANCE = "login.windows.net";

    private UUID mCorrelationId;

    /**
     * interface to use in testing.
     */
    private IWebRequestHandler mWebrequestHandler;

    public Discovery() {
        initValidList();
        mWebrequestHandler = new WebRequestHandler();
    }

    @Override
    public boolean isValidAuthority(URL authorizationEndpoint) {
        // For comparison purposes, convert to lowercase Locale.US
        // getProtocol returns scheme and it is available if it is absolute url
        // Authority is in the form of https://Instance/tenant/somepath
        if (authorizationEndpoint != null
                && !StringExtensions.IsNullOrBlank(authorizationEndpoint.getHost())
                && authorizationEndpoint.getProtocol().equals("https")
                && StringExtensions.IsNullOrBlank(authorizationEndpoint.getQuery())
                && StringExtensions.IsNullOrBlank(authorizationEndpoint.getRef())
                && !StringExtensions.IsNullOrBlank(authorizationEndpoint.getPath())) {

            if (UrlExtensions.isADFSAuthority(authorizationEndpoint)) {
                throw new AuthenticationException(ADALError.DISCOVERY_NOT_SUPPORTED);
            } else if (sValidHosts.contains(authorizationEndpoint.getHost().toLowerCase(Locale.US))) {
                // host can be the instance or inside the validated list.
                // Valid hosts will help to skip validation if validated before
                // call Callback and skip the look up
                return true;
            } else {
                // Only query from Prod instance for now, not all of the
                // instances in the list
                return queryInstance(authorizationEndpoint);
            }
        }

        return false;
    }

    

    /**
     * add this host as valid to skip another query to server.
     * 
     * @param validhost
     */
    private void addValidHostToList(URL validhost) {
        String validHost = validhost.getHost();
        if (!StringExtensions.IsNullOrBlank(validHost)) {
            // for comparisons it uses Locale.US, so it needs to be same
            // here
            sValidHosts.add(validHost.toLowerCase(Locale.US));
        }
    }

    /**
     * initialize initial valid host list with known instances.
     */
    private void initValidList() {
        // mValidHosts is a sync set
        if (sValidHosts.size() == 0) {
            sValidHosts.add("login.windows.net");
            sValidHosts.add("login.chinacloudapi.cn");
            sValidHosts.add("login.cloudgovapi.us");
        }
    }

    private boolean queryInstance(final URL authorizationEndpointUrl) {

        // It will query prod instance to verify the authority
        // construct query string for this instance
        URL queryUrl;
        boolean result = false;       
        try {
            queryUrl = buildQueryString(TRUSTED_QUERY_INSTANCE,
                    getAuthorizationCommonEndpoint(authorizationEndpointUrl));

            result = sendRequest(queryUrl);
        } catch (MalformedURLException e) {
            Logger.e(TAG, "Invalid authority", "", ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_URL,
                    e);
            result = false;
        } catch (JSONException e) {
            Logger.e(TAG, "Json parsing error", "",
                    ADALError.DEVELOPER_AUTHORITY_CAN_NOT_BE_VALIDED, e);
            result = false;
        }

        if (result) {
            // it is validated
            addValidHostToList(authorizationEndpointUrl);
        }

        return result;
    }

    private boolean sendRequest(final URL queryUrl) throws MalformedURLException, JSONException {

        Logger.v(TAG, "Sending discovery request to:" + queryUrl);
        HashMap<String, String> headers = new HashMap<String, String>();
        headers.put(WebRequestHandler.HEADER_ACCEPT, WebRequestHandler.HEADER_ACCEPT_JSON);

        // CorrelationId is used to track the request at the Azure services
        if (mCorrelationId != null) {
            headers.put(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, mCorrelationId.toString());
            headers.put(AuthenticationConstants.AAD.RETURN_CLIENT_REQUEST_ID, "true");
        }
               
        HttpWebResponse webResponse = null;
        String errorCodes = "";
        try {
            ClientMetrics.INSTANCE.beginClientMetricsRecord(queryUrl, mCorrelationId, headers);
            webResponse = mWebrequestHandler.sendGet(queryUrl, headers);
            if (webResponse.getResponseException() == null) {
                ClientMetrics.INSTANCE.setLastError(null);
            } else {
                ClientMetrics.INSTANCE.setLastError(String.valueOf(webResponse.getStatusCode()));
            }
            
            // parse discovery response to find tenant info
            HashMap<String, String> discoveryResponse = parseResponse(webResponse);
            if(discoveryResponse.containsKey(AuthenticationConstants.OAuth2.ERROR_CODES))
            {
                errorCodes = discoveryResponse.get(AuthenticationConstants.OAuth2.ERROR_CODES);
                ClientMetrics.INSTANCE.setLastError(errorCodes);
            }
            
            return (discoveryResponse != null && discoveryResponse.containsKey(TENANT_DISCOVERY_ENDPOINT));
        } catch (IllegalArgumentException exc) {
            Logger.e(TAG, exc.getMessage(), "", ADALError.DEVELOPER_AUTHORITY_CAN_NOT_BE_VALIDED,
                    exc);
            throw exc;
        } catch (JSONException e) {
            Logger.e(TAG, "Json parsing error", "",
                    ADALError.DEVELOPER_AUTHORITY_CAN_NOT_BE_VALIDED, e);
            throw e;
        }
        finally {
            ClientMetrics.INSTANCE.endClientMetricsRecord(ClientMetricsEndpointType.INSTANCE_DISCOVERY, mCorrelationId);                
        }
    }

    /**
     * get Json output from web response body. If it is well formed response, it
     * will have tenant discovery endpoint.
     * 
     * @param webResponse
     * @return true if tenant discovery endpoint is reported. false otherwise.
     * @throws JSONException
     */
    private HashMap<String, String> parseResponse(HttpWebResponse webResponse) throws JSONException {

        return HashMapExtensions.getJsonResponse(webResponse);
    }

    /**
     * service side does not validate tenant, so it is sending common keyword as
     * tenant.
     * 
     * @param authorizationEndpointUrl
     * @return https://hostname/common
     * @throws MalformedURLException
     */
    private String getAuthorizationCommonEndpoint(final URL authorizationEndpointUrl)
            throws MalformedURLException {
        return String.format("https://%s%s", authorizationEndpointUrl.getHost(),
                AUTHORIZATION_COMMON_ENDPOINT);
    }

    /**
     * It will build query url to check the authorization endpoint.
     * 
     * @param instance
     * @param authorizationEndpointUrl
     * @return
     * @throws MalformedURLException
     */
    private URL buildQueryString(final String instance, final String authorizationEndpointUrl)
            throws MalformedURLException {

        Uri.Builder builder = new Uri.Builder();
        builder.scheme("https").authority(instance);
        // replacing tenant to common since instance validation does not check
        // tenant name
        builder.appendEncodedPath(INSTANCE_DISCOVERY_SUFFIX);
        builder.appendQueryParameter(API_VERSION_KEY, API_VERSION_VALUE);
        builder.appendQueryParameter(AUTHORIZATION_ENDPOINT_KEY, authorizationEndpointUrl);
        return new URL(builder.build().toString());
    }

    @Override
    public void setCorrelationId(UUID requestCorrelationId) {
        mCorrelationId = requestCorrelationId;
    }
}




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