/// <summary>
        /// Gets an access token from a <see cref="ClientContext"/> instance. Only works when using an add-in or app-only authentication flow.
        /// </summary>
        /// <param name="clientContext"><see cref="ClientContext"/> instance to obtain an access token for</param>
        /// <returns>Access token for the given <see cref="ClientContext"/> instance</returns>
        public static string GetAccessToken(this ClientRuntimeContext clientContext)
        {
            string accessToken = null;

            if (PnPProvisioningContext.Current != null)
            {
                accessToken = PnPProvisioningContext.Current.AcquireToken(new Uri(clientContext.Url).Authority, null);
            }
            else
            {
                if (clientContext.GetContextSettings().AuthenticationManager != null)
                {
                    var contextSettings = clientContext.GetContextSettings();
                    if (contextSettings.Type == ClientContextType.SharePointACSAppOnly)
                    {
                    }
                    else
                    {
                        accessToken = contextSettings.AuthenticationManager.GetAccessTokenAsync(clientContext.Url).GetAwaiter().GetResult();
                    }
                }
                else
                {
                    EventHandler <WebRequestEventArgs> handler = (s, e) =>
                    {
                        string authorization = e.WebRequestExecutor.RequestHeaders["Authorization"];
                        if (!string.IsNullOrEmpty(authorization))
                        {
                            accessToken = authorization.Replace("Bearer ", string.Empty);
                        }
                    };
                    // Issue a dummy request to get it from the Authorization header
                    clientContext.ExecutingWebRequest += handler;
                    clientContext.ExecuteQuery();
                    clientContext.ExecutingWebRequest -= handler;
                }
            }

            return(accessToken);
        }
        /// <summary>
        /// Clones a ClientContext object while "taking over" the security context of the existing ClientContext instance
        /// </summary>
        /// <param name="clientContext">ClientContext to be cloned</param>
        /// <param name="siteUrl">Site URL to be used for cloned ClientContext</param>
        /// <param name="accessTokens">Dictionary of access tokens for sites URLs</param>
        /// <returns>A ClientContext object created for the passed site URL</returns>
        public static ClientContext Clone(this ClientRuntimeContext clientContext, Uri siteUrl, Dictionary <String, String> accessTokens = null)
        {
            if (siteUrl == null)
            {
                throw new ArgumentException(CoreResources.ClientContextExtensions_Clone_Url_of_the_site_is_required_, nameof(siteUrl));
            }

            ClientContext clonedClientContext = new ClientContext(siteUrl);

            clonedClientContext.AuthenticationMode = clientContext.AuthenticationMode;
            clonedClientContext.ClientTag          = clientContext.ClientTag;
#if !ONPREMISES || SP2016 || SP2019
            clonedClientContext.DisableReturnValueCache = clientContext.DisableReturnValueCache;
#endif


            // In case of using networkcredentials in on premises or SharePointOnlineCredentials in Office 365
            if (clientContext.Credentials != null)
            {
                clonedClientContext.Credentials = clientContext.Credentials;
            }
            else
            {
                // Check if we do have context settings
                var contextSettings = clientContext.GetContextSettings();

                if (contextSettings != null) // We do have more information about this client context, so let's use it to do a more intelligent clone
                {
                    string newSiteUrl = siteUrl.ToString();

                    // A diffent host = different audience ==> new access token is needed
                    if (contextSettings.UsesDifferentAudience(newSiteUrl))
                    {
                        // We need to create a new context using a new authentication manager as the token expiration is handled in there
                        OfficeDevPnP.Core.AuthenticationManager authManager = new OfficeDevPnP.Core.AuthenticationManager();

                        ClientContext newClientContext = null;
                        if (contextSettings.Type == ClientContextType.SharePointACSAppOnly)
                        {
                            newClientContext = authManager.GetAppOnlyAuthenticatedContext(newSiteUrl, TokenHelper.GetRealmFromTargetUrl(new Uri(newSiteUrl)), contextSettings.ClientId, contextSettings.ClientSecret, contextSettings.AcsHostUrl, contextSettings.GlobalEndPointPrefix);
                        }
#if !ONPREMISES && !NETSTANDARD2_0
                        else if (contextSettings.Type == ClientContextType.AzureADCredentials)
                        {
                            newClientContext = authManager.GetAzureADCredentialsContext(newSiteUrl, contextSettings.UserName, contextSettings.Password);
                        }
                        else if (contextSettings.Type == ClientContextType.AzureADCertificate)
                        {
                            newClientContext = authManager.GetAzureADAppOnlyAuthenticatedContext(newSiteUrl, contextSettings.ClientId, contextSettings.Tenant, contextSettings.Certificate, contextSettings.Environment);
                        }
#endif

                        if (newClientContext != null)
                        {
                            //Take over the form digest handling setting
                            newClientContext.FormDigestHandlingEnabled = (clientContext as ClientContext).FormDigestHandlingEnabled;
                            newClientContext.ClientTag = clientContext.ClientTag;
#if !ONPREMISES || SP2016 || SP2019
                            newClientContext.DisableReturnValueCache = clientContext.DisableReturnValueCache;
#endif
                            return(newClientContext);
                        }
                        else
                        {
                            throw new Exception($"Cloning for context setting type {contextSettings.Type} was not yet implemented");
                        }
                    }
                    else
                    {
                        // Take over the context settings, this is needed if we later on want to clone this context to a different audience
                        contextSettings.SiteUrl = newSiteUrl;
                        clonedClientContext.AddContextSettings(contextSettings);

                        clonedClientContext.ExecutingWebRequest += delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
                        {
                            // Call the ExecutingWebRequest delegate method from the original ClientContext object, but pass along the webRequestEventArgs of
                            // the new delegate method
                            MethodInfo methodInfo      = clientContext.GetType().GetMethod("OnExecutingWebRequest", BindingFlags.Instance | BindingFlags.NonPublic);
                            object[]   parametersArray = new object[] { webRequestEventArgs };
                            methodInfo.Invoke(clientContext, parametersArray);
                        };
                    }
                }
                else // Fallback the default cloning logic if there were not context settings available
                {
                    //Take over the form digest handling setting
                    clonedClientContext.FormDigestHandlingEnabled = (clientContext as ClientContext).FormDigestHandlingEnabled;

                    var originalUri = new Uri(clientContext.Url);
                    // If the cloned host is not the same as the original one
                    // and if there is an active PnPProvisioningContext
                    if (originalUri.Host != siteUrl.Host &&
                        PnPProvisioningContext.Current != null)
                    {
                        // Let's apply that specific Access Token
                        clonedClientContext.ExecutingWebRequest += (sender, args) =>
                        {
                            // We get a fresh new Access Token for every request, to avoid using an expired one
                            var accessToken = PnPProvisioningContext.Current.AcquireToken(siteUrl.Authority, null);
                            args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
                        };
                    }
                    // Else if the cloned host is not the same as the original one
                    // and if there is a custom Access Token for it in the input arguments
                    else if (originalUri.Host != siteUrl.Host &&
                             accessTokens != null && accessTokens.Count > 0 &&
                             accessTokens.ContainsKey(siteUrl.Authority))
                    {
                        // Let's apply that specific Access Token
                        clonedClientContext.ExecutingWebRequest += (sender, args) =>
                        {
                            args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessTokens[siteUrl.Authority];
                        };
                    }
                    // Else if the cloned host is not the same as the original one
                    // and if the client context is a PnPClientContext with custom access tokens in its property bag
                    else if (originalUri.Host != siteUrl.Host &&
                             accessTokens == null && clientContext is PnPClientContext &&
                             ((PnPClientContext)clientContext).PropertyBag.ContainsKey("AccessTokens") &&
                             ((Dictionary <string, string>)((PnPClientContext)clientContext).PropertyBag["AccessTokens"]).ContainsKey(siteUrl.Authority))
                    {
                        // Let's apply that specific Access Token
                        clonedClientContext.ExecutingWebRequest += (sender, args) =>
                        {
                            args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + ((Dictionary <string, string>)((PnPClientContext)clientContext).PropertyBag["AccessTokens"])[siteUrl.Authority];
                        };
                    }
                    else
                    {
                        // In case of app only or SAML
                        clonedClientContext.ExecutingWebRequest += (sender, webRequestEventArgs) =>
                        {
                            // Call the ExecutingWebRequest delegate method from the original ClientContext object, but pass along the webRequestEventArgs of
                            // the new delegate method
                            MethodInfo methodInfo      = clientContext.GetType().GetMethod("OnExecutingWebRequest", BindingFlags.Instance | BindingFlags.NonPublic);
                            object[]   parametersArray = new object[] { webRequestEventArgs };
                            methodInfo.Invoke(clientContext, parametersArray);
                        };
                    }
                }
            }

            return(clonedClientContext);
        }
        /// <summary>
        /// Clones a ClientContext object while "taking over" the security context of the existing ClientContext instance
        /// </summary>
        /// <param name="clientContext">ClientContext to be cloned</param>
        /// <param name="targetContext">CientContext stub to be used for cloning</param>
        /// <param name="siteUrl">Site URL to be used for cloned ClientContext</param>
        /// <param name="accessTokens">Dictionary of access tokens for sites URLs</param>
        /// <returns>A ClientContext object created for the passed site URL</returns>
        internal static ClientContext Clone(this ClientRuntimeContext clientContext, ClientContext targetContext, Uri siteUrl, Dictionary <string, string> accessTokens = null)
        {
            if (siteUrl == null)
            {
                throw new ArgumentException(CoreResources.ClientContextExtensions_Clone_Url_of_the_site_is_required_, nameof(siteUrl));
            }

            ClientContext clonedClientContext = targetContext;

            clonedClientContext.ClientTag = clientContext.ClientTag;
            clonedClientContext.DisableReturnValueCache = clientContext.DisableReturnValueCache;


            // Check if we do have context settings
            var contextSettings = clientContext.GetContextSettings();

            if (contextSettings != null) // We do have more information about this client context, so let's use it to do a more intelligent clone
            {
                string newSiteUrl = siteUrl.ToString();

                // A diffent host = different audience ==> new access token is needed
                if (contextSettings.UsesDifferentAudience(newSiteUrl))
                {
                    var           authManager      = contextSettings.AuthenticationManager;
                    ClientContext newClientContext = null;
                    if (contextSettings.Type == ClientContextType.SharePointACSAppOnly)
                    {
                        newClientContext = authManager.GetACSAppOnlyContext(newSiteUrl, contextSettings.ClientId, contextSettings.ClientSecret, contextSettings.Environment);
                    }
                    else
                    {
                        newClientContext = authManager.GetContextAsync(newSiteUrl).GetAwaiter().GetResult();
                    }
                    if (newClientContext != null)
                    {
                        //Take over the form digest handling setting
                        newClientContext.ClientTag = clientContext.ClientTag;
                        newClientContext.DisableReturnValueCache = clientContext.DisableReturnValueCache;
                        return(newClientContext);
                    }
                    else
                    {
                        throw new Exception($"Cloning for context setting type {contextSettings.Type} was not yet implemented");
                    }
                }
                else
                {
                    // Take over the context settings, this is needed if we later on want to clone this context to a different audience
                    contextSettings.SiteUrl = newSiteUrl;
                    clonedClientContext.AddContextSettings(contextSettings);

                    clonedClientContext.ExecutingWebRequest += delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
                    {
                        // Call the ExecutingWebRequest delegate method from the original ClientContext object, but pass along the webRequestEventArgs of
                        // the new delegate method
                        MethodInfo methodInfo      = clientContext.GetType().GetMethod("OnExecutingWebRequest", BindingFlags.Instance | BindingFlags.NonPublic);
                        object[]   parametersArray = new object[] { webRequestEventArgs };
                        methodInfo.Invoke(clientContext, parametersArray);
                    };
                }
            }
            else // Fallback the default cloning logic if there were not context settings available
            {
                //Take over the form digest handling setting

                var originalUri = new Uri(clientContext.Url);
                // If the cloned host is not the same as the original one
                // and if there is an active PnPProvisioningContext
                if (originalUri.Host != siteUrl.Host &&
                    PnPProvisioningContext.Current != null)
                {
                    // Let's apply that specific Access Token
                    clonedClientContext.ExecutingWebRequest += (sender, args) =>
                    {
                        // We get a fresh new Access Token for every request, to avoid using an expired one
                        var accessToken = PnPProvisioningContext.Current.AcquireToken(siteUrl.Authority, null);
                        args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
                    };
                }
                // Else if the cloned host is not the same as the original one
                // and if there is a custom Access Token for it in the input arguments
                else if (originalUri.Host != siteUrl.Host &&
                         accessTokens != null && accessTokens.Count > 0 &&
                         accessTokens.ContainsKey(siteUrl.Authority))
                {
                    // Let's apply that specific Access Token
                    clonedClientContext.ExecutingWebRequest += (sender, args) =>
                    {
                        args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessTokens[siteUrl.Authority];
                    };
                }
                // Else if the cloned host is not the same as the original one
                // and if the client context is a PnPClientContext with custom access tokens in its property bag
                else if (originalUri.Host != siteUrl.Host &&
                         accessTokens == null && clientContext is PnPClientContext &&
                         ((PnPClientContext)clientContext).PropertyBag.ContainsKey("AccessTokens") &&
                         ((Dictionary <string, string>)((PnPClientContext)clientContext).PropertyBag["AccessTokens"]).ContainsKey(siteUrl.Authority))
                {
                    // Let's apply that specific Access Token
                    clonedClientContext.ExecutingWebRequest += (sender, args) =>
                    {
                        args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + ((Dictionary <string, string>)((PnPClientContext)clientContext).PropertyBag["AccessTokens"])[siteUrl.Authority];
                    };
                }
                else
                {
                    // In case of app only or SAML
                    clonedClientContext.ExecutingWebRequest += (sender, webRequestEventArgs) =>
                    {
                        // Call the ExecutingWebRequest delegate method from the original ClientContext object, but pass along the webRequestEventArgs of
                        // the new delegate method
                        MethodInfo methodInfo      = clientContext.GetType().GetMethod("OnExecutingWebRequest", BindingFlags.Instance | BindingFlags.NonPublic);
                        object[]   parametersArray = new object[] { webRequestEventArgs };
                        methodInfo.Invoke(clientContext, parametersArray);
                    };
                }
            }

            return(clonedClientContext);
        }