|
//------------------------------------------------------------------------------
// <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);
}
}
}
|