[bcl] Updates referencesource to 4.7.1
[mono-project.git] / mcs / class / referencesource / System.Web / Security / FormsAuthentication.cs
blobe50e16e588564bef88bdc57e622db81c7e485f21
1 //------------------------------------------------------------------------------
2 // <copyright file="FormsAuthentication.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
7 /*
8 * FormsAuthentication class
10 * Copyright (c) 1999 Microsoft Corporation
13 namespace System.Web.Security {
14 using System;
15 using System.Web;
16 using System.Text;
17 using System.Web.Configuration;
18 using System.Web.Caching;
19 using System.Collections;
20 using System.Web.Util;
21 using System.Security.Cryptography;
22 using System.Security.Principal;
23 using System.Threading;
24 using System.Globalization;
25 using System.Security.Permissions;
26 using System.Web.Management;
27 using System.Collections.Specialized;
28 using System.Web.Compilation;
29 using System.Web.Security.Cryptography;
33 /// <devdoc>
34 /// This class consists of static methods that
35 /// provides helper utilities for manipulating authentication tickets.
36 /// </devdoc>
37 public sealed class FormsAuthentication {
38 private const int MAX_TICKET_LENGTH = 4096;
39 private static object _lockObject = new object();
41 public FormsAuthentication() { }
43 /////////////////////////////////////////////////////////////////////////////
44 /////////////////////////////////////////////////////////////////////////////
45 /////////////////////////////////////////////////////////////////////////////
46 // Helper functions: Hash a password
48 /// <devdoc>
49 /// Initializes FormsAuthentication by reading
50 /// configuration and getting the cookie values and encryption keys for the given
51 /// application.
52 /// </devdoc>
53 [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.")]
54 public static String HashPasswordForStoringInConfigFile(String password, String passwordFormat) {
55 if (password == null) {
56 throw new ArgumentNullException("password");
58 if (passwordFormat == null) {
59 throw new ArgumentNullException("passwordFormat");
61 HashAlgorithm hashAlgorithm;
62 if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha1"))
63 hashAlgorithm = CryptoAlgorithms.CreateSHA1();
64 else if (StringUtil.EqualsIgnoreCase(passwordFormat, "md5"))
65 hashAlgorithm = CryptoAlgorithms.CreateMD5();
66 else if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha256"))
67 hashAlgorithm = CryptoAlgorithms.CreateSHA256();
68 else if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha384"))
69 hashAlgorithm = CryptoAlgorithms.CreateSHA384();
70 else if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha512"))
71 hashAlgorithm = CryptoAlgorithms.CreateSHA512();
72 else
73 throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "passwordFormat"));
75 using (hashAlgorithm) {
76 return CryptoUtil.BinaryToHex(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(password)));
80 /////////////////////////////////////////////////////////////////////////////
81 /////////////////////////////////////////////////////////////////////////////
82 /////////////////////////////////////////////////////////////////////////////
83 // Initialize this
85 /// <devdoc>
86 /// Initializes FormsAuthentication by reading
87 /// configuration and getting the cookie values and encryption keys for the given
88 /// application.
89 /// </devdoc>
90 public static void Initialize() {
91 if (_Initialized)
92 return;
94 lock(_lockObject) {
95 if (_Initialized)
96 return;
98 AuthenticationSection settings = RuntimeConfig.GetAppConfig().Authentication;
99 settings.ValidateAuthenticationMode();
100 _FormsName = settings.Forms.Name;
101 _RequireSSL = settings.Forms.RequireSSL;
102 _SlidingExpiration = settings.Forms.SlidingExpiration;
103 if (_FormsName == null)
104 _FormsName = CONFIG_DEFAULT_COOKIE;
106 _Protection = settings.Forms.Protection;
107 _Timeout = (int) settings.Forms.Timeout.TotalMinutes;
108 _FormsCookiePath = settings.Forms.Path;
109 _LoginUrl = settings.Forms.LoginUrl;
110 if (_LoginUrl == null)
111 _LoginUrl = "login.aspx";
112 _DefaultUrl = settings.Forms.DefaultUrl;
113 if (_DefaultUrl == null)
114 _DefaultUrl = "default.aspx";
115 _CookieMode = settings.Forms.Cookieless;
116 _CookieDomain = settings.Forms.Domain;
117 _EnableCrossAppRedirects = settings.Forms.EnableCrossAppRedirects;
118 _TicketCompatibilityMode = settings.Forms.TicketCompatibilityMode;
120 _Initialized = true;
124 /////////////////////////////////////////////////////////////////////////////
125 /////////////////////////////////////////////////////////////////////////////
126 /////////////////////////////////////////////////////////////////////////////
127 // Decrypt and get the auth ticket
129 /// <devdoc>
130 /// <para>Given an encrypted authenitcation ticket as
131 /// obtained from an HTTP cookie, this method returns an instance of a
132 /// FormsAuthenticationTicket class.</para>
133 /// </devdoc>
134 public static FormsAuthenticationTicket Decrypt(string encryptedTicket) {
135 if (String.IsNullOrEmpty(encryptedTicket) || encryptedTicket.Length > MAX_TICKET_LENGTH)
136 throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "encryptedTicket"));
138 Initialize();
139 byte[] bBlob = null;
140 if ((encryptedTicket.Length % 2) == 0) { // Could be a hex string
141 try {
142 bBlob = CryptoUtil.HexToBinary(encryptedTicket);
143 } catch { }
145 if (bBlob == null)
146 bBlob = HttpServerUtility.UrlTokenDecode(encryptedTicket);
147 if (bBlob == null || bBlob.Length < 1)
148 throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "encryptedTicket"));
150 int ticketLength;
152 if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider) {
153 // If new crypto routines are enabled, call them instead.
154 ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(Purpose.FormsAuthentication_Ticket);
155 byte[] unprotectedData = cryptoService.Unprotect(bBlob);
156 ticketLength = unprotectedData.Length;
157 bBlob = unprotectedData;
158 } else {
159 #pragma warning disable 618 // calling obsolete methods
160 // Otherwise call into MachineKeySection routines.
162 if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Encryption)
164 // DevDiv Bugs 137864: Include a random IV if under the right compat mode
165 // for improved encryption semantics
166 bBlob = MachineKeySection.EncryptOrDecryptData(false, bBlob, null, 0, bBlob.Length, false, false, IVType.Random);
167 if (bBlob == null)
168 return null;
171 ticketLength = bBlob.Length;
173 if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Validation)
175 if (!MachineKeySection.VerifyHashedData(bBlob))
176 return null;
177 ticketLength -= MachineKeySection.HashSize;
179 #pragma warning restore 618 // calling obsolete methods
182 //////////////////////////////////////////////////////////////////////
183 // Step 4: Change binary ticket to managed struct
185 // ** MSRC 11838 **
186 // Framework20 / Framework40 ticket generation modes are insecure. We should use a
187 // secure serialization mode by default.
188 if (!AppSettings.UseLegacyFormsAuthenticationTicketCompatibility) {
189 return FormsAuthenticationTicketSerializer.Deserialize(bBlob, ticketLength);
192 // ** MSRC 11838 **
193 // If we have reached this point of execution, the developer has explicitly elected
194 // to continue using the insecure code path instead of the secure one. We removed
195 // the Framework40 serialization mode, so everybody using the legacy code path is
196 // forced to Framework20.
198 int iSize = ((ticketLength > MAX_TICKET_LENGTH) ? MAX_TICKET_LENGTH : ticketLength);
199 StringBuilder name = new StringBuilder(iSize);
200 StringBuilder data = new StringBuilder(iSize);
201 StringBuilder path = new StringBuilder(iSize);
202 byte [] pBin = new byte[4];
203 long [] pDates = new long[2];
205 int iRet = UnsafeNativeMethods.CookieAuthParseTicket(bBlob, ticketLength,
206 name, iSize,
207 data, iSize,
208 path, iSize,
209 pBin, pDates);
211 if (iRet != 0)
212 return null;
214 DateTime dt1 = DateTime.FromFileTime(pDates[0]);
215 DateTime dt2 = DateTime.FromFileTime(pDates[1]);
217 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket((int) pBin[0],
218 name.ToString(),
219 dt1,
220 dt2,
221 (bool) (pBin[1] != 0),
222 data.ToString(),
223 path.ToString());
224 return ticket;
228 /////////////////////////////////////////////////////////////////////////////
229 /////////////////////////////////////////////////////////////////////////////
230 /////////////////////////////////////////////////////////////////////////////
231 // Encrypt a ticket
233 /// <devdoc>
234 /// Given a FormsAuthenticationTicket, this
235 /// method produces a string containing an encrypted authentication ticket suitable
236 /// for use in an HTTP cookie.
237 /// </devdoc>
238 public static String Encrypt(FormsAuthenticationTicket ticket) {
239 return Encrypt(ticket, true);
241 internal static String Encrypt(FormsAuthenticationTicket ticket, bool hexEncodedTicket) {
242 if (ticket == null)
243 throw new ArgumentNullException("ticket");
245 Initialize();
246 //////////////////////////////////////////////////////////////////////
247 // Step 1a: Make it into a binary blob
248 byte[] bBlob = MakeTicketIntoBinaryBlob(ticket);
249 if (bBlob == null)
250 return null;
252 //////////////////////////////////////////////////////////////////////
253 // Step 1b: If new crypto routines are enabled, call them instead.
254 if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider) {
255 ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(Purpose.FormsAuthentication_Ticket);
256 byte[] protectedData = cryptoService.Protect(bBlob);
257 bBlob = protectedData;
259 else {
260 #pragma warning disable 618 // calling obsolete methods
261 // otherwise..
263 //////////////////////////////////////////////////////////////////////
264 // Step 2: Get the MAC and add to the blob
265 if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Validation) {
266 byte[] bMac = MachineKeySection.HashData(bBlob, null, 0, bBlob.Length);
267 if (bMac == null)
268 return null;
269 byte[] bAll = new byte[bMac.Length + bBlob.Length];
270 Buffer.BlockCopy(bBlob, 0, bAll, 0, bBlob.Length);
271 Buffer.BlockCopy(bMac, 0, bAll, bBlob.Length, bMac.Length);
272 bBlob = bAll;
275 if (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Encryption) {
276 //////////////////////////////////////////////////////////////////////
277 // Step 3: Do the actual encryption
278 // DevDiv Bugs 137864: Include a random IV if under the right compat mode
279 // for improved encryption semantics
280 bBlob = MachineKeySection.EncryptOrDecryptData(true, bBlob, null, 0, bBlob.Length, false, false, IVType.Random);
282 #pragma warning restore 618 // calling obsolete methods
285 if (!hexEncodedTicket)
286 return HttpServerUtility.UrlTokenEncode(bBlob);
287 else
288 return CryptoUtil.BinaryToHex(bBlob);
291 /////////////////////////////////////////////////////////////////////////////
292 /////////////////////////////////////////////////////////////////////////////
293 /////////////////////////////////////////////////////////////////////////////
294 // Verify User name and Password
296 /// <devdoc>
297 /// Given the supplied credentials, this method
298 /// attempts to validate the credentials against those contained in the configured
299 /// credential store.
300 /// </devdoc>
301 [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.")]
302 public static bool Authenticate(String name, String password) {
303 bool retVal = InternalAuthenticate(name, password);
305 if (retVal) {
306 PerfCounters.IncrementCounter(AppPerfCounter.FORMS_AUTH_SUCCESS);
307 WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditFormsAuthenticationSuccess, name);
309 else {
310 PerfCounters.IncrementCounter(AppPerfCounter.FORMS_AUTH_FAIL);
311 WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditFormsAuthenticationFailure, name);
314 return retVal;
317 private static bool InternalAuthenticate(String name, String password) {
318 //////////////////////////////////////////////////////////////////////
319 // Step 1: Make sure we are initialized
320 if (name == null || password == null)
321 return false;
323 Initialize();
324 //////////////////////////////////////////////////////////////////////
325 // Step 2: Get the user database
326 AuthenticationSection settings = RuntimeConfig.GetAppConfig().Authentication;
327 settings.ValidateAuthenticationMode();
328 FormsAuthenticationUserCollection Users = settings.Forms.Credentials.Users;
330 // Hashtable hTable = settings.Credentials;
332 if (Users == null) {
333 return false;
336 //////////////////////////////////////////////////////////////////////
337 // Step 3: Get the (hashed) password for this user
338 FormsAuthenticationUser u = Users[name.ToLower(CultureInfo.InvariantCulture)];
339 if (u == null)
340 return false;
342 String pass = (String)u.Password;
344 if (pass == null) {
345 return false;
348 //////////////////////////////////////////////////////////////////////
349 // Step 4: Hash the given password
350 String encPassword;
352 #pragma warning disable 618 // HashPasswordForStorignInConfigFile is now obsolete
353 switch (settings.Forms.Credentials.PasswordFormat)
355 case FormsAuthPasswordFormat.SHA256:
356 encPassword = HashPasswordForStoringInConfigFile(password, "sha256");
357 break;
358 case FormsAuthPasswordFormat.SHA384:
359 encPassword = HashPasswordForStoringInConfigFile(password, "sha384");
360 break;
361 case FormsAuthPasswordFormat.SHA512:
362 encPassword = HashPasswordForStoringInConfigFile(password, "sha512");
363 break;
364 case FormsAuthPasswordFormat.SHA1:
365 encPassword = HashPasswordForStoringInConfigFile(password, "sha1");
366 break;
368 case FormsAuthPasswordFormat.MD5:
369 encPassword = HashPasswordForStoringInConfigFile(password, "md5");
370 break;
372 case FormsAuthPasswordFormat.Clear:
373 encPassword = password;
374 break;
376 default:
377 return false;
379 #pragma warning restore 618
381 //////////////////////////////////////////////////////////////////////
382 // Step 5: Compare the hashes
383 return(String.Compare(encPassword,
384 pass,
385 ((settings.Forms.Credentials.PasswordFormat != FormsAuthPasswordFormat.Clear)
386 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
387 == 0);
390 /////////////////////////////////////////////////////////////////////////////
391 /////////////////////////////////////////////////////////////////////////////
392 /////////////////////////////////////////////////////////////////////////////
394 /// <devdoc>
395 /// Given an authenticated user, calling SignOut
396 /// removes the authentication ticket by doing a SetForms with an empty value. This
397 /// removes either durable or session cookies.
398 /// </devdoc>
399 public static void SignOut() {
400 Initialize();
402 HttpContext context = HttpContext.Current;
403 bool needToRedirect = context.CookielessHelper.DoesCookieValueExistInOriginal('F');
405 context.CookielessHelper.SetCookieValue('F', null); // Always clear the uri-cookie
407 if (!CookielessHelperClass.UseCookieless(context, false, CookieMode) || context.Request.Browser.Cookies)
408 { // clear cookie if required
409 string cookieValue = String.Empty;
410 if (context.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
411 cookieValue = "NoCookie";
412 HttpCookie cookie = new HttpCookie(FormsCookieName, cookieValue);
413 cookie.HttpOnly = true;
414 cookie.Path = _FormsCookiePath;
415 cookie.Expires = new System.DateTime(1999, 10, 12);
416 cookie.Secure = _RequireSSL;
417 if (_CookieDomain != null)
418 cookie.Domain = _CookieDomain;
419 context.Response.Cookies.RemoveCookie(FormsCookieName);
420 context.Response.Cookies.Add(cookie);
422 if (needToRedirect)
423 context.Response.Redirect(GetLoginPage(null), false);
425 /////////////////////////////////////////////////////////////////////////////
426 /////////////////////////////////////////////////////////////////////////////
427 /////////////////////////////////////////////////////////////////////////////
429 /// <devdoc>
430 /// This method creates an authentication ticket
431 /// for the given userName and attaches it to the cookies collection of the outgoing
432 /// response. It does not perform a redirect.
433 /// </devdoc>
434 public static void SetAuthCookie(String userName, bool createPersistentCookie) {
435 Initialize();
436 SetAuthCookie(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
439 /// <devdoc>
440 /// This method creates an authentication ticket
441 /// for the given userName and attaches it to the cookies collection of the outgoing
442 /// response. It does not perform a redirect.
443 /// </devdoc>
444 public static void SetAuthCookie(String userName, bool createPersistentCookie, String strCookiePath) {
445 Initialize();
446 HttpContext context = HttpContext.Current;
448 if (!context.Request.IsSecureConnection && RequireSSL)
449 throw new HttpException(SR.GetString(SR.Connection_not_secure_creating_secure_cookie));
450 bool cookieless = CookielessHelperClass.UseCookieless(context, false, CookieMode);
451 HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie, cookieless ? "/" : strCookiePath, !cookieless);
453 if (!cookieless) {
454 HttpContext.Current.Response.Cookies.Add(cookie);
455 context.CookielessHelper.SetCookieValue('F', null);
457 else {
458 context.CookielessHelper.SetCookieValue('F', cookie.Value);
462 /////////////////////////////////////////////////////////////////////////////
463 /////////////////////////////////////////////////////////////////////////////
464 /////////////////////////////////////////////////////////////////////////////
466 /// <devdoc>
467 /// Creates an authentication cookie for a given
468 /// user name. This does not set the cookie as part of the outgoing response, so
469 /// that an application can have more control over how the cookie is issued.
470 /// </devdoc>
471 public static HttpCookie GetAuthCookie(String userName, bool createPersistentCookie) {
472 Initialize();
473 return GetAuthCookie(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
476 public static HttpCookie GetAuthCookie(String userName, bool createPersistentCookie, String strCookiePath) {
477 return GetAuthCookie(userName, createPersistentCookie, strCookiePath, true);
479 private static HttpCookie GetAuthCookie(String userName, bool createPersistentCookie, String strCookiePath, bool hexEncodedTicket) {
480 Initialize();
481 if (userName == null)
482 userName = String.Empty;
484 if (strCookiePath == null || strCookiePath.Length < 1)
485 strCookiePath = FormsCookiePath;
487 DateTime issueDateUtc = DateTime.UtcNow;
488 DateTime expirationUtc = issueDateUtc.AddMinutes(_Timeout);
490 FormsAuthenticationTicket ticket = FormsAuthenticationTicket.FromUtc(
491 2, // version
492 userName, // User-Name
493 issueDateUtc, // Issue-Date
494 expirationUtc, // Expiration
495 createPersistentCookie, // IsPersistent
496 String.Empty, // User-Data
497 strCookiePath // Cookie Path
500 String strTicket = Encrypt(ticket, hexEncodedTicket);
501 if (strTicket == null || strTicket.Length < 1)
502 throw new HttpException(
503 SR.GetString(SR.Unable_to_encrypt_cookie_ticket));
506 HttpCookie cookie = new HttpCookie(FormsCookieName, strTicket);
508 cookie.HttpOnly = true;
509 cookie.Path = strCookiePath;
510 cookie.Secure = _RequireSSL;
511 if (_CookieDomain != null)
512 cookie.Domain = _CookieDomain;
513 if (ticket.IsPersistent)
514 cookie.Expires = ticket.Expiration;
515 return cookie;
518 /////////////////////////////////////////////////////////////////////////////
519 /////////////////////////////////////////////////////////////////////////////
520 /////////////////////////////////////////////////////////////////////////////
522 internal static String GetReturnUrl(bool useDefaultIfAbsent)
524 Initialize();
526 HttpContext context = HttpContext.Current;
527 String returnUrl = context.Request.QueryString[ReturnUrlVar];
529 // If it is not in the QueryString, look in the Posted-body
530 if (returnUrl == null) {
531 returnUrl = context.Request.Form[ReturnUrlVar];
532 if (!string.IsNullOrEmpty(returnUrl) && !returnUrl.Contains("/") && returnUrl.Contains("%"))
533 returnUrl = HttpUtility.UrlDecode(returnUrl);
536 // Make sure it is on the current server if EnableCrossAppRedirects is false
537 if (!string.IsNullOrEmpty(returnUrl) && !EnableCrossAppRedirects) {
538 if (!UrlPath.IsPathOnSameServer(returnUrl, context.Request.Url))
539 returnUrl = null;
542 // Make sure it is not dangerous, i.e. does not contain script, etc.
543 if (!string.IsNullOrEmpty(returnUrl) && CrossSiteScriptingValidation.IsDangerousUrl(returnUrl))
544 throw new HttpException(SR.GetString(SR.Invalid_redirect_return_url));
546 return ((returnUrl == null && useDefaultIfAbsent) ? DefaultUrl : returnUrl);
549 /// <devdoc>
550 /// Returns the redirect URL for the original
551 /// request that caused the redirect to the login page.
552 /// </devdoc>
553 public static String GetRedirectUrl(String userName, bool createPersistentCookie)
555 if (userName == null)
556 return null;
557 return GetReturnUrl(true);
559 /////////////////////////////////////////////////////////////////////////////
560 /////////////////////////////////////////////////////////////////////////////
561 /////////////////////////////////////////////////////////////////////////////
562 // Redirect from logon page to orignal page
564 /// <devdoc>
565 /// This method redirects an authenticated user
566 /// back to the original URL that they requested.
567 /// </devdoc>
568 public static void RedirectFromLoginPage(String userName, bool createPersistentCookie) {
569 Initialize();
570 RedirectFromLoginPage(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
573 public static void RedirectFromLoginPage(String userName, bool createPersistentCookie, String strCookiePath) {
574 Initialize();
575 if (userName == null)
576 return;
578 HttpContext context = HttpContext.Current;
579 string strUrl = GetReturnUrl(true);
580 if ( CookiesSupported || // Cookies-supported: Most common scenario
581 IsPathWithinAppRoot(context, strUrl)) { // Cookies not suported, so add it to the current app URL
583 SetAuthCookie(userName, createPersistentCookie, strCookiePath);
584 strUrl = RemoveQueryStringVariableFromUrl(strUrl, FormsCookieName); // Make sure there is no other ticket in the Query String.
585 if (!CookiesSupported) {// Make sure the URL is relative, if we are using cookieless.
586 int pos = strUrl.IndexOf("://", StringComparison.Ordinal);
587 if (pos > 0) {
588 pos = strUrl.IndexOf('/', pos + 3);
589 if (pos > 0)
590 strUrl = strUrl.Substring(pos);
593 } else if (EnableCrossAppRedirects) { // Cookieless scenario -- add it to the QueryString if allowed to
595 HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie, strCookiePath);
596 strUrl = RemoveQueryStringVariableFromUrl(strUrl, cookie.Name); // Make sure there is no other ticket in the Query String.
597 if (strUrl.IndexOf('?') > 0) {
598 strUrl += "&" + cookie.Name + "=" + cookie.Value;
600 else {
601 strUrl += "?" + cookie.Name + "=" + cookie.Value;
604 } else {
605 // Broken scenario:
606 throw new HttpException(SR.GetString(SR.Can_not_issue_cookie_or_redirect));
609 context.Response.Redirect(strUrl, false);
612 public static FormsAuthenticationTicket RenewTicketIfOld(FormsAuthenticationTicket tOld) {
613 if (tOld == null)
614 return null;
616 DateTime utcNow = DateTime.UtcNow;
617 TimeSpan ticketAge = utcNow - tOld.IssueDateUtc;
618 TimeSpan ticketRemainingLifetime = tOld.ExpirationUtc - utcNow;
620 if (ticketRemainingLifetime > ticketAge)
621 return tOld; // no need to renew
623 // The original ticket may have had a custom-specified lifetime separate from
624 // the default timeout specified in config. We should honor that original
625 // lifetime when renewing the ticket.
626 TimeSpan originalTicketTotalLifetime = tOld.ExpirationUtc - tOld.IssueDateUtc;
627 DateTime newExpirationUtc = utcNow + originalTicketTotalLifetime;
629 FormsAuthenticationTicket ticket = FormsAuthenticationTicket.FromUtc(
630 tOld.Version /* version */,
631 tOld.Name /* name */,
632 utcNow /* issueDateUtc */,
633 newExpirationUtc /* expirationUtc */,
634 tOld.IsPersistent /* isPersistent */,
635 tOld.UserData /* userData */,
636 tOld.CookiePath /* cookiePath */);
638 return ticket;
641 public static void EnableFormsAuthentication(NameValueCollection configurationData) {
642 BuildManager.ThrowIfPreAppStartNotRunning();
643 configurationData = configurationData ?? new NameValueCollection();
644 AuthenticationConfig.Mode = AuthenticationMode.Forms;
645 Initialize();
646 // Last caller overwrites only the values that are present in the dictionary.
647 string defaultUrl = configurationData["defaultUrl"];
648 if (!String.IsNullOrEmpty(defaultUrl)) {
649 _DefaultUrl = defaultUrl;
651 string loginUrl = configurationData["loginUrl"];
652 if (!String.IsNullOrEmpty(loginUrl)) {
653 _LoginUrl = loginUrl;
657 public static bool IsEnabled {
658 get {
659 return AuthenticationConfig.Mode == AuthenticationMode.Forms;
663 public static String FormsCookieName { get { Initialize(); return _FormsName; }}
665 public static String FormsCookiePath { get { Initialize(); return _FormsCookiePath; }}
667 public static bool RequireSSL { get { Initialize(); return _RequireSSL; }}
669 public static TimeSpan Timeout { get { Initialize(); return new TimeSpan(0, _Timeout, 0); } }
671 public static bool SlidingExpiration { get { Initialize(); return _SlidingExpiration; }}
673 public static HttpCookieMode CookieMode { get { Initialize(); return _CookieMode; }}
675 public static string CookieDomain { get { Initialize ();return _CookieDomain; } }
677 public static bool EnableCrossAppRedirects { get { Initialize(); return _EnableCrossAppRedirects; } }
679 public static TicketCompatibilityMode TicketCompatibilityMode { get { Initialize(); return _TicketCompatibilityMode; } }
681 public static bool CookiesSupported {
682 get {
683 HttpContext context = HttpContext.Current;
684 if (context != null) {
685 return !(CookielessHelperClass.UseCookieless(context, false, CookieMode));
687 return true;
691 public static string LoginUrl {
692 get {
693 Initialize();
694 HttpContext context = HttpContext.Current;
695 if (context != null) {
696 return AuthenticationConfig.GetCompleteLoginUrl(context, _LoginUrl);
698 if (_LoginUrl.Length == 0 || (_LoginUrl[0] != '/' && _LoginUrl.IndexOf("//", StringComparison.Ordinal) < 0))
699 return ("/" + _LoginUrl);
700 return _LoginUrl;
704 public static string DefaultUrl {
705 get {
706 Initialize();
707 HttpContext context = HttpContext.Current;
708 if (context != null) {
709 return AuthenticationConfig.GetCompleteLoginUrl(context, _DefaultUrl);
711 if (_DefaultUrl.Length == 0 || (_DefaultUrl[0] != '/' && _DefaultUrl.IndexOf("//", StringComparison.Ordinal) < 0))
712 return ("/" + _DefaultUrl);
713 return _DefaultUrl;
717 internal static string ReturnUrlVar {
718 get {
719 if (!String.IsNullOrEmpty(AppSettings.FormsAuthReturnUrlVar)) {
720 return AppSettings.FormsAuthReturnUrlVar;
723 return "ReturnUrl";
727 internal static string GetLoginPage(string extraQueryString) {
728 return GetLoginPage(extraQueryString, false);
730 internal static string GetLoginPage(string extraQueryString, bool reuseReturnUrl) {
731 HttpContext context = HttpContext.Current;
732 string loginUrl = FormsAuthentication.LoginUrl;
733 if (loginUrl.IndexOf('?') >= 0)
734 loginUrl = RemoveQueryStringVariableFromUrl(loginUrl, ReturnUrlVar);
735 int pos = loginUrl.IndexOf('?');
736 if (pos < 0)
737 loginUrl += "?";
738 else
739 if (pos < loginUrl.Length -1)
740 loginUrl += "&";
741 string returnUrl = null;
742 if (reuseReturnUrl) {
743 returnUrl = HttpUtility.UrlEncode( GetReturnUrl(false),
744 context.Request.QueryStringEncoding );
746 if (returnUrl == null)
747 returnUrl = HttpUtility.UrlEncode(context.Request.RawUrl, context.Request.ContentEncoding);
749 loginUrl += ReturnUrlVar + "=" + returnUrl;
750 if (!String.IsNullOrEmpty(extraQueryString)) {
751 loginUrl += "&" + extraQueryString;
753 return loginUrl;
757 public static void RedirectToLoginPage() {
758 RedirectToLoginPage(null);
762 public static void RedirectToLoginPage(string extraQueryString) {
763 HttpContext context = HttpContext.Current;
764 string loginUrl = GetLoginPage(extraQueryString);
765 context.Response.Redirect(loginUrl, false);
768 /////////////////////////////////////////////////////////////////////////////
769 /////////////////////////////////////////////////////////////////////////////
770 /////////////////////////////////////////////////////////////////////////////
771 // Private stuff
773 /////////////////////////////////////////////////////////////////////////////
774 // Config Tags
775 private const String CONFIG_DEFAULT_COOKIE = ".ASPXAUTH";
777 /////////////////////////////////////////////////////////////////////////////
778 // Private data
779 private static bool _Initialized;
780 private static String _FormsName;
781 //private static FormsProtectionEnum _Protection;
782 private static FormsProtectionEnum _Protection;
783 private static Int32 _Timeout;
784 private static String _FormsCookiePath;
785 private static bool _RequireSSL;
786 private static bool _SlidingExpiration;
787 private static string _LoginUrl;
788 private static string _DefaultUrl;
789 private static HttpCookieMode _CookieMode;
790 private static string _CookieDomain = null;
791 private static bool _EnableCrossAppRedirects;
792 private static TicketCompatibilityMode _TicketCompatibilityMode;
794 /////////////////////////////////////////////////////////////////////////////
795 private static byte[] MakeTicketIntoBinaryBlob(FormsAuthenticationTicket ticket) {
796 // None of the modes (Framework20 / Framework40 / beyond) support null values for these fields;
797 // they always eventually just returned a null value.
798 if (ticket.Name == null || ticket.UserData == null || ticket.CookiePath == null) {
799 return null;
802 // ** MSRC 11838 **
803 // Framework20 / Framework40 ticket generation modes are insecure. We should use a
804 // secure serialization mode by default.
805 if (!AppSettings.UseLegacyFormsAuthenticationTicketCompatibility) {
806 return FormsAuthenticationTicketSerializer.Serialize(ticket);
809 // ** MSRC 11838 **
810 // If we have reached this point of execution, the developer has explicitly elected
811 // to continue using the insecure code path instead of the secure one. We removed
812 // the Framework40 serialization mode, so everybody using the legacy code path is
813 // forced to Framework20.
815 byte [] bData = new byte[4096];
816 byte [] pBin = new byte[4];
817 long [] pDates = new long[2];
818 byte [] pNull = { 0, 0, 0 };
820 // DevDiv Bugs 137864: 8 bytes may not be enough random bits as the length should be equal to the
821 // key size. In CompatMode > Framework20SP1, use the IVType.Random feature instead of these 8 bytes,
822 // but still include empty 8 bytes for compat with webengine.dll, where CookieAuthConstructTicket is.
823 // Note that even in CompatMode = Framework20SP2 we fill 8 bytes with random data if the ticket
824 // is not going to be encrypted.
826 bool willEncrypt = (_Protection == FormsProtectionEnum.All || _Protection == FormsProtectionEnum.Encryption);
827 bool legacyPadding = !willEncrypt || (MachineKeySection.CompatMode == MachineKeyCompatibilityMode.Framework20SP1);
828 if (legacyPadding) {
829 // Fill the first 8 bytes of the blob with random bits
830 byte[] bRandom = new byte[8];
831 RNGCryptoServiceProvider randgen = new RNGCryptoServiceProvider();
832 randgen.GetBytes(bRandom);
833 Buffer.BlockCopy(bRandom, 0, bData, 0, 8);
835 else {
836 // use blank 8 bytes for compatibility with CookieAuthConstructTicket (do nothing)
839 pBin[0] = (byte) ticket.Version;
840 pBin[1] = (byte) (ticket.IsPersistent ? 1 : 0);
842 pDates[0] = ticket.IssueDate.ToFileTime();
843 pDates[1] = ticket.Expiration.ToFileTime();
845 int iRet = UnsafeNativeMethods.CookieAuthConstructTicket(
846 bData, bData.Length,
847 ticket.Name, ticket.UserData, ticket.CookiePath,
848 pBin, pDates);
850 if (iRet < 0)
851 return null;
853 byte[] ciphertext = new byte[iRet];
854 Buffer.BlockCopy(bData, 0, ciphertext, 0, iRet);
855 return ciphertext;
858 /////////////////////////////////////////////////////////////////////////////
859 /////////////////////////////////////////////////////////////////////////////
861 internal static string RemoveQueryStringVariableFromUrl(string strUrl, string QSVar) {
862 int posQ = strUrl.IndexOf('?');
863 if (posQ < 0)
864 return strUrl;
866 // Remove non-encoded QSVars
867 string amp = @"&";
868 string question = @"?";
870 string token = amp + QSVar + "=";
871 RemoveQSVar(ref strUrl, posQ, token, amp, amp.Length);
873 token = question + QSVar + "=";
874 RemoveQSVar(ref strUrl, posQ, token, amp, question.Length);
876 // Remove Url-enocoded strings
877 amp = HttpUtility.UrlEncode(@"&");
878 question = HttpUtility.UrlEncode(@"?");
880 token = amp + HttpUtility.UrlEncode(QSVar + "=");
881 RemoveQSVar(ref strUrl, posQ, token, amp, amp.Length);
883 token = question + HttpUtility.UrlEncode(QSVar + "=");
884 RemoveQSVar(ref strUrl, posQ, token, amp, question.Length);
885 return strUrl;
888 /////////////////////////////////////////////////////////////////////////////
889 /////////////////////////////////////////////////////////////////////////////
890 static private void RemoveQSVar(ref string strUrl, int posQ, string token, string sep, int lenAtStartToLeave)
892 for (int pos = strUrl.LastIndexOf(token, StringComparison.Ordinal); pos >= posQ; pos = strUrl.LastIndexOf(token, StringComparison.Ordinal))
894 int end = strUrl.IndexOf(sep, pos + token.Length, StringComparison.Ordinal) + sep.Length;
895 if (end < sep.Length || end >= strUrl.Length)
896 { // ending separator not found or nothing is at the end
897 strUrl = strUrl.Substring(0, pos);
899 else
901 strUrl = strUrl.Substring(0, pos + lenAtStartToLeave) + strUrl.Substring(end);
905 static private bool IsPathWithinAppRoot(HttpContext context, string path)
907 Uri absUri;
908 if (!Uri.TryCreate(path, UriKind.Absolute, out absUri))
909 return HttpRuntime.IsPathWithinAppRoot(path);
911 if (!absUri.IsLoopback && !string.Equals(context.Request.Url.Host, absUri.Host, StringComparison.OrdinalIgnoreCase))
912 return false; // different servers
914 return HttpRuntime.IsPathWithinAppRoot(absUri.AbsolutePath);