File: net\System\Net\AuthenticationManager.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <copyright file="AuthenticationManager.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Net {
 
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.Globalization;
    using System.Net.Configuration;
    using System.Reflection;
    using System.Security.Authentication.ExtendedProtection;
    using System.Security.Permissions;
    using System;
    using System.Threading;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
 
    //
    // A contract that applications can use to restrict auth scenarios in current appDomain
    //
    public interface ICredentialPolicy
    {
        bool ShouldSendCredential(
            Uri challengeUri, 
            WebRequest request, 
            NetworkCredential credential, 
            IAuthenticationModule authenticationModule);
    }
 
    /// <devdoc>
    ///    <para>Manages the authentication modules called during the client authentication
    ///       process.</para>
    /// </devdoc>
    public class AuthenticationManager 
    {
        private static object instanceLock = new object();
        private static IAuthenticationManager internalInstance = null;
        internal const string authenticationManagerRoot = "System.Net.AuthenticationManager";
        
        // Following names are used both as a per-app key as a global setting
        internal const string configHighPerformance = authenticationManagerRoot + ".HighPerformance";
        internal const string configPrefixLookupMaxCount = authenticationManagerRoot + ".PrefixLookupMaxCount";
        
        private AuthenticationManager()
        {
        }
 
        private static IAuthenticationManager Instance 
        {
            get
            {
                if (internalInstance == null)
                {
                    lock (instanceLock)
                    {
                        if (internalInstance == null)
                        {
                            internalInstance = SelectAuthenticationManagerInstance();
                        }
                    }
                }
 
                return internalInstance;
            }
        }
 
        private static IAuthenticationManager SelectAuthenticationManagerInstance()
        {
            bool highPerformance = false;
 
            try
            {
                if (RegistryConfiguration.GlobalConfigReadInt(configHighPerformance, 0) == 1)
                {
                    highPerformance = true;
                }
                else if (RegistryConfiguration.AppConfigReadInt(configHighPerformance, 0) == 1)
                {
                    highPerformance = true;
                }
            
                if (highPerformance)
                {
                    int? maxPrefixLookupEntries = ReadPrefixLookupMaxEntriesConfig();
                    if ((maxPrefixLookupEntries != null) && (maxPrefixLookupEntries > 0))
                    {
                        return new AuthenticationManager2((int)maxPrefixLookupEntries);
                    }
                    else
                    {
                        return new AuthenticationManager2();
                    }
                }
            }
            catch (Exception e)
            {
                if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
                {
                    throw;
                }
            }
            
            return new AuthenticationManagerDefault();
        }
 
        private static int? ReadPrefixLookupMaxEntriesConfig()
        {
            int? maxPrefixLookupEntries = null;
 
            int configuredMaxPrefixLookupEntries =
                RegistryConfiguration.GlobalConfigReadInt(configPrefixLookupMaxCount, -1);
 
            if (configuredMaxPrefixLookupEntries > 0)
            {
                maxPrefixLookupEntries = configuredMaxPrefixLookupEntries;
            }
 
            // Per-process setting will override global configuration.
            configuredMaxPrefixLookupEntries =
                RegistryConfiguration.AppConfigReadInt(configPrefixLookupMaxCount, -1);
 
            if (configuredMaxPrefixLookupEntries > 0)
            {
                maxPrefixLookupEntries = configuredMaxPrefixLookupEntries;
            }
            return maxPrefixLookupEntries;
        }
                
        public static ICredentialPolicy CredentialPolicy {
            get 
            {
                return Instance.CredentialPolicy; 
            }
 
            set 
            {
                ExceptionHelper.ControlPolicyPermission.Demand();
                Instance.CredentialPolicy = value;
            }
        }
 
        public static StringDictionary CustomTargetNameDictionary 
        {
            get 
            {
                return Instance.CustomTargetNameDictionary;  
            }
        }
 
        internal static SpnDictionary SpnDictionary 
        {
            get 
            {
                return Instance.SpnDictionary;
            }
        }
 
        internal static void EnsureConfigLoaded() 
        {
            Instance.EnsureConfigLoaded();          
        }
 
        internal static bool OSSupportsExtendedProtection 
        {
            get
            {
                return Instance.OSSupportsExtendedProtection;
            }
        }
 
        internal static bool SspSupportsExtendedProtection 
        {
            get 
            {
                return Instance.SspSupportsExtendedProtection;
            }
        }
 
        /// <devdoc>
        ///    <para>Call each registered authentication module to determine the first module that
        ///       can respond to the authentication request.</para>
        /// </devdoc>
        public static Authorization Authenticate(string challenge, WebRequest request, ICredentials credentials) 
        {
            return Instance.Authenticate(challenge, request, credentials);
        }
 
        /// <devdoc>
        ///    <para>Pre-authenticates a request.</para>
        /// </devdoc>
        public static Authorization PreAuthenticate(WebRequest request, ICredentials credentials) 
        {
            return Instance.PreAuthenticate(request, credentials);
        }
 
        /// <devdoc>
        ///    <para>Registers an authentication module with the authentication manager.</para>
        /// </devdoc>
        public static void Register(IAuthenticationModule authenticationModule) 
        {
            ExceptionHelper.UnmanagedPermission.Demand();
            Instance.Register(authenticationModule);
        }
 
        /// <devdoc>
        ///    <para>Unregisters authentication modules for an authentication scheme.</para>
        /// </devdoc>
        public static void Unregister(IAuthenticationModule authenticationModule) 
        {
            ExceptionHelper.UnmanagedPermission.Demand();
            Instance.Unregister(authenticationModule);
        }
 
        /// <devdoc>
        ///    <para>Unregisters authentication modules for an authentication scheme.</para>
        /// </devdoc>
        public static void Unregister(string authenticationScheme) 
        {
            ExceptionHelper.UnmanagedPermission.Demand();
            Instance.Unregister(authenticationScheme);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Returns a list of registered authentication modules.
        ///    </para>
        /// </devdoc>
        public static IEnumerator RegisteredModules 
        {
            get 
            {
                return Instance.RegisteredModules;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Binds an authentication response to a request for pre-authentication.
        ///    </para>
        /// </devdoc>
        // Create binding between an authorization response and the module
        // generating that response
        // This association is used for deciding which module to invoke
        // for preauthentication purposes
        internal static void BindModule(Uri uri, Authorization response, IAuthenticationModule module) 
        {
            Instance.BindModule(uri, response, module);
        }
 
        //
        // The method will extract the blob that does correspond to the moduled with the name passed in signature 
        // parameter. The method avoids confusion arisen from the parameters passed in a quoted string, such as:
        // WWW-Authenticate: Digest username="NTLM", realm="wit", NTLM ...
        //
        [SuppressMessage(
            "Microsoft.Globalization", "CA1308", Justification = "Assert-only by check for lower-case signature")]
        internal static int FindSubstringNotInQuotes(string challenge, string signature) 
        {
            int index = -1;
            Debug.Assert(signature.ToLowerInvariant().Equals(signature, StringComparison.Ordinal),
                "'signature' parameter must be lower case");
            if (challenge != null && signature != null && challenge.Length >= signature.Length)
            {
                int firstQuote = -1, secondQuote = -1;
                for (int i = 0; i < challenge.Length && index < 0; i++)
                {
                    // Search for the quotes
                    if (challenge[i] == '\"')
                    {
                        if (firstQuote <= secondQuote)
                            firstQuote = i;
                        else
                            secondQuote = i;
                    }
                    // We've found both ends of an unquoted segment (could be whole challenge), search inside for the signature.
                    if (i == challenge.Length - 1 || (challenge[i] == '\"' && firstQuote > secondQuote))
                    {
                        // see if the portion of challenge out of the quotes contains
                        // the signature of the IAuthenticationModule
                        if (i == challenge.Length - 1)
                            firstQuote = challenge.Length;
                        // unquoted segment is too small to hold a scheme name, ie: scheme param="value",a=""
                        if (firstQuote < secondQuote + 3)
                            continue;
 
                        int checkstart = secondQuote + 1;
                        int checkLength = firstQuote - secondQuote - 1;
                        do
                        {
                            // Search for the next (partial match) occurance of the signature
                            index = IndexOf(challenge, signature, checkstart, checkLength);
 
                            if (index >= 0)
                            {
                                // Verify the signature is a full scheme name match, not a partial match or a parameter name:
                                if ((index == 0 || challenge[index - 1] == ' ' || challenge[index - 1] == ',') &&
                                    (index + signature.Length == challenge.Length || challenge[index + signature.Length] == ' ' || challenge[index + signature.Length] == ','))
                                {
                                    break;
                                }
                                // Only a partial match / param name, but maybe there is another occurance of the signature later?
                                checkLength -= index - checkstart + 1;
                                checkstart = index + 1;
                            }
                        } while (index >= 0);
                    }
                }
            }
            GlobalLog.Print("AuthenticationManager::FindSubstringNotInQuotes(" + challenge + ", " + signature + ")=" + index.ToString());
            return index;
        }
 
        //
        // Helper for FindSubstringNotInQuotes
        // Find the FIRST possible index of a signature.
        private static int IndexOf(string challenge, string lwrCaseSignature, int start, int count)
        {
            count += start + 1 - lwrCaseSignature.Length;
            for (; start < count; ++start)
            {
                int i = 0;
                for (; i < lwrCaseSignature.Length; ++i)
                {
                    // force a challenge char to lowecase (safe assuming it works on trusted ASCII source)
                    if ((challenge[start + i] | 0x20) != lwrCaseSignature[i])
                        break;
                }
                if (i == lwrCaseSignature.Length)
                    return start;
            }
            return -1;
        }
 
        //
        // this method is called by the IAuthenticationModule implementations
        // (mainly Digest) to safely find their list of parameters in a challenge.
        // it returns the index of the first ',' that is not included in quotes,
        // -1 is returned on error or end of string. on return offset contains the
        // index of the first '=' that is not included in quotes, -1 if no '=' was found.
        //
        internal static int SplitNoQuotes(string challenge, ref int offset) 
        {
            // GlobalLog.Print("SplitNoQuotes([" + challenge + "], " + offset.ToString() + ")");
            //
            // save offset
            //
            int realOffset = offset;
            //
            // default is not found
            //
            offset = -1;
 
            if (challenge != null && realOffset < challenge.Length)
            {
                int firstQuote = -1, secondQuote = -1;
 
                for (int i = realOffset; i < challenge.Length; i++)
                {
                    //
                    // firstQuote>secondQuote means we are in a quoted string
                    //
                    if (firstQuote > secondQuote && challenge[i] == '\\' && i + 1 < challenge.Length && challenge[i + 1] == '\"')
                    {
                        //
                        // skip <\"> when in a quoted string
                        //
                        i++;
                    }
                    else if (challenge[i] == '\"')
                    {
                        if (firstQuote <= secondQuote)
                        {
                            firstQuote = i;
                        }
                        else
                        {
                            secondQuote = i;
                        }
                    }
                    else if (challenge[i] == '=' && firstQuote <= secondQuote && offset < 0)
                    {
                        offset = i;
                    }
                    else if (challenge[i] == ',' && firstQuote <= secondQuote)
                    {
                        return i;
                    }
                }
            }
 
            return -1;
        }
 
#if !FEATURE_PAL
        internal static Authorization GetGroupAuthorization(
            IAuthenticationModule thisModule, 
            string token, 
            bool finished, 
            NTAuthentication authSession, 
            bool shareAuthenticatedConnections, 
            bool mutualAuth) 
        {
            return new Authorization(
                    token,
                    finished,
                    (shareAuthenticatedConnections) ? null 
                        : (thisModule.GetType().FullName + "/" + authSession.UniqueUserId),
                    mutualAuth);
        }
#endif // !FEATURE_PAL
 
    } // class AuthenticationManager
 
} // namespace System.Net