//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * AnonymousIdentificationModule class * * Copyright (c) 1999 Microsoft Corporation */ namespace System.Web.Security { using System.Web; using System.Text; using System.Web.Configuration; using System.Web.Caching; using System.Web.Handlers; using System.Collections; using System.Configuration.Provider; using System.Web.Util; using System.Security.Principal; using System.Security.Permissions; using System.Globalization; using System.Web.Management; using System.Web.Hosting; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Web.Security.Cryptography; /// /// [To be supplied.] /// public sealed class AnonymousIdentificationModule : IHttpModule { private const int MAX_ENCODED_COOKIE_STRING = 512; private const int MAX_ID_LENGTH = 128; /// /// /// Initializes a new instance of the /// class. /// /// [SecurityPermission(SecurityAction.Demand, Unrestricted=true)] public AnonymousIdentificationModule() { } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Events private AnonymousIdentificationEventHandler _CreateNewIdEventHandler; public event AnonymousIdentificationEventHandler Creating { add { _CreateNewIdEventHandler += value; } remove { _CreateNewIdEventHandler -= value; } } public static void ClearAnonymousIdentifier() { if (!s_Initialized) Initialize(); HttpContext context = HttpContext.Current; if (context == null) return; // VSWhidbey 418835: When this feature is enabled, prevent infinite loop when cookieless // mode != Cookies and there was no cookie, also we cannot clear when current user is anonymous. if (!s_Enabled || !context.Request.IsAuthenticated) { throw new NotSupportedException(SR.GetString(SR.Anonymous_ClearAnonymousIdentifierNotSupported)); } //////////////////////////////////////////////////////////// // Check if we need to clear the ticket stored in the URI bool clearUri = false; if (context.CookielessHelper.GetCookieValue('A') != null) { context.CookielessHelper.SetCookieValue('A', null); // Always clear the uri-cookie clearUri = true; } //////////////////////////////////////////////////////////// // Clear cookie if cookies are supported by the browser if (!CookielessHelperClass.UseCookieless(context, false, s_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(s_CookieName, cookieValue); cookie.HttpOnly = true; cookie.Path = s_CookiePath; cookie.Secure = s_RequireSSL; if (s_Domain != null) cookie.Domain = s_Domain; cookie.Expires = new System.DateTime(1999, 10, 12); context.Response.Cookies.RemoveCookie(s_CookieName); context.Response.Cookies.Add(cookie); } //////////////////////////////////////////////////////////// // Redirect back to this page if we removed a URI ticket if (clearUri) { context.Response.Redirect(context.Request.RawUrl, false); } } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Public functions public void Dispose() { } public void Init(HttpApplication app) { // for IIS 7, skip event wireup altogether if this feature isn't // enabled if (!s_Initialized) { Initialize(); } if (s_Enabled) { app.PostAuthenticateRequest += new EventHandler(this.OnEnter); } } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// /// /// [To be supplied.] /// private void OnEnter(Object source, EventArgs eventArgs) { if (!s_Initialized) Initialize(); if (!s_Enabled) return; HttpApplication app; HttpContext context; HttpCookie cookie = null; bool createCookie = false; AnonymousIdData decodedValue = null; bool cookieLess; string encValue = null; bool isAuthenticated = false; app = (HttpApplication)source; context = app.Context; isAuthenticated = context.Request.IsAuthenticated; if (isAuthenticated) { cookieLess = CookielessHelperClass.UseCookieless(context, false /* no redirect */, s_CookieMode); } else { cookieLess = CookielessHelperClass.UseCookieless(context, true /* do redirect */, s_CookieMode); //if (!cookieLess && s_RequireSSL && !context.Request.IsSecureConnection) // throw new HttpException(SR.GetString(SR.Connection_not_secure_creating_secure_cookie)); } //////////////////////////////////////////////////////////////////////// // Handle secure-cookies over non SSL if (s_RequireSSL && !context.Request.IsSecureConnection) { if (!cookieLess) { cookie = context.Request.Cookies[s_CookieName]; if (cookie != null) { cookie = new HttpCookie(s_CookieName, String.Empty); cookie.HttpOnly = true; cookie.Path = s_CookiePath; cookie.Secure = s_RequireSSL; if (s_Domain != null) cookie.Domain = s_Domain; cookie.Expires = new System.DateTime(1999, 10, 12); if (context.Request.Browser["supportsEmptyStringInCookieValue"] == "false") cookie.Value = "NoCookie"; context.Response.Cookies.Add(cookie); } return; } } //////////////////////////////////////////////////////////// // Step 2: See if cookie, or cookie-header has the value if (!cookieLess) { cookie = context.Request.Cookies[s_CookieName]; if (cookie != null) { encValue = cookie.Value; cookie.Path = s_CookiePath; if (s_Domain != null) cookie.Domain = s_Domain; } } else { encValue = context.CookielessHelper.GetCookieValue('A'); } decodedValue = GetDecodedValue(encValue); if (decodedValue != null && decodedValue.AnonymousId != null) { // Copy existing value in Request context.Request.AnonymousID = decodedValue.AnonymousId; } if (isAuthenticated) // For the authenticated case, we are done return; if (context.Request.AnonymousID == null) { //////////////////////////////////////////////////////////// // Step 3: Create new Identity // Raise event if (_CreateNewIdEventHandler != null) { AnonymousIdentificationEventArgs e = new AnonymousIdentificationEventArgs(context); _CreateNewIdEventHandler(this, e); context.Request.AnonymousID = e.AnonymousID; } // Create from GUID if (string.IsNullOrEmpty(context.Request.AnonymousID)) { context.Request.AnonymousID = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture); } else { if (context.Request.AnonymousID.Length > MAX_ID_LENGTH) throw new HttpException(SR.GetString(SR.Anonymous_id_too_long)); } if (s_RequireSSL && !context.Request.IsSecureConnection && !cookieLess) return; // Don't create secure-cookie in un-secured connection createCookie = true; } //////////////////////////////////////////////////////////// // Step 4: Check if cookie has to be created DateTime dtNow = DateTime.UtcNow; if (!createCookie) { if (s_SlidingExpiration) { if (decodedValue == null || decodedValue.ExpireDate < dtNow) { createCookie = true; } else { double secondsLeft = (decodedValue.ExpireDate - dtNow).TotalSeconds; if (secondsLeft < (double) ((s_CookieTimeout*60)/2)) { createCookie = true; } } } } //////////////////////////////////////////////////////////// // Step 4: Create new cookie or cookieless header if (createCookie) { DateTime dtExpireTime = dtNow.AddMinutes(s_CookieTimeout); encValue = GetEncodedValue(new AnonymousIdData(context.Request.AnonymousID, dtExpireTime)); if (encValue.Length > MAX_ENCODED_COOKIE_STRING) throw new HttpException(SR.GetString(SR.Anonymous_id_too_long_2)); if (!cookieLess) { cookie = new HttpCookie(s_CookieName, encValue); cookie.HttpOnly = true; cookie.Expires = dtExpireTime; cookie.Path = s_CookiePath; cookie.Secure = s_RequireSSL; if (s_Domain != null) cookie.Domain = s_Domain; context.Response.Cookies.Add(cookie); } else { context.CookielessHelper.SetCookieValue('A', encValue); context.Response.Redirect(context.Request.RawUrl); } } } public static bool Enabled { get { if (!s_Initialized) { Initialize(); } return s_Enabled; } } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Static config settings private static bool s_Initialized = false; private static bool s_Enabled = false; private static string s_CookieName = ".ASPXANONYMOUS"; private static string s_CookiePath = "/"; private static int s_CookieTimeout = 100000; private static bool s_RequireSSL = false; private static string s_Domain = null; private static bool s_SlidingExpiration = true; private static byte [] s_Modifier = null; private static object s_InitLock = new object(); private static HttpCookieMode s_CookieMode = HttpCookieMode.UseDeviceProfile; private static CookieProtection s_Protection = CookieProtection.None; ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Static functions private static void Initialize() { if (s_Initialized) return; lock(s_InitLock) { if (s_Initialized) return; AnonymousIdentificationSection settings = RuntimeConfig.GetAppConfig().AnonymousIdentification; s_Enabled = settings.Enabled; s_CookieName = settings.CookieName; s_CookiePath = settings.CookiePath; s_CookieTimeout = (int) settings.CookieTimeout.TotalMinutes; s_RequireSSL = settings.CookieRequireSSL; s_SlidingExpiration = settings.CookieSlidingExpiration; s_Protection = settings.CookieProtection; s_CookieMode = settings.Cookieless; s_Domain = settings.Domain; s_Modifier = Encoding.UTF8.GetBytes("AnonymousIdentification"); if (s_CookieTimeout < 1) s_CookieTimeout = 1; if (s_CookieTimeout > 60 * 24 * 365 * 2) s_CookieTimeout = 60 * 24 * 365 * 2; // 2 years s_Initialized = true; } } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// private static string GetEncodedValue(AnonymousIdData data){ if (data == null) return null; byte [] bufId = Encoding.UTF8.GetBytes(data.AnonymousId); byte [] bufIdLen = BitConverter.GetBytes(bufId.Length); byte [] bufDate = BitConverter.GetBytes(data.ExpireDate.ToFileTimeUtc()); byte [] buffer = new byte[12 + bufId.Length]; Buffer.BlockCopy(bufDate, 0, buffer, 0, 8); Buffer.BlockCopy(bufIdLen, 0, buffer, 8, 4); Buffer.BlockCopy(bufId, 0, buffer, 12, bufId.Length); return CookieProtectionHelper.Encode(s_Protection, buffer, Purpose.AnonymousIdentificationModule_Ticket); } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// private static AnonymousIdData GetDecodedValue(string data){ if (data == null || data.Length < 1 || data.Length > MAX_ENCODED_COOKIE_STRING) return null; try { byte [] bBlob = CookieProtectionHelper.Decode(s_Protection, data, Purpose.AnonymousIdentificationModule_Ticket); if (bBlob == null || bBlob.Length < 13) return null; DateTime expireDate = DateTime.FromFileTimeUtc(BitConverter.ToInt64(bBlob, 0)); if (expireDate < DateTime.UtcNow) return null; int len = BitConverter.ToInt32(bBlob, 8); if (len < 0 || len > bBlob.Length - 12) return null; string id = Encoding.UTF8.GetString(bBlob, 12, len); if (id.Length > MAX_ID_LENGTH) return null; return new AnonymousIdData(id, expireDate); } catch {} return null; } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// [Serializable] internal class AnonymousIdData { internal AnonymousIdData(string id, DateTime dt) { ExpireDate = dt; AnonymousId = (dt > DateTime.UtcNow) ? id : null; // Ignore expired data } internal string AnonymousId; internal DateTime ExpireDate; } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// public delegate void AnonymousIdentificationEventHandler(object sender, AnonymousIdentificationEventArgs e); ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// public sealed class AnonymousIdentificationEventArgs : EventArgs { public string AnonymousID { get { return _AnonymousId;} set { _AnonymousId = value;}} public HttpContext Context { get { return _Context; }} private string _AnonymousId; private HttpContext _Context; public AnonymousIdentificationEventArgs(HttpContext context) { _Context = context; } } }