File: Security\FormsAuthentication.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="FormsAuthentication.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
/*
 * FormsAuthentication class
 *
 * Copyright (c) 1999 Microsoft Corporation
 */
 
namespace System.Web.Security {
    using System;
    using System.Web;
    using System.Text;
    using System.Web.Configuration;
    using System.Web.Caching;
    using System.Collections;
    using System.Web.Util;
    using System.Security.Cryptography;
    using System.Security.Principal;
    using System.Threading;
    using System.Globalization;
    using System.Security.Permissions;
    using System.Web.Management;
    using System.Collections.Specialized;
    using System.Web.Compilation;
    using System.Web.Security.Cryptography;
 
 
 
    /// <devdoc>
    ///    This class consists of static methods that
    ///    provides helper utilities for manipulating authentication tickets.
    /// </devdoc>
    public sealed class FormsAuthentication {
        private const int MAX_TICKET_LENGTH = 4096;
        private static object _lockObject = new object();
 
        public FormsAuthentication() { }
 
		/////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Helper functions: Hash a password
 
        /// <devdoc>
        ///    Initializes FormsAuthentication by reading
        ///    configuration and getting the cookie values and encryption keys for the given
        ///    application.
        /// </devdoc>
        [Obsolete("The recommended alternative is to use the Membership APIs, such as Membership.CreateUser. For more information, see http://go.microsoft.com/fwlink/?LinkId=252463.")]
        public static String HashPasswordForStoringInConfigFile(String password, String passwordFormat) {
            if (password == null) {
                throw new ArgumentNullException("password");
            }
            if (passwordFormat == null) {
                throw new ArgumentNullException("passwordFormat");
            }
            HashAlgorithm hashAlgorithm;
            if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha1"))
                hashAlgorithm = CryptoAlgorithms.CreateSHA1();
            else if (StringUtil.EqualsIgnoreCase(passwordFormat, "md5"))
                hashAlgorithm = CryptoAlgorithms.CreateMD5();
            else if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha256"))
                hashAlgorithm = CryptoAlgorithms.CreateSHA256();
            else if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha384"))
                hashAlgorithm = CryptoAlgorithms.CreateSHA384();
            else if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha512"))
                hashAlgorithm = CryptoAlgorithms.CreateSHA512();
            else
                throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "passwordFormat"));
 
            using (hashAlgorithm) {
                return CryptoUtil.BinaryToHex(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(password)));
            }
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Initialize this
 
        /// <devdoc>
        ///    Initializes FormsAuthentication by reading
        ///    configuration and getting the cookie values and encryption keys for the given
        ///    application.
        /// </devdoc>
        public static void Initialize() {
            if (_Initialized)
                return;
 
            lock(_lockObject) {
                if (_Initialized)
                    return;
 
                AuthenticationSection settings = RuntimeConfig.GetAppConfig().Authentication;
                settings.ValidateAuthenticationMode();
                _FormsName = settings.Forms.Name;
                _RequireSSL = settings.Forms.RequireSSL;
                _SlidingExpiration = settings.Forms.SlidingExpiration;
                if (_FormsName == null)
                    _FormsName = CONFIG_DEFAULT_COOKIE;
 
                _Protection = settings.Forms.Protection;
                _Timeout = (int) settings.Forms.Timeout.TotalMinutes;
                _FormsCookiePath = settings.Forms.Path;
                _LoginUrl = settings.Forms.LoginUrl;
                if (_LoginUrl == null)
                    _LoginUrl = "login.aspx";
                _DefaultUrl = settings.Forms.DefaultUrl;
                if (_DefaultUrl == null)
                    _DefaultUrl = "default.aspx";
                _CookieMode = settings.Forms.Cookieless;
                _CookieDomain = settings.Forms.Domain;
                _EnableCrossAppRedirects = settings.Forms.EnableCrossAppRedirects;
                _TicketCompatibilityMode = settings.Forms.TicketCompatibilityMode;
                _cookieSameSite = settings.Forms.CookieSameSite;
 
                _Initialized = true;
            }
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Decrypt and get the auth ticket
 
        /// <devdoc>
        ///    <para>Given an encrypted authenitcation ticket as
        ///       obtained from an HTTP cookie, this method returns an instance of a
        ///       FormsAuthenticationTicket class.</para>
        /// </devdoc>
        public static FormsAuthenticationTicket Decrypt(string encryptedTicket) {
            if (String.IsNullOrEmpty(encryptedTicket) || encryptedTicket.Length > MAX_TICKET_LENGTH)
                throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "encryptedTicket"));
 
            Initialize();
            byte[] bBlob = null;
            if ((encryptedTicket.Length % 2) == 0) { // Could be a hex string
                try {
                    bBlob = CryptoUtil.HexToBinary(encryptedTicket);
                } catch { }
            }
            if (bBlob == null)
                bBlob = HttpServerUtility.UrlTokenDecode(encryptedTicket);
            if (bBlob == null || bBlob.Length < 1)
                throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "encryptedTicket"));
 
            int ticketLength;
 
            if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider) {
                // If new crypto routines are enabled, call them instead.
                ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(Purpose.FormsAuthentication_Ticket);
                byte[] unprotectedData = cryptoService.Unprotect(bBlob);
                ticketLength = unprotectedData.Length;
                bBlob = unprotectedData;
            } else {
#pragma warning disable 618 // calling obsolete methods
                // Otherwise call into MachineKeySection routines.
 
                if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Encryption)
                {
                    // DevDiv Bugs 137864: Include a random IV if under the right compat mode
                    // for improved encryption semantics
                    bBlob = MachineKeySection.EncryptOrDecryptData(false, bBlob, null, 0, bBlob.Length, false, false, IVType.Random);
                    if (bBlob == null)
                        return null;
                }
 
                ticketLength = bBlob.Length;
 
                if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Validation)
                {
                    if (!MachineKeySection.VerifyHashedData(bBlob))
                        return null;
                    ticketLength -= MachineKeySection.HashSize;
                }
#pragma warning restore 618 // calling obsolete methods
            }
 
            //////////////////////////////////////////////////////////////////////
            // Step 4: Change binary ticket to managed struct
 
            // ** MSRC 11838 **
            // Framework20 / Framework40 ticket generation modes are insecure. We should use a
            // secure serialization mode by default.
            if (!AppSettings.UseLegacyFormsAuthenticationTicketCompatibility) {
                return FormsAuthenticationTicketSerializer.Deserialize(bBlob, ticketLength);
            }
 
            // ** MSRC 11838 **
            // If we have reached this point of execution, the developer has explicitly elected
            // to continue using the insecure code path instead of the secure one. We removed
            // the Framework40 serialization mode, so everybody using the legacy code path is
            // forced to Framework20.
 
            int iSize = ((ticketLength > MAX_TICKET_LENGTH) ? MAX_TICKET_LENGTH : ticketLength);
            StringBuilder     name = new StringBuilder(iSize);
            StringBuilder     data = new StringBuilder(iSize);
            StringBuilder     path = new StringBuilder(iSize);
            byte []           pBin = new byte[4];
            long []           pDates = new long[2];
 
            int iRet = UnsafeNativeMethods.CookieAuthParseTicket(bBlob, ticketLength,
                                                                   name, iSize,
                                                                   data, iSize,
                                                                   path, iSize,
                                                                   pBin, pDates);
 
            if (iRet != 0)
                return null;
 
            DateTime dt1 = DateTime.FromFileTime(pDates[0]);
            DateTime dt2 = DateTime.FromFileTime(pDates[1]);
 
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket((int) pBin[0],
                                                     name.ToString(),
                                                     dt1,
                                                     dt2,
                                                     (bool) (pBin[1] != 0),
                                                     data.ToString(),
                                                     path.ToString());
            return ticket;
        }
 
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Encrypt a ticket
 
        /// <devdoc>
        ///    Given a FormsAuthenticationTicket, this
        ///    method produces a string containing an encrypted authentication ticket suitable
        ///    for use in an HTTP cookie.
        /// </devdoc>
        public static String Encrypt(FormsAuthenticationTicket ticket) {
            return Encrypt(ticket, true);
        }
        internal static String Encrypt(FormsAuthenticationTicket ticket, bool hexEncodedTicket) {
            if (ticket == null)
                throw new ArgumentNullException("ticket");
 
            Initialize();
            //////////////////////////////////////////////////////////////////////
            // Step 1a: Make it into a binary blob
            byte[] bBlob = MakeTicketIntoBinaryBlob(ticket);
            if (bBlob == null)
                return null;
 
            //////////////////////////////////////////////////////////////////////
            // Step 1b: If new crypto routines are enabled, call them instead.
            if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider) {
                ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(Purpose.FormsAuthentication_Ticket);
                byte[] protectedData = cryptoService.Protect(bBlob);
                bBlob = protectedData;
            }
            else {
#pragma warning disable 618 // calling obsolete methods
                // otherwise..
 
                //////////////////////////////////////////////////////////////////////
                // Step 2: Get the MAC and add to the blob
                if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Validation) {
                    byte[] bMac = MachineKeySection.HashData(bBlob, null, 0, bBlob.Length);
                    if (bMac == null)
                        return null;
                    byte[] bAll = new byte[bMac.Length + bBlob.Length];
                    Buffer.BlockCopy(bBlob, 0, bAll, 0, bBlob.Length);
                    Buffer.BlockCopy(bMac, 0, bAll, bBlob.Length, bMac.Length);
                    bBlob = bAll;
                }
 
                if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Encryption) {
                    //////////////////////////////////////////////////////////////////////
                    // Step 3: Do the actual encryption
                    // DevDiv Bugs 137864: Include a random IV if under the right compat mode
                    // for improved encryption semantics
                    bBlob = MachineKeySection.EncryptOrDecryptData(true, bBlob, null, 0, bBlob.Length, false, false, IVType.Random);
                }
#pragma warning restore 618 // calling obsolete methods
            }
 
            if (!hexEncodedTicket)
                return HttpServerUtility.UrlTokenEncode(bBlob);
            else
                return CryptoUtil.BinaryToHex(bBlob);
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Verify User name and Password
 
        /// <devdoc>
        ///    Given the supplied credentials, this method
        ///    attempts to validate the credentials against those contained in the configured
        ///    credential store.
        /// </devdoc>
        [Obsolete("The recommended alternative is to use the Membership APIs, such as Membership.ValidateUser. For more information, see http://go.microsoft.com/fwlink/?LinkId=252463.")]
        public static bool Authenticate(String name, String password) {
            bool retVal = InternalAuthenticate(name, password);
 
            if (retVal) {
                PerfCounters.IncrementCounter(AppPerfCounter.FORMS_AUTH_SUCCESS);
                WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditFormsAuthenticationSuccess, name);
            }
            else {
                PerfCounters.IncrementCounter(AppPerfCounter.FORMS_AUTH_FAIL);
                WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditFormsAuthenticationFailure, name);
            }
 
            return retVal;
        }
 
        private static bool InternalAuthenticate(String name, String password) {
            //////////////////////////////////////////////////////////////////////
            // Step 1: Make sure we are initialized
            if (name == null || password == null)
                return false;
 
            Initialize();
            //////////////////////////////////////////////////////////////////////
            // Step 2: Get the user database
            AuthenticationSection settings = RuntimeConfig.GetAppConfig().Authentication;
            settings.ValidateAuthenticationMode();
            FormsAuthenticationUserCollection Users = settings.Forms.Credentials.Users;
 
//            Hashtable hTable = settings.Credentials;
 
            if (Users == null) {
                return false;
            }
 
            //////////////////////////////////////////////////////////////////////
            // Step 3: Get the (hashed) password for this user
            FormsAuthenticationUser u = Users[name.ToLower(CultureInfo.InvariantCulture)];
            if (u == null)
                return false;
 
            String pass = (String)u.Password;
 
            if (pass == null) {
                return false;
            }
 
            //////////////////////////////////////////////////////////////////////
            // Step 4: Hash the given password
            String   encPassword;
 
#pragma warning disable 618 // HashPasswordForStorignInConfigFile is now obsolete
            switch (settings.Forms.Credentials.PasswordFormat)
            {
                case FormsAuthPasswordFormat.SHA256:
                    encPassword = HashPasswordForStoringInConfigFile(password, "sha256");
                    break;
                case FormsAuthPasswordFormat.SHA384:
                    encPassword = HashPasswordForStoringInConfigFile(password, "sha384");
                    break;
                case FormsAuthPasswordFormat.SHA512:
                    encPassword = HashPasswordForStoringInConfigFile(password, "sha512");
                    break;
                case FormsAuthPasswordFormat.SHA1:
                    encPassword = HashPasswordForStoringInConfigFile(password, "sha1");
                    break;
 
                case FormsAuthPasswordFormat.MD5:
                    encPassword = HashPasswordForStoringInConfigFile(password, "md5");
                    break;
 
                case FormsAuthPasswordFormat.Clear:
                    encPassword = password;
                    break;
 
                default:
                    return false;
            }
#pragma warning restore 618
 
            //////////////////////////////////////////////////////////////////////
            // Step 5: Compare the hashes
            return(String.Compare(encPassword,
                                  pass,
                                  ((settings.Forms.Credentials.PasswordFormat != FormsAuthPasswordFormat.Clear)
                                        ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
                   == 0);
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
 
        /// <devdoc>
        ///    Given an authenticated user, calling SignOut
        ///    removes the authentication ticket by doing a SetForms with an empty value. This
        ///    removes either durable or session cookies.
        /// </devdoc>
        public static void SignOut() {
            Initialize();
 
            HttpContext    context    = HttpContext.Current;
            bool           needToRedirect  = context.CookielessHelper.DoesCookieValueExistInOriginal('F');
 
            context.CookielessHelper.SetCookieValue('F', null); // Always clear the uri-cookie
 
            if (!CookielessHelperClass.UseCookieless(context, false, CookieMode) || context.Request.Browser.Cookies)
            { // clear cookie if required
                string cookieValue = String.Empty;
                if (context.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
                    cookieValue = "NoCookie";
                HttpCookie cookie = new HttpCookie(FormsCookieName, cookieValue);
                cookie.HttpOnly = true;
                cookie.Path = _FormsCookiePath;
                cookie.Expires = new System.DateTime(1999, 10, 12);
                cookie.Secure = _RequireSSL;
                if (_CookieDomain != null)
                    cookie.Domain = _CookieDomain;
                cookie.SameSite = _cookieSameSite;
                context.Response.Cookies.RemoveCookie(FormsCookieName);
                context.Response.Cookies.Add(cookie);
            }
            if (needToRedirect)
                context.Response.Redirect(GetLoginPage(null), false);
        }
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
 
        /// <devdoc>
        ///    This method creates an authentication ticket
        ///    for the given userName and attaches it to the cookies collection of the outgoing
        ///    response. It does not perform a redirect.
        /// </devdoc>
        public static void SetAuthCookie(String userName, bool createPersistentCookie) {
            Initialize();
            SetAuthCookie(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
        }
 
        /// <devdoc>
        ///    This method creates an authentication ticket
        ///    for the given userName and attaches it to the cookies collection of the outgoing
        ///    response. It does not perform a redirect.
        /// </devdoc>
        public static void SetAuthCookie(String userName, bool createPersistentCookie, String strCookiePath) {
            Initialize();
            HttpContext context = HttpContext.Current;
 
            if (!context.Request.IsSecureConnection && RequireSSL)
                throw new HttpException(SR.GetString(SR.Connection_not_secure_creating_secure_cookie));
            bool        cookieless  = CookielessHelperClass.UseCookieless(context, false, CookieMode);
            HttpCookie  cookie      = GetAuthCookie(userName, createPersistentCookie, cookieless ? "/" : strCookiePath, !cookieless);
 
            if (!cookieless) {
                HttpContext.Current.Response.Cookies.Add(cookie);
                context.CookielessHelper.SetCookieValue('F', null);
            }
            else {
                context.CookielessHelper.SetCookieValue('F', cookie.Value);
            }
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
 
        /// <devdoc>
        ///    Creates an authentication cookie for a given
        ///    user name. This does not set the cookie as part of the outgoing response, so
        ///    that an application can have more control over how the cookie is issued.
        /// </devdoc>
        public static HttpCookie GetAuthCookie(String userName, bool createPersistentCookie) {
            Initialize();
            return GetAuthCookie(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
        }
 
        public static HttpCookie GetAuthCookie(String userName, bool createPersistentCookie, String strCookiePath) {
            return GetAuthCookie(userName, createPersistentCookie, strCookiePath, true);
        }
        private static HttpCookie GetAuthCookie(String userName, bool createPersistentCookie, String strCookiePath, bool hexEncodedTicket) {
            Initialize();
            if (userName == null)
                userName = String.Empty;
 
            if (strCookiePath == null || strCookiePath.Length < 1)
                strCookiePath = FormsCookiePath;
 
            DateTime issueDateUtc = DateTime.UtcNow;
            DateTime expirationUtc = issueDateUtc.AddMinutes(_Timeout);
 
            FormsAuthenticationTicket ticket = FormsAuthenticationTicket.FromUtc(
                2, // version
                userName, // User-Name
                issueDateUtc, // Issue-Date
                expirationUtc, // Expiration
                createPersistentCookie, // IsPersistent
                String.Empty, // User-Data
                strCookiePath // Cookie Path
                );
 
            String strTicket = Encrypt(ticket, hexEncodedTicket);
            if (strTicket == null || strTicket.Length < 1)
                        throw new HttpException(
                                SR.GetString(SR.Unable_to_encrypt_cookie_ticket));
 
 
            HttpCookie cookie = new HttpCookie(FormsCookieName, strTicket);
 
            cookie.HttpOnly = true;
            cookie.Path = strCookiePath;
            cookie.Secure = _RequireSSL;
            if (_CookieDomain != null)
                cookie.Domain = _CookieDomain;
            if (ticket.IsPersistent)
                cookie.Expires = ticket.Expiration;
            cookie.SameSite = _cookieSameSite;
            return cookie;
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
 
        internal static String GetReturnUrl(bool useDefaultIfAbsent)
        {
            Initialize();
 
            HttpContext     context     = HttpContext.Current;
            String          returnUrl   = context.Request.QueryString[ReturnUrlVar];
 
            // If it is not in the QueryString, look in the Posted-body
            if (returnUrl == null) {
                returnUrl = context.Request.Form[ReturnUrlVar];
                if (!string.IsNullOrEmpty(returnUrl) && !returnUrl.Contains("/") && returnUrl.Contains("%"))
                    returnUrl = HttpUtility.UrlDecode(returnUrl);
            }
 
            // Make sure it is on the current server if EnableCrossAppRedirects is false
            if (!string.IsNullOrEmpty(returnUrl) && !EnableCrossAppRedirects) {
                if (!UrlPath.IsPathOnSameServer(returnUrl, context.Request.Url))
                    returnUrl = null;
            }
 
            // Make sure it is not dangerous, i.e. does not contain script, etc.
            if (!string.IsNullOrEmpty(returnUrl) && CrossSiteScriptingValidation.IsDangerousUrl(returnUrl))
                throw new HttpException(SR.GetString(SR.Invalid_redirect_return_url));
 
            return ((returnUrl == null && useDefaultIfAbsent) ? DefaultUrl : returnUrl);
        }
 
        /// <devdoc>
        ///    Returns the redirect URL for the original
        ///    request that caused the redirect to the login page.
        /// </devdoc>
        public static String GetRedirectUrl(String userName, bool createPersistentCookie)
        {
            if (userName == null)
                return null;
            return GetReturnUrl(true);
        }
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Redirect from logon page to orignal page
 
        /// <devdoc>
        ///    This method redirects an authenticated user
        ///    back to the original URL that they requested.
        /// </devdoc>
        public static void RedirectFromLoginPage(String userName, bool createPersistentCookie) {
            Initialize();
            RedirectFromLoginPage(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
        }
 
        public static void RedirectFromLoginPage(String userName, bool createPersistentCookie, String strCookiePath) {
            Initialize();
            if (userName == null)
                return;
 
            HttpContext context = HttpContext.Current;
            string strUrl = GetReturnUrl(true);
            if (  CookiesSupported || // Cookies-supported: Most common scenario
                  IsPathWithinAppRoot(context, strUrl)) { // Cookies not suported, so add it to the current app URL
 
                SetAuthCookie(userName, createPersistentCookie, strCookiePath);
                strUrl = RemoveQueryStringVariableFromUrl(strUrl, FormsCookieName); // Make sure there is no other ticket in the Query String.
                if (!CookiesSupported) {// Make sure the URL is relative, if we are using cookieless.
                    int pos = strUrl.IndexOf("://", StringComparison.Ordinal);
                    if (pos > 0) {
                        pos = strUrl.IndexOf('/', pos + 3);
                        if (pos > 0)
                            strUrl = strUrl.Substring(pos);
                    }
                }
            } else if (EnableCrossAppRedirects) { // Cookieless scenario -- add it to the QueryString if allowed to
 
                HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie, strCookiePath);
                strUrl = RemoveQueryStringVariableFromUrl(strUrl, cookie.Name); // Make sure there is no other ticket in the Query String.
                if (strUrl.IndexOf('?') > 0) {
                    strUrl += "&" + cookie.Name + "=" + cookie.Value;
                }
                else {
                    strUrl += "?" + cookie.Name + "=" + cookie.Value;
                }
 
            } else {
                // Broken scenario:
                throw new HttpException(SR.GetString(SR.Can_not_issue_cookie_or_redirect));
            }
 
            context.Response.Redirect(strUrl, false);
        }
 
        public static FormsAuthenticationTicket RenewTicketIfOld(FormsAuthenticationTicket tOld) {
            if (tOld == null)
                return null;
 
            DateTime utcNow = DateTime.UtcNow;
            TimeSpan ticketAge = utcNow - tOld.IssueDateUtc;
            TimeSpan ticketRemainingLifetime = tOld.ExpirationUtc - utcNow;
 
            if (ticketRemainingLifetime > ticketAge)
                return tOld; // no need to renew
 
            // The original ticket may have had a custom-specified lifetime separate from
            // the default timeout specified in config. We should honor that original
            // lifetime when renewing the ticket.
            TimeSpan originalTicketTotalLifetime = tOld.ExpirationUtc - tOld.IssueDateUtc;
            DateTime newExpirationUtc = utcNow + originalTicketTotalLifetime;
 
            FormsAuthenticationTicket ticket = FormsAuthenticationTicket.FromUtc(
                tOld.Version /* version */,
                tOld.Name /* name */,
                utcNow /* issueDateUtc */,
                newExpirationUtc /* expirationUtc */,
                tOld.IsPersistent /* isPersistent */,
                tOld.UserData /* userData */,
                tOld.CookiePath /* cookiePath */);
 
            return ticket;
        }
 
        public static void EnableFormsAuthentication(NameValueCollection configurationData) {
            BuildManager.ThrowIfPreAppStartNotRunning();
            configurationData = configurationData ?? new NameValueCollection();
            AuthenticationConfig.Mode = AuthenticationMode.Forms;
            Initialize();
            // Last caller overwrites only the values that are present in the dictionary.
            string defaultUrl = configurationData["defaultUrl"];
            if (!String.IsNullOrEmpty(defaultUrl)) {
                _DefaultUrl = defaultUrl;
            }
            string loginUrl = configurationData["loginUrl"];
            if (!String.IsNullOrEmpty(loginUrl)) {
                _LoginUrl = loginUrl;
            }
        }
 
        public static bool IsEnabled {
            get {
                return AuthenticationConfig.Mode == AuthenticationMode.Forms;
            }
        }
 
        public static String FormsCookieName { get { Initialize(); return _FormsName; }}
 
        public static String FormsCookiePath { get { Initialize(); return _FormsCookiePath; }}
 
        public static bool   RequireSSL { get { Initialize(); return _RequireSSL; }}
 
        public static TimeSpan Timeout { get { Initialize(); return new TimeSpan(0, _Timeout, 0); } }
 
        public static bool   SlidingExpiration { get { Initialize(); return _SlidingExpiration; }}
 
        public static HttpCookieMode CookieMode { get { Initialize(); return _CookieMode; }}
 
        public static string CookieDomain { get { Initialize ();return _CookieDomain; } }
 
        public static bool EnableCrossAppRedirects { get { Initialize(); return _EnableCrossAppRedirects; } }
 
        public static TicketCompatibilityMode TicketCompatibilityMode { get { Initialize(); return _TicketCompatibilityMode; } }
 
        public static SameSiteMode CookieSameSite { get { Initialize(); return _cookieSameSite; }}
 
        public static bool CookiesSupported {
            get {
                HttpContext context = HttpContext.Current;
                if (context != null) {
                    return !(CookielessHelperClass.UseCookieless(context, false, CookieMode));
                }
                return true;
            }
        }
 
        public static string LoginUrl {
            get {
                Initialize();
                HttpContext context = HttpContext.Current;
                if (context != null)  {
                    return AuthenticationConfig.GetCompleteLoginUrl(context, _LoginUrl);
                }
                if (_LoginUrl.Length == 0 || (_LoginUrl[0] != '/' && _LoginUrl.IndexOf("//", StringComparison.Ordinal) < 0))
                    return ("/" + _LoginUrl);
                return _LoginUrl;
            }
        }
 
        public static string DefaultUrl {
            get {
                Initialize();
                HttpContext context = HttpContext.Current;
                if (context != null)  {
                    return AuthenticationConfig.GetCompleteLoginUrl(context, _DefaultUrl);
                }
                if (_DefaultUrl.Length == 0 || (_DefaultUrl[0] != '/' && _DefaultUrl.IndexOf("//", StringComparison.Ordinal) < 0))
                    return ("/" + _DefaultUrl);
                return _DefaultUrl;
            }
        }
 
        internal static string ReturnUrlVar {
            get {
                if (!String.IsNullOrEmpty(AppSettings.FormsAuthReturnUrlVar)) {
                    return AppSettings.FormsAuthReturnUrlVar;
                }
 
                return "ReturnUrl";
            }
        }
 
        internal static string GetLoginPage(string extraQueryString) {
            return GetLoginPage(extraQueryString, false);
        }
        internal static string GetLoginPage(string extraQueryString, bool reuseReturnUrl) {
            HttpContext context = HttpContext.Current;
            string loginUrl = FormsAuthentication.LoginUrl;
            if (loginUrl.IndexOf('?') >= 0)
                loginUrl = RemoveQueryStringVariableFromUrl(loginUrl, ReturnUrlVar);
            int pos = loginUrl.IndexOf('?');
            if (pos < 0)
                loginUrl += "?";
            else
                if (pos < loginUrl.Length -1)
                    loginUrl += "&";
            string returnUrl = null;
            if (reuseReturnUrl) {
                returnUrl = HttpUtility.UrlEncode( GetReturnUrl(false),
                                                   context.Request.QueryStringEncoding );
            }
            if (returnUrl == null)
                returnUrl = HttpUtility.UrlEncode(context.Request.RawUrl, context.Request.ContentEncoding);
 
            loginUrl += ReturnUrlVar + "=" + returnUrl;
            if (!String.IsNullOrEmpty(extraQueryString)) {
                loginUrl += "&" + extraQueryString;
            }
            return loginUrl;
        }
 
 
        public static void RedirectToLoginPage() {
            RedirectToLoginPage(null);
        }
 
 
        public static void RedirectToLoginPage(string extraQueryString) {
            HttpContext context = HttpContext.Current;
            string loginUrl = GetLoginPage(extraQueryString);
            context.Response.Redirect(loginUrl, false);
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        // Private stuff
 
        /////////////////////////////////////////////////////////////////////////////
        // Config Tags
        private  const String   CONFIG_DEFAULT_COOKIE    = ".ASPXAUTH";
 
        /////////////////////////////////////////////////////////////////////////////
        // Private data
        private static bool                _Initialized;
        private static String              _FormsName;
        //private static FormsProtectionEnum _Protection;
        private static FormsProtectionEnum _Protection;
        private static Int32               _Timeout;
        private static String              _FormsCookiePath;
        private static bool                _RequireSSL;
        private static bool                _SlidingExpiration;
        private static string              _LoginUrl;
        private static string              _DefaultUrl;
        private static HttpCookieMode      _CookieMode;
        private static string              _CookieDomain = null;
        private static bool                _EnableCrossAppRedirects;
        private static TicketCompatibilityMode _TicketCompatibilityMode;
        private static SameSiteMode        _cookieSameSite;
 
        /////////////////////////////////////////////////////////////////////////////
        private static byte[] MakeTicketIntoBinaryBlob(FormsAuthenticationTicket ticket) {
            // None of the modes (Framework20 / Framework40 / beyond) support null values for these fields;
            // they always eventually just returned a null value.
            if (ticket.Name == null || ticket.UserData == null || ticket.CookiePath == null) {
                return null;
            }
 
            // ** MSRC 11838 **
            // Framework20 / Framework40 ticket generation modes are insecure. We should use a
            // secure serialization mode by default.
            if (!AppSettings.UseLegacyFormsAuthenticationTicketCompatibility) {
                return FormsAuthenticationTicketSerializer.Serialize(ticket);
            }
 
            // ** MSRC 11838 **
            // If we have reached this point of execution, the developer has explicitly elected
            // to continue using the insecure code path instead of the secure one. We removed
            // the Framework40 serialization mode, so everybody using the legacy code path is
            // forced to Framework20.
 
            byte []   bData  = new byte[4096];
            byte []   pBin   = new byte[4];
            long []   pDates = new long[2];
            byte []   pNull  = { 0, 0, 0 };
 
            // DevDiv Bugs 137864: 8 bytes may not be enough random bits as the length should be equal to the
            // key size. In CompatMode > Framework20SP1, use the IVType.Random feature instead of these 8 bytes,
            // but still include empty 8 bytes for compat with webengine.dll, where CookieAuthConstructTicket is.
            // Note that even in CompatMode = Framework20SP2 we fill 8 bytes with random data if the ticket
            // is not going to be encrypted.
 
            bool willEncrypt = (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Encryption);
            bool legacyPadding = !willEncrypt || (MachineKeySection.CompatMode == MachineKeyCompatibilityMode.Framework20SP1);
            if (legacyPadding) {
                // Fill the first 8 bytes of the blob with random bits
                byte[] bRandom = new byte[8];
                RNGCryptoServiceProvider randgen = new RNGCryptoServiceProvider();
                randgen.GetBytes(bRandom);
                Buffer.BlockCopy(bRandom, 0, bData, 0, 8);
            }
            else {
                // use blank 8 bytes for compatibility with CookieAuthConstructTicket (do nothing)
            }
 
            pBin[0] = (byte) ticket.Version;
            pBin[1] = (byte) (ticket.IsPersistent ? 1 : 0);
 
            pDates[0] = ticket.IssueDate.ToFileTime();
            pDates[1] = ticket.Expiration.ToFileTime();
 
            int iRet = UnsafeNativeMethods.CookieAuthConstructTicket(
                        bData, bData.Length,
                        ticket.Name, ticket.UserData, ticket.CookiePath,
                        pBin, pDates);
 
            if (iRet < 0)
                return null;
 
            byte[] ciphertext = new byte[iRet];
            Buffer.BlockCopy(bData, 0, ciphertext, 0, iRet);
            return ciphertext;
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
 
        internal static string RemoveQueryStringVariableFromUrl(string strUrl, string QSVar) {
            int posQ = strUrl.IndexOf('?');
            if (posQ < 0)
                return strUrl;
 
            // Remove non-encoded QSVars
            string amp   = @"&";
            string question = @"?";
 
            string token = amp + QSVar + "=";
            RemoveQSVar(ref strUrl, posQ, token, amp, amp.Length);
 
            token = question + QSVar + "=";
            RemoveQSVar(ref strUrl, posQ, token, amp, question.Length);
 
            // Remove Url-enocoded strings
            amp = HttpUtility.UrlEncode(@"&");
            question = HttpUtility.UrlEncode(@"?");
 
            token = amp + HttpUtility.UrlEncode(QSVar + "=");
            RemoveQSVar(ref strUrl, posQ, token, amp, amp.Length);
 
            token = question + HttpUtility.UrlEncode(QSVar + "=");
            RemoveQSVar(ref strUrl, posQ, token, amp, question.Length);
            return strUrl;
        }
 
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        static private void RemoveQSVar(ref string strUrl, int posQ, string token, string sep, int lenAtStartToLeave)
        {
            for (int pos = strUrl.LastIndexOf(token, StringComparison.Ordinal); pos >= posQ; pos = strUrl.LastIndexOf(token, StringComparison.Ordinal))
            {
                int end = strUrl.IndexOf(sep, pos + token.Length, StringComparison.Ordinal) + sep.Length;
                if (end < sep.Length || end >= strUrl.Length)
                { // ending separator not found or nothing is at the end
                    strUrl = strUrl.Substring(0, pos);
                }
                else
                {
                    strUrl = strUrl.Substring(0, pos + lenAtStartToLeave) + strUrl.Substring(end);
                }
            }
        }
        static private bool IsPathWithinAppRoot(HttpContext context, string path)
        {
            Uri absUri;
            if (!Uri.TryCreate(path, UriKind.Absolute, out absUri))
                return HttpRuntime.IsPathWithinAppRoot(path);
 
            if (!absUri.IsLoopback && !string.Equals(context.Request.Url.Host, absUri.Host, StringComparison.OrdinalIgnoreCase))
                return false; // different servers
 
            return HttpRuntime.IsPathWithinAppRoot(absUri.AbsolutePath);
        }
    }
}