File: Security\Membership.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="Membership.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.Security {
    using  System.Web;
    using  System.Web.Configuration;
    using  System.Security.Principal;
    using  System.Security.Permissions;
    using  System.Globalization;
    using  System.Runtime.Serialization;
    using  System.Collections;
    using  System.Security.Cryptography;
    using  System.Configuration.Provider;
    using  System.Text;
    using  System.Configuration;
    using  System.Web.Management;
    using  System.Web.Hosting;
    using  System.Threading;
    using  System.Web.Util;
    using  System.Collections.Specialized;
    using System.Web.Compilation;
 
 
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    // This has no hosting permission demands because of DevDiv Bugs 31461: ClientAppSvcs: ASP.net Provider support
    public static class Membership
    {
 
        public static bool   EnablePasswordRetrieval   { get { Initialize(); return Provider.EnablePasswordRetrieval;}}
 
        public static bool   EnablePasswordReset       { get { Initialize(); return Provider.EnablePasswordReset;}}
 
        public static bool   RequiresQuestionAndAnswer   { get { Initialize(); return Provider.RequiresQuestionAndAnswer;}}
 
        public static int    UserIsOnlineTimeWindow      { get { Initialize(); return s_UserIsOnlineTimeWindow; }}
 
 
        public static MembershipProviderCollection    Providers    { get { Initialize(); return s_Providers; }}
 
        public static MembershipProvider Provider {
            get {
                Initialize();
                if (s_Provider == null) {
                    throw new InvalidOperationException(SR.GetString(SR.Def_membership_provider_not_found));
                }
                return s_Provider;
            }
        }
 
        public static string   HashAlgorithmType { get { Initialize(); return s_HashAlgorithmType; }}
        internal static bool   IsHashAlgorithmFromMembershipConfig { get { Initialize(); return s_HashAlgorithmFromConfig; }}
 
        public static int MaxInvalidPasswordAttempts
        {
            get
            {
                Initialize();
 
                return Provider.MaxInvalidPasswordAttempts;
            }
        }
 
        public static int PasswordAttemptWindow
        {
            get
            {
                Initialize();
 
                return Provider.PasswordAttemptWindow;
            }
        }
 
        public static int MinRequiredPasswordLength
        {
            get
            {
                Initialize();
 
                return Provider.MinRequiredPasswordLength;
            }
        }
 
        public static int MinRequiredNonAlphanumericCharacters
        {
            get
            {
                Initialize();
 
                return Provider.MinRequiredNonAlphanumericCharacters;
            }
        }
 
        public static string PasswordStrengthRegularExpression
        {
            get
            {
                Initialize();
 
                return Provider.PasswordStrengthRegularExpression;
            }
        }
 
 
        public static string ApplicationName
        {
            get { return Provider.ApplicationName; }
            set { Provider.ApplicationName = value; }
        }
 
 
        public static MembershipUser CreateUser(string username, string password)
        {
            return CreateUser(username, password, null);
        }
 
 
        public static MembershipUser CreateUser(string username, string password, string email)
        {
            MembershipCreateStatus status;
            MembershipUser u = CreateUser(username, password, email,null,null,true, out status);
            if (u == null)
                throw new MembershipCreateUserException(status);
            return u;
        }
 
 
        public static MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, out MembershipCreateStatus status)
        {
            return CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, null, out status);
        }
 
        public static MembershipUser CreateUser( string username, string password,  string email, string passwordQuestion,string passwordAnswer,
                                                 bool   isApproved, object providerUserKey, out MembershipCreateStatus status )
        {
            if( !SecUtility.ValidateParameter(ref username,  true,  true, true, 0))
            {
                status = MembershipCreateStatus.InvalidUserName;
                return null;
            }
 
            if( !SecUtility.ValidatePasswordParameter(ref password, 0))
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }
 
 
            if( !SecUtility.ValidateParameter( ref email, false, false, false, 0))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }
 
            if( !SecUtility.ValidateParameter(ref passwordQuestion, false, true, false, 0))
            {
                status = MembershipCreateStatus.InvalidQuestion;
                return null;
            }
 
            if( !SecUtility.ValidateParameter(ref passwordAnswer, false, true, false, 0))
            {
                status = MembershipCreateStatus.InvalidAnswer;
                return null;
            }
 
            return Provider.CreateUser( username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status);
        }
 
 
        public static bool ValidateUser(string username, string password)
        {
            return Provider.ValidateUser(username, password);
            /*
            if (retVal) {
                PerfCounters.IncrementCounter(AppPerfCounter.MEMBER_SUCCESS);
                WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditMembershipAuthenticationSuccess, username);
            }
            else {
                PerfCounters.IncrementCounter(AppPerfCounter.MEMBER_FAIL);
                WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditMembershipAuthenticationFailure, username);
            }
 
            return retVal;
             */
        }
 
 
        public static MembershipUser GetUser()
        {
            return GetUser(GetCurrentUserName(), true);
        }
 
 
        public static MembershipUser GetUser(bool userIsOnline)
        {
            return GetUser(GetCurrentUserName(), userIsOnline);
        }
 
 
        public static MembershipUser GetUser(string username)
        {
            return GetUser(username, false);
        }
 
 
        public static MembershipUser GetUser(string username, bool userIsOnline)
        {
            SecUtility.CheckParameter( ref username, true, false, true, 0, "username" );
 
            return Provider.GetUser(username, userIsOnline);
        }
 
        public static MembershipUser GetUser( object providerUserKey )
        {
            return GetUser( providerUserKey, false);
        }
 
        public static MembershipUser GetUser( object providerUserKey, bool userIsOnline )
        {
            if( providerUserKey == null )
            {
                throw new ArgumentNullException( "providerUserKey" );
            }
 
 
            return Provider.GetUser( providerUserKey, userIsOnline);
        }
 
 
        public static string GetUserNameByEmail( string emailToMatch )
        {
            SecUtility.CheckParameter( ref emailToMatch,
                                       false,
                                       false,
                                       false,
                                       0,
                                       "emailToMatch" );
 
            return Provider.GetUserNameByEmail( emailToMatch );
        }
 
 
        public static bool DeleteUser(string username)
        {
            SecUtility.CheckParameter( ref username, true, true, true, 0, "username" );
            return Provider.DeleteUser( username, true );
        }
 
 
        public static bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            SecUtility.CheckParameter( ref username, true, true, true, 0, "username" );
            return Provider.DeleteUser( username, deleteAllRelatedData );
        }
 
 
        public static void UpdateUser( MembershipUser user )
        {
            if( user == null )
            {
                throw new ArgumentNullException( "user" );
            }
 
            user.Update();
        }
 
 
        public static MembershipUserCollection GetAllUsers()
        {
            int totalRecords = 0;
            return GetAllUsers( 0, Int32.MaxValue, out totalRecords);
        }
 
        public static MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            if ( pageIndex < 0 )
            {
                throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex");
            }
 
            if ( pageSize < 1 )
            {
                throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize");
            }
 
            return Provider.GetAllUsers(pageIndex, pageSize, out totalRecords);
        }
 
 
        public static int GetNumberOfUsersOnline() {
            return Provider.GetNumberOfUsersOnline();
        }
 
        private static char [] punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray();
 
 
        public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters) {
            if (length < 1 || length > 128)
            {
                throw new ArgumentException(SR.GetString(SR.Membership_password_length_incorrect));
            }
 
            if( numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0 )
            {
                throw new ArgumentException(SR.GetString(SR.Membership_min_required_non_alphanumeric_characters_incorrect,
                                                         "numberOfNonAlphanumericCharacters"));
            }
 
            string password;
            int    index;
            byte[] buf;
            char[] cBuf;
            int count;
 
            do {
                buf = new byte[length];
                cBuf = new char[length];
                count = 0;
 
                (new RNGCryptoServiceProvider()).GetBytes(buf);
 
                for(int iter=0; iter<length; iter++)
                {
                    int i = (int) (buf[iter] % 87);
                    if (i < 10)
                        cBuf[iter] = (char) ('0' + i);
                    else if (i < 36)
                        cBuf[iter] = (char) ('A' + i - 10);
                    else if (i < 62)
                        cBuf[iter] = (char) ('a' + i - 36);
                    else
                    {
                        cBuf[iter] = punctuations[i-62];
                        count++;
                    }
                }
 
                if( count < numberOfNonAlphanumericCharacters )
                {
                    int j, k;
                    Random rand = new Random();
 
                    for( j = 0; j < numberOfNonAlphanumericCharacters - count; j++ )
                    {
                        do
                        {
                            k = rand.Next( 0, length );
                        }
                        while( !Char.IsLetterOrDigit( cBuf[k] ) );
 
                        cBuf[k] = punctuations[rand.Next(0, punctuations.Length)];
                    }
                }
 
                password = new string(cBuf);
            }
            while(CrossSiteScriptingValidation.IsDangerousString(password, out index));
 
            return password;
        }
 
        private static void Initialize() 
        {
            if (s_Initialized && s_InitializedDefaultProvider) {
                return;
            }
            if (s_InitializeException != null)
                throw s_InitializeException;
 
            if (HostingEnvironment.IsHosted)
                HttpRuntime.CheckAspNetHostingPermission(AspNetHostingPermissionLevel.Low, SR.Feature_not_supported_at_this_level);
 
            lock (s_lock) {
                if (s_Initialized && s_InitializedDefaultProvider) {
                    return;
                }
                if (s_InitializeException != null)
                    throw s_InitializeException;
 
                bool initializeGeneralSettings = !s_Initialized;
                // the default provider can be initialized once the pre start init has happened (i.e. when compilation has begun)
                // or if this is not even a hosted scenario
                bool initializeDefaultProvider = !s_InitializedDefaultProvider &&
                    (!HostingEnvironment.IsHosted || BuildManager.PreStartInitStage == PreStartInitStage.AfterPreStartInit);
 
                if (!initializeDefaultProvider && !initializeGeneralSettings) {
                    return;
                }
 
                bool generalSettingsInitialized;
                bool defaultProviderInitialized = false;
                try {
                    RuntimeConfig appConfig = RuntimeConfig.GetAppConfig();
                    MembershipSection settings = appConfig.Membership;
                    generalSettingsInitialized = InitializeSettings(initializeGeneralSettings, appConfig, settings);
                    defaultProviderInitialized = InitializeDefaultProvider(initializeDefaultProvider, settings);
                    // VSO #265267 log warning in event log when using clear password and encrypted password in Membership provider
                    // VSO #433626 In order to minimize the behavior change, we are going to read the password format from the config settings only instead of getting from the provider class
                    // Also allow user to opt-out this feature.
                    if (AppSettings.LogMembershipPasswordFormatWarning) {
                        CheckedPasswordFormat(settings);
                    }
                } catch (Exception e) {
                    s_InitializeException = e;
                    throw;
                }
 
                // update this state only after the whole method completes to preserve the behavior where
                // the system is uninitialized if any exceptions were thrown.
                if (generalSettingsInitialized) {
                    s_Initialized = true;
                }
                if (defaultProviderInitialized) {
                    s_InitializedDefaultProvider = true;
                }
            }
        }
 
        // VSO #265267 we want to log a warning in the event log, whenever detect using clear password or encrypted password formats settings in Membership provider
        // VSO #433626 In order to minimize the behavior change, we are going to read the password format from the config settings only instead of getting from the provider class
        private static void CheckedPasswordFormat(MembershipSection settings) {
            //VSO #294931 Since this is an optional feature, we want to prevent any corner cases that were not able to return the password format. In those cases, we will just do nothing and not log any warnings.
            try {
                if (settings != null && settings.Providers != null) {
                    foreach (ProviderSettings ps in settings.Providers) {
                        if (ps != null && ps.Parameters != null) {
                           string passwordFormat = ps.Parameters["passwordFormat"];
                            if(StringUtil.EqualsIgnoreCase(passwordFormat, "Clear") || StringUtil.EqualsIgnoreCase(passwordFormat, "Encrypted")) {
                                string providerName = ps.Name ?? string.Empty;
                                WebBaseEvent.RaiseRuntimeError(new ConfigurationErrorsException(SR.GetString(SR.MembershipPasswordFormat_Obsoleted, providerName, passwordFormat)), typeof(MembershipProvider));
                            }
                        }
                    }
                }
            }
            catch { }
        }
 
        private static bool InitializeSettings(bool initializeGeneralSettings, RuntimeConfig appConfig, MembershipSection settings) {
            if (!initializeGeneralSettings) {
                return false;
            }
 
            s_HashAlgorithmType = settings.HashAlgorithmType;
            s_HashAlgorithmFromConfig = !string.IsNullOrEmpty(s_HashAlgorithmType);
            if (!s_HashAlgorithmFromConfig) {
                // If no hash algorithm is specified, use the same as the "validation" in "<machineKey>".
                // If the validation is "3DES", switch it to use "SHA1" instead.
                MachineKeyValidation v = appConfig.MachineKey.Validation;
                if (v != MachineKeyValidation.AES && v != MachineKeyValidation.TripleDES)
                    s_HashAlgorithmType = appConfig.MachineKey.ValidationAlgorithm;
                else
                    s_HashAlgorithmType = "SHA1";
            }
            s_Providers = new MembershipProviderCollection();
            if (HostingEnvironment.IsHosted) {
                ProvidersHelper.InstantiateProviders(settings.Providers, s_Providers, typeof(MembershipProvider));
            } else {
                foreach (ProviderSettings ps in settings.Providers) {
                    Type t = Type.GetType(ps.Type, true, true);
                    if (!typeof(MembershipProvider).IsAssignableFrom(t))
                        throw new ArgumentException(SR.GetString(SR.Provider_must_implement_type, typeof(MembershipProvider).ToString()));
                    MembershipProvider provider = (MembershipProvider)Activator.CreateInstance(t);
                    NameValueCollection pars = ps.Parameters;
                    NameValueCollection cloneParams = new NameValueCollection(pars.Count, StringComparer.Ordinal);
                    foreach (string key in pars)
                        cloneParams[key] = pars[key];
                    provider.Initialize(ps.Name, cloneParams);
                    s_Providers.Add(provider);
                }
            }
 
            TimeSpan timeWindow = settings.UserIsOnlineTimeWindow;
            s_UserIsOnlineTimeWindow = (int)timeWindow.TotalMinutes;
 
            return true;
        }
 
        private static bool InitializeDefaultProvider(bool initializeDefaultProvider, MembershipSection settings) {
            if (!initializeDefaultProvider) {
                return false;
            }
 
            s_Providers.SetReadOnly();
 
            if (settings.DefaultProvider == null || s_Providers.Count < 1)
                throw new ProviderException(SR.GetString(SR.Def_membership_provider_not_specified));
 
            s_Provider = s_Providers[settings.DefaultProvider];
            if (s_Provider == null) {
                throw new ConfigurationErrorsException(SR.GetString(SR.Def_membership_provider_not_found), settings.ElementInformation.Properties["defaultProvider"].Source, settings.ElementInformation.Properties["defaultProvider"].LineNumber);
            }
 
            return true;
        }
 
        public static MembershipUserCollection FindUsersByName( string usernameToMatch,
                                                                int pageIndex,
                                                                int pageSize,
                                                                out int totalRecords )
        {
            SecUtility.CheckParameter( ref usernameToMatch,
                                       true,
                                       true,
                                       false,
                                       0,
                                       "usernameToMatch" );
 
            if ( pageIndex < 0 )
            {
                throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex");
            }
 
            if ( pageSize < 1 )
            {
                throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize");
            }
 
            return Provider.FindUsersByName( usernameToMatch,
                                             pageIndex,
                                             pageSize,
                                             out totalRecords);
        }
 
 
        public static MembershipUserCollection FindUsersByName( string usernameToMatch )
        {
            SecUtility.CheckParameter( ref usernameToMatch,
                                       true,
                                       true,
                                       false,
                                       0,
                                       "usernameToMatch" );
 
            int totalRecords = 0;
            return Provider.FindUsersByName( usernameToMatch,
                                             0,
                                             Int32.MaxValue,
                                             out totalRecords );
        }
 
        public static MembershipUserCollection FindUsersByEmail( string  emailToMatch,
                                                                 int     pageIndex,
                                                                 int     pageSize,
                                                                 out int totalRecords )
        {
            SecUtility.CheckParameter( ref emailToMatch,
                                       false,
                                       false,
                                       false,
                                       0,
                                       "emailToMatch" );
 
            if ( pageIndex < 0 )
            {
                throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex");
            }
 
            if ( pageSize < 1 )
            {
                throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize");
            }
 
            return Provider.FindUsersByEmail( emailToMatch,
                                              pageIndex,
                                              pageSize,
                                              out totalRecords );
        }
 
        public static MembershipUserCollection FindUsersByEmail(string emailToMatch)
        {
            SecUtility.CheckParameter( ref emailToMatch,
                                       false,
                                       false,
                                       false,
                                       0,
                                       "emailToMatch" );
 
            int totalRecords = 0;
            return FindUsersByEmail(emailToMatch, 0, Int32.MaxValue, out totalRecords);
        }
 
        private static string GetCurrentUserName()
        {
            if (HostingEnvironment.IsHosted) {
                HttpContext cur = HttpContext.Current;
                if (cur != null)
                    return cur.User.Identity.Name;
            }
            IPrincipal user = Thread.CurrentPrincipal;
            if (user == null || user.Identity == null)
                return String.Empty;
            else
                return user.Identity.Name;
        }
 
        public static event MembershipValidatePasswordEventHandler ValidatingPassword
        {
            add
            {
                Provider.ValidatingPassword += value;
            }
            remove
            {
                Provider.ValidatingPassword -= value;
            }
        }
 
        private static MembershipProviderCollection   s_Providers;
        private static MembershipProvider             s_Provider;
        private static int                            s_UserIsOnlineTimeWindow = 15;
        private static object                         s_lock = new object();
        private static bool                           s_Initialized = false;
        private static bool                           s_InitializedDefaultProvider;
        private static Exception                      s_InitializeException = null;
        private static string                         s_HashAlgorithmType;
        private static bool                           s_HashAlgorithmFromConfig;
 
    }
}