2 // System.Web.Security.FormsAuthentication
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
8 // Copyright (c) 2005-2010 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections;
34 using System.Security.Cryptography;
35 using System.Security.Permissions;
38 using System.Web.Configuration;
39 using System.Web.Compilation;
40 using System.Web.Util;
41 using System.Globalization;
42 using System.Collections.Specialized;
44 namespace System.Web.Security
46 // CAS - no InheritanceDemand here as the class is sealed
47 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48 public sealed class FormsAuthentication
50 static string authConfigPath = "system.web/authentication";
51 static string machineKeyConfigPath = "system.web/machineKey";
52 static object locker = new object ();
54 const string Forms_initialized = "Forms.initialized";
55 const string Forms_cookieName = "Forms.cookieName";
56 const string Forms_cookiePath = "Forms.cookiePath";
57 const string Forms_timeout = "Forms.timeout";
58 const string Forms_protection = "Forms.protection";
59 static bool initialized
62 object o = AppDomain.CurrentDomain.GetData (Forms_initialized);
63 return o != null ? (bool) o : false;
65 set { AppDomain.CurrentDomain.SetData (Forms_initialized, value); }
67 static string cookieName
69 get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookieName); }
70 set { AppDomain.CurrentDomain.SetData (Forms_cookieName, value); }
72 static string cookiePath
74 get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookiePath); }
75 set { AppDomain.CurrentDomain.SetData (Forms_cookiePath, value); }
80 object o = AppDomain.CurrentDomain.GetData (Forms_timeout);
81 return o != null ? (int) o : 0;
83 set { AppDomain.CurrentDomain.SetData (Forms_timeout, value); }
85 static FormsProtectionEnum protection
87 get { return (FormsProtectionEnum) AppDomain.CurrentDomain.GetData (Forms_protection); }
88 set { AppDomain.CurrentDomain.SetData (Forms_protection, value); }
91 const string Forms_requireSSL = "Forms.requireSSL";
92 const string Forms_slidingExpiration = "Forms.slidingExpiration";
94 static bool requireSSL
97 object o = AppDomain.CurrentDomain.GetData (Forms_requireSSL);
98 return o != null ? (bool) o : false;
100 set { AppDomain.CurrentDomain.SetData (Forms_requireSSL, value); }
102 static bool slidingExpiration
105 object o = AppDomain.CurrentDomain.GetData (Forms_slidingExpiration);
106 return o != null ? (bool) o : false;
108 set { AppDomain.CurrentDomain.SetData (Forms_slidingExpiration, value); }
111 const string Forms_cookie_domain = "Forms.cookie_domain";
112 const string Forms_cookie_mode = "Forms.cookie_mode";
113 const string Forms_cookies_supported = "Forms.cookies_supported";
114 const string Forms_default_url = "Forms.default_url";
115 const string Forms_enable_crossapp_redirects = "Forms.enable_crossapp_redirects";
116 const string Forms_login_url = "Forms.login_url";
117 static string cookie_domain
119 get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookie_domain); }
120 set { AppDomain.CurrentDomain.SetData (Forms_cookie_domain, value); }
122 static HttpCookieMode cookie_mode
124 get { return (HttpCookieMode) AppDomain.CurrentDomain.GetData (Forms_cookie_mode); }
125 set { AppDomain.CurrentDomain.SetData (Forms_cookie_mode, value); }
127 static bool cookies_supported
130 object o = AppDomain.CurrentDomain.GetData (Forms_cookies_supported);
131 return o != null ? (bool) o : false;
133 set { AppDomain.CurrentDomain.SetData (Forms_cookies_supported, value); }
135 static string default_url
137 get { return (string) AppDomain.CurrentDomain.GetData (Forms_default_url); }
138 set { AppDomain.CurrentDomain.SetData (Forms_default_url, value); }
140 static bool enable_crossapp_redirects
143 object o = AppDomain.CurrentDomain.GetData (Forms_enable_crossapp_redirects);
144 return o != null ? (bool) o : false;
146 set { AppDomain.CurrentDomain.SetData (Forms_enable_crossapp_redirects, value); }
148 static string login_url
150 get { return (string) AppDomain.CurrentDomain.GetData (Forms_login_url); }
151 set { AppDomain.CurrentDomain.SetData (Forms_login_url, value); }
154 static bool initialized;
155 static string cookieName;
156 static string cookiePath;
158 static FormsProtectionEnum protection;
159 static bool requireSSL;
160 static bool slidingExpiration;
161 static string cookie_domain;
162 static HttpCookieMode cookie_mode;
163 static bool cookies_supported;
164 static string default_url;
165 static bool enable_crossapp_redirects;
166 static string login_url;
168 // same names and order used in xsp
169 static string [] indexFiles = { "index.aspx",
175 public static void EnableFormsAuthentication (NameValueCollection configurationData)
177 BuildManager.AssertPreStartMethodsRunning ();
178 if (configurationData == null || configurationData.Count == 0)
181 string value = configurationData ["loginUrl"];
182 if (!String.IsNullOrEmpty (value))
185 value = configurationData ["defaultUrl"];
186 if (!String.IsNullOrEmpty (value))
190 public FormsAuthentication ()
194 public static bool Authenticate (string name, string password)
196 if (name == null || password == null)
200 HttpContext context = HttpContext.Current;
202 throw new HttpException ("Context is null!");
204 name = name.ToLower (Helpers.InvariantCulture);
206 AuthenticationSection section = (AuthenticationSection) WebConfigurationManager.GetSection (authConfigPath);
207 FormsAuthenticationCredentials config = section.Forms.Credentials;
208 FormsAuthenticationUser user = config.Users[name];
209 string stored = null;
212 stored = user.Password;
217 bool caseInsensitive = true;
218 switch (config.PasswordFormat) {
219 case FormsAuthPasswordFormat.Clear:
220 caseInsensitive = false;
223 case FormsAuthPasswordFormat.MD5:
224 password = HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.MD5);
226 case FormsAuthPasswordFormat.SHA1:
227 password = HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.SHA1);
231 return String.Compare (password, stored, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0;
234 static FormsAuthenticationTicket Decrypt2 (byte [] bytes)
236 if (protection == FormsProtectionEnum.None)
237 return FormsAuthenticationTicket.FromByteArray (bytes);
239 MachineKeySection config = (MachineKeySection) WebConfigurationManager.GetWebApplicationSection (machineKeyConfigPath);
240 byte [] result = null;
241 if (protection == FormsProtectionEnum.All) {
242 result = MachineKeySectionUtils.VerifyDecrypt (config, bytes);
243 } else if (protection == FormsProtectionEnum.Encryption) {
244 result = MachineKeySectionUtils.Decrypt (config, bytes);
245 } else if (protection == FormsProtectionEnum.Validation) {
246 result = MachineKeySectionUtils.Verify (config, bytes);
249 return FormsAuthenticationTicket.FromByteArray (result);
252 public static FormsAuthenticationTicket Decrypt (string encryptedTicket)
254 if (String.IsNullOrEmpty (encryptedTicket))
255 throw new ArgumentException ("Invalid encrypted ticket", "encryptedTicket");
259 FormsAuthenticationTicket ticket;
260 byte [] bytes = Convert.FromBase64String (encryptedTicket);
263 ticket = Decrypt2 (bytes);
264 } catch (Exception) {
271 public static string Encrypt (FormsAuthenticationTicket ticket)
274 throw new ArgumentNullException ("ticket");
277 byte [] ticket_bytes = ticket.ToByteArray ();
278 if (protection == FormsProtectionEnum.None)
279 return Convert.ToBase64String (ticket_bytes);
281 byte [] result = null;
282 MachineKeySection config = (MachineKeySection) WebConfigurationManager.GetWebApplicationSection (machineKeyConfigPath);
284 if (protection == FormsProtectionEnum.All) {
285 result = MachineKeySectionUtils.EncryptSign (config, ticket_bytes);
286 } else if (protection == FormsProtectionEnum.Encryption) {
287 result = MachineKeySectionUtils.Encrypt (config, ticket_bytes);
288 } else if (protection == FormsProtectionEnum.Validation) {
289 result = MachineKeySectionUtils.Sign (config, ticket_bytes);
292 return Convert.ToBase64String (result);
295 public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie)
297 return GetAuthCookie (userName, createPersistentCookie, null);
300 public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
304 if (userName == null)
305 userName = String.Empty;
307 if (strCookiePath == null || strCookiePath.Length == 0)
308 strCookiePath = cookiePath;
310 DateTime now = DateTime.Now;
312 if (createPersistentCookie)
313 then = now.AddYears (50);
315 then = now.AddMinutes (timeout);
317 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket (1,
321 createPersistentCookie,
325 if (!createPersistentCookie)
326 then = DateTime.MinValue;
328 HttpCookie cookie = new HttpCookie (cookieName, Encrypt (ticket), strCookiePath, then);
330 cookie.Secure = true;
331 if (!String.IsNullOrEmpty (cookie_domain))
332 cookie.Domain = cookie_domain;
337 internal static string ReturnUrl {
338 get { return HttpContext.Current.Request ["RETURNURL"]; }
341 public static string GetRedirectUrl (string userName, bool createPersistentCookie)
343 if (userName == null)
347 HttpRequest request = HttpContext.Current.Request;
348 string returnUrl = ReturnUrl;
349 if (returnUrl != null)
352 returnUrl = request.ApplicationPath;
353 string apppath = request.PhysicalApplicationPath;
356 foreach (string indexFile in indexFiles) {
357 string filePath = Path.Combine (apppath, indexFile);
358 if (File.Exists (filePath)) {
359 returnUrl = UrlUtils.Combine (returnUrl, indexFile);
366 returnUrl = UrlUtils.Combine (returnUrl, "index.aspx");
371 static string HashPasswordForStoringInConfigFile (string password, FormsAuthPasswordFormat passwordFormat)
373 if (password == null)
374 throw new ArgumentNullException ("password");
377 switch (passwordFormat) {
378 case FormsAuthPasswordFormat.MD5:
379 bytes = MD5.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
382 case FormsAuthPasswordFormat.SHA1:
383 bytes = SHA1.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
387 throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
390 return MachineKeySectionUtils.GetHexString (bytes);
393 public static string HashPasswordForStoringInConfigFile (string password, string passwordFormat)
395 if (password == null)
396 throw new ArgumentNullException ("password");
398 if (passwordFormat == null)
399 throw new ArgumentNullException ("passwordFormat");
401 if (String.Compare (passwordFormat, "MD5", StringComparison.OrdinalIgnoreCase) == 0) {
402 return HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.MD5);
403 } else if (String.Compare (passwordFormat, "SHA1", StringComparison.OrdinalIgnoreCase) == 0) {
404 return HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.SHA1);
406 throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
410 public static void Initialize ()
419 AuthenticationSection section = (AuthenticationSection)WebConfigurationManager.GetSection (authConfigPath);
420 FormsAuthenticationConfiguration config = section.Forms;
422 cookieName = config.Name;
423 timeout = (int)config.Timeout.TotalMinutes;
424 cookiePath = config.Path;
425 protection = config.Protection;
426 requireSSL = config.RequireSSL;
427 slidingExpiration = config.SlidingExpiration;
428 cookie_domain = config.Domain;
429 cookie_mode = config.Cookieless;
430 cookies_supported = true; /* XXX ? */
432 if (!String.IsNullOrEmpty (default_url))
433 default_url = MapUrl (default_url);
436 default_url = MapUrl(config.DefaultUrl);
437 enable_crossapp_redirects = config.EnableCrossAppRedirects;
439 if (!String.IsNullOrEmpty (login_url))
440 login_url = MapUrl (login_url);
443 login_url = MapUrl(config.LoginUrl);
449 static string MapUrl (string url) {
450 if (UrlUtils.IsRelativeUrl (url))
451 return UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
453 return UrlUtils.ResolveVirtualPathFromAppAbsolute (url);
456 public static void RedirectFromLoginPage (string userName, bool createPersistentCookie)
458 RedirectFromLoginPage (userName, createPersistentCookie, null);
461 public static void RedirectFromLoginPage (string userName, bool createPersistentCookie, string strCookiePath)
463 if (userName == null)
467 SetAuthCookie (userName, createPersistentCookie, strCookiePath);
468 Redirect (GetRedirectUrl (userName, createPersistentCookie), false);
471 public static FormsAuthenticationTicket RenewTicketIfOld (FormsAuthenticationTicket tOld)
476 DateTime now = DateTime.Now;
477 TimeSpan toIssue = now - tOld.IssueDate;
478 TimeSpan toExpiration = tOld.Expiration - now;
479 if (toExpiration > toIssue)
482 FormsAuthenticationTicket tNew = tOld.Clone ();
483 tNew.SetDates (now, now + (tOld.Expiration - tOld.IssueDate));
487 public static void SetAuthCookie (string userName, bool createPersistentCookie)
490 SetAuthCookie (userName, createPersistentCookie, cookiePath);
493 public static void SetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
495 HttpContext context = HttpContext.Current;
497 throw new HttpException ("Context is null!");
499 HttpResponse response = context.Response;
500 if (response == null)
501 throw new HttpException ("Response is null!");
503 response.Cookies.Add (GetAuthCookie (userName, createPersistentCookie, strCookiePath));
506 public static void SignOut ()
510 HttpContext context = HttpContext.Current;
512 throw new HttpException ("Context is null!");
514 HttpResponse response = context.Response;
515 if (response == null)
516 throw new HttpException ("Response is null!");
518 HttpCookieCollection cc = response.Cookies;
519 cc.Remove (cookieName);
520 HttpCookie expiration_cookie = new HttpCookie (cookieName, String.Empty);
521 expiration_cookie.Expires = new DateTime (1999, 10, 12);
522 expiration_cookie.Path = cookiePath;
523 if (!String.IsNullOrEmpty (cookie_domain))
524 expiration_cookie.Domain = cookie_domain;
525 cc.Add (expiration_cookie);
526 Roles.DeleteCookie ();
529 public static string FormsCookieName
537 public static string FormsCookiePath
545 public static bool RequireSSL {
552 public static bool SlidingExpiration {
555 return slidingExpiration;
559 public static string CookieDomain {
560 get { Initialize (); return cookie_domain; }
563 public static HttpCookieMode CookieMode {
564 get { Initialize (); return cookie_mode; }
567 public static bool CookiesSupported {
568 get { Initialize (); return cookies_supported; }
571 public static string DefaultUrl {
572 get { Initialize (); return default_url; }
575 public static bool EnableCrossAppRedirects {
576 get { Initialize (); return enable_crossapp_redirects; }
579 public static string LoginUrl {
580 get { Initialize (); return login_url; }
583 public static void RedirectToLoginPage ()
588 [MonoTODO ("needs more tests")]
589 public static void RedirectToLoginPage (string extraQueryString)
591 // TODO: if ? is in LoginUrl (legal?), ? in query (legal?) ...
592 Redirect (LoginUrl + "?" + extraQueryString);
595 static void Redirect (string url)
597 HttpContext.Current.Response.Redirect (url);
600 static void Redirect (string url, bool end)
602 HttpContext.Current.Response.Redirect (url, end);