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 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.Util;
40 using System.Globalization;
42 namespace System.Web.Security
44 // CAS - no InheritanceDemand here as the class is sealed
45 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
46 public sealed class FormsAuthentication
48 static string authConfigPath = "system.web/authentication";
49 static string machineKeyConfigPath = "system.web/machineKey";
51 const string Forms_initialized = "Forms.initialized";
52 const string Forms_cookieName = "Forms.cookieName";
53 const string Forms_cookiePath = "Forms.cookiePath";
54 const string Forms_timeout = "Forms.timeout";
55 const string Forms_protection = "Forms.protection";
56 static bool initialized
59 object o = AppDomain.CurrentDomain.GetData (Forms_initialized);
60 return o != null ? (bool) o : false;
62 set { AppDomain.CurrentDomain.SetData (Forms_initialized, value); }
64 static string cookieName
66 get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookieName); }
67 set { AppDomain.CurrentDomain.SetData (Forms_cookieName, value); }
69 static string cookiePath
71 get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookiePath); }
72 set { AppDomain.CurrentDomain.SetData (Forms_cookiePath, value); }
77 object o = AppDomain.CurrentDomain.GetData (Forms_timeout);
78 return o != null ? (int) o : 0;
80 set { AppDomain.CurrentDomain.SetData (Forms_timeout, value); }
82 static FormsProtectionEnum protection
84 get { return (FormsProtectionEnum) AppDomain.CurrentDomain.GetData (Forms_protection); }
85 set { AppDomain.CurrentDomain.SetData (Forms_protection, value); }
87 static object locker = new object ();
89 static bool initialized;
90 static string cookieName;
91 static string cookiePath;
93 static FormsProtectionEnum protection;
94 static object locker = new object ();
98 const string Forms_requireSSL = "Forms.requireSSL";
99 const string Forms_slidingExpiration = "Forms.slidingExpiration";
101 static bool requireSSL
104 object o = AppDomain.CurrentDomain.GetData (Forms_requireSSL);
105 return o != null ? (bool) o : false;
107 set { AppDomain.CurrentDomain.SetData (Forms_requireSSL, value); }
109 static bool slidingExpiration
112 object o = AppDomain.CurrentDomain.GetData (Forms_slidingExpiration);
113 return o != null ? (bool) o : false;
115 set { AppDomain.CurrentDomain.SetData (Forms_slidingExpiration, value); }
118 static bool requireSSL;
119 static bool slidingExpiration;
124 const string Forms_cookie_domain = "Forms.cookie_domain";
125 const string Forms_cookie_mode = "Forms.cookie_mode";
126 const string Forms_cookies_supported = "Forms.cookies_supported";
127 const string Forms_default_url = "Forms.default_url";
128 const string Forms_enable_crossapp_redirects = "Forms.enable_crossapp_redirects";
129 const string Forms_login_url = "Forms.login_url";
130 static string cookie_domain
132 get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookie_domain); }
133 set { AppDomain.CurrentDomain.SetData (Forms_cookie_domain, value); }
135 static HttpCookieMode cookie_mode
137 get { return (HttpCookieMode) AppDomain.CurrentDomain.GetData (Forms_cookie_mode); }
138 set { AppDomain.CurrentDomain.SetData (Forms_cookie_mode, value); }
140 static bool cookies_supported
143 object o = AppDomain.CurrentDomain.GetData (Forms_cookies_supported);
144 return o != null ? (bool) o : false;
146 set { AppDomain.CurrentDomain.SetData (Forms_cookies_supported, value); }
148 static string default_url
150 get { return (string) AppDomain.CurrentDomain.GetData (Forms_default_url); }
151 set { AppDomain.CurrentDomain.SetData (Forms_default_url, value); }
153 static bool enable_crossapp_redirects
156 object o = AppDomain.CurrentDomain.GetData (Forms_enable_crossapp_redirects);
157 return o != null ? (bool) o : false;
159 set { AppDomain.CurrentDomain.SetData (Forms_enable_crossapp_redirects, value); }
161 static string login_url
163 get { return (string) AppDomain.CurrentDomain.GetData (Forms_login_url); }
164 set { AppDomain.CurrentDomain.SetData (Forms_login_url, value); }
167 static string cookie_domain;
168 static HttpCookieMode cookie_mode;
169 static bool cookies_supported;
170 static string default_url;
171 static bool enable_crossapp_redirects;
172 static string login_url;
175 // same names and order used in xsp
176 static string [] indexFiles = { "index.aspx",
182 public FormsAuthentication ()
186 public static bool Authenticate (string name, string password)
188 if (name == null || password == null)
192 HttpContext context = HttpContext.Current;
194 throw new HttpException ("Context is null!");
196 name = name.ToLower (Helpers.InvariantCulture);
198 AuthenticationSection section = (AuthenticationSection) WebConfigurationManager.GetSection (authConfigPath);
199 FormsAuthenticationCredentials config = section.Forms.Credentials;
200 FormsAuthenticationUser user = config.Users[name];
201 string stored = null;
204 stored = user.Password;
206 AuthConfig config = context.GetConfig (authConfigPath) as AuthConfig;
207 Hashtable users = config.CredentialUsers;
208 string stored = users [name] as string;
213 bool caseInsensitive = true;
214 switch (config.PasswordFormat) {
215 case FormsAuthPasswordFormat.Clear:
216 caseInsensitive = false;
219 case FormsAuthPasswordFormat.MD5:
220 password = HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.MD5);
222 case FormsAuthPasswordFormat.SHA1:
223 password = HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.SHA1);
227 return String.Compare (password, stored, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0;
230 static FormsAuthenticationTicket Decrypt2 (byte [] bytes)
232 if (protection == FormsProtectionEnum.None)
233 return FormsAuthenticationTicket.FromByteArray (bytes);
236 MachineKeySection config = (MachineKeySection) WebConfigurationManager.GetWebApplicationSection (machineKeyConfigPath);
238 MachineKeyConfig config = HttpContext.GetAppConfig (machineKeyConfigPath) as MachineKeyConfig;
241 byte [] result = null;
242 if (protection == FormsProtectionEnum.All) {
243 result = MachineKeySectionUtils.VerifyDecrypt (config, bytes);
244 } else if (protection == FormsProtectionEnum.Encryption) {
245 result = MachineKeySectionUtils.Decrypt (config, bytes);
246 } else if (protection == FormsProtectionEnum.Validation) {
247 result = MachineKeySectionUtils.Verify (config, bytes);
250 return FormsAuthenticationTicket.FromByteArray (result);
253 public static FormsAuthenticationTicket Decrypt (string encryptedTicket)
255 if (encryptedTicket == null || encryptedTicket == String.Empty)
256 throw new ArgumentException ("Invalid encrypted ticket", "encryptedTicket");
260 FormsAuthenticationTicket ticket;
261 byte [] bytes = Convert.FromBase64String (encryptedTicket);
264 ticket = Decrypt2 (bytes);
265 } catch (Exception) {
272 public static string Encrypt (FormsAuthenticationTicket ticket)
275 throw new ArgumentNullException ("ticket");
278 byte [] ticket_bytes = ticket.ToByteArray ();
279 if (protection == FormsProtectionEnum.None)
280 return Convert.ToBase64String (ticket_bytes);
282 byte [] result = null;
284 MachineKeySection config = (MachineKeySection) WebConfigurationManager.GetWebApplicationSection (machineKeyConfigPath);
286 MachineKeyConfig config = HttpContext.GetAppConfig (machineKeyConfigPath) as MachineKeyConfig;
288 if (protection == FormsProtectionEnum.All) {
289 result = MachineKeySectionUtils.EncryptSign (config, ticket_bytes);
290 } else if (protection == FormsProtectionEnum.Encryption) {
291 result = MachineKeySectionUtils.Encrypt (config, ticket_bytes);
292 } else if (protection == FormsProtectionEnum.Validation) {
293 result = MachineKeySectionUtils.Sign (config, ticket_bytes);
296 return Convert.ToBase64String (result);
299 public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie)
301 return GetAuthCookie (userName, createPersistentCookie, null);
304 public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
308 if (userName == null)
309 userName = String.Empty;
311 if (strCookiePath == null || strCookiePath.Length == 0)
312 strCookiePath = cookiePath;
314 DateTime now = DateTime.Now;
316 if (createPersistentCookie)
317 then = now.AddYears (50);
319 then = now.AddMinutes (timeout);
321 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket (1,
325 createPersistentCookie,
329 if (!createPersistentCookie)
330 then = DateTime.MinValue;
332 HttpCookie cookie = new HttpCookie (cookieName, Encrypt (ticket), strCookiePath, then);
334 cookie.Secure = true;
336 if (!String.IsNullOrEmpty (cookie_domain))
337 cookie.Domain = cookie_domain;
342 internal static string ReturnUrl
344 get { return HttpContext.Current.Request ["RETURNURL"]; }
347 public static string GetRedirectUrl (string userName, bool createPersistentCookie)
349 if (userName == null)
353 HttpRequest request = HttpContext.Current.Request;
354 string returnUrl = ReturnUrl;
355 if (returnUrl != null)
358 returnUrl = request.ApplicationPath;
359 string apppath = request.PhysicalApplicationPath;
362 foreach (string indexFile in indexFiles) {
363 string filePath = Path.Combine (apppath, indexFile);
364 if (File.Exists (filePath)) {
365 returnUrl = UrlUtils.Combine (returnUrl, indexFile);
372 returnUrl = UrlUtils.Combine (returnUrl, "index.aspx");
377 static string HashPasswordForStoringInConfigFile (string password, FormsAuthPasswordFormat passwordFormat)
379 if (password == null)
380 throw new ArgumentNullException ("password");
383 switch (passwordFormat) {
384 case FormsAuthPasswordFormat.MD5:
385 bytes = MD5.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
388 case FormsAuthPasswordFormat.SHA1:
389 bytes = SHA1.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
393 throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
396 return MachineKeySectionUtils.GetHexString (bytes);
399 public static string HashPasswordForStoringInConfigFile (string password, string passwordFormat)
401 if (password == null)
402 throw new ArgumentNullException ("password");
404 if (passwordFormat == null)
405 throw new ArgumentNullException ("passwordFormat");
407 if (String.Compare (passwordFormat, "MD5", true, Helpers.InvariantCulture) == 0) {
408 return HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.MD5);
409 } else if (String.Compare (passwordFormat, "SHA1", true, Helpers.InvariantCulture) == 0) {
410 return HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.SHA1);
412 throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
416 public static void Initialize ()
426 AuthenticationSection section = (AuthenticationSection)WebConfigurationManager.GetSection (authConfigPath);
427 FormsAuthenticationConfiguration config = section.Forms;
429 cookieName = config.Name;
430 timeout = (int)config.Timeout.TotalMinutes;
431 cookiePath = config.Path;
432 protection = config.Protection;
433 requireSSL = config.RequireSSL;
434 slidingExpiration = config.SlidingExpiration;
435 cookie_domain = config.Domain;
436 cookie_mode = config.Cookieless;
437 cookies_supported = true; /* XXX ? */
438 default_url = MapUrl(config.DefaultUrl);
439 enable_crossapp_redirects = config.EnableCrossAppRedirects;
440 login_url = MapUrl(config.LoginUrl);
442 HttpContext context = HttpContext.Current;
443 AuthConfig authConfig = context.GetConfig (authConfigPath) as AuthConfig;
444 if (authConfig != null) {
445 cookieName = authConfig.CookieName;
446 timeout = authConfig.Timeout;
447 cookiePath = authConfig.CookiePath;
448 protection = authConfig.Protection;
450 requireSSL = authConfig.RequireSSL;
451 slidingExpiration = authConfig.SlidingExpiration;
454 cookieName = ".MONOAUTH";
457 protection = FormsProtectionEnum.All;
459 slidingExpiration = true;
469 static string MapUrl (string url) {
470 if (UrlUtils.IsRelativeUrl (url))
471 return UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
473 return UrlUtils.ResolveVirtualPathFromAppAbsolute (url);
477 public static void RedirectFromLoginPage (string userName, bool createPersistentCookie)
479 RedirectFromLoginPage (userName, createPersistentCookie, null);
482 public static void RedirectFromLoginPage (string userName, bool createPersistentCookie, string strCookiePath)
484 if (userName == null)
488 SetAuthCookie (userName, createPersistentCookie, strCookiePath);
489 Redirect (GetRedirectUrl (userName, createPersistentCookie), false);
492 public static FormsAuthenticationTicket RenewTicketIfOld (FormsAuthenticationTicket tOld)
497 DateTime now = DateTime.Now;
498 TimeSpan toIssue = now - tOld.IssueDate;
499 TimeSpan toExpiration = tOld.Expiration - now;
500 if (toExpiration > toIssue)
503 FormsAuthenticationTicket tNew = tOld.Clone ();
504 tNew.SetDates (now, now + (tOld.Expiration - tOld.IssueDate));
508 public static void SetAuthCookie (string userName, bool createPersistentCookie)
511 SetAuthCookie (userName, createPersistentCookie, cookiePath);
514 public static void SetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
516 HttpContext context = HttpContext.Current;
518 throw new HttpException ("Context is null!");
520 HttpResponse response = context.Response;
521 if (response == null)
522 throw new HttpException ("Response is null!");
524 response.Cookies.Add (GetAuthCookie (userName, createPersistentCookie, strCookiePath));
527 public static void SignOut ()
531 HttpContext context = HttpContext.Current;
533 throw new HttpException ("Context is null!");
535 HttpResponse response = context.Response;
536 if (response == null)
537 throw new HttpException ("Response is null!");
539 HttpCookieCollection cc = response.Cookies;
540 cc.Remove (cookieName);
541 HttpCookie expiration_cookie = new HttpCookie (cookieName, String.Empty);
542 expiration_cookie.Expires = new DateTime (1999, 10, 12);
543 expiration_cookie.Path = cookiePath;
545 if (!String.IsNullOrEmpty (cookie_domain))
546 expiration_cookie.Domain = cookie_domain;
548 cc.Add (expiration_cookie);
551 Roles.DeleteCookie ();
555 public static string FormsCookieName
563 public static string FormsCookiePath
571 public static bool RequireSSL {
578 public static bool SlidingExpiration {
581 return slidingExpiration;
587 public static string CookieDomain {
588 get { Initialize (); return cookie_domain; }
591 public static HttpCookieMode CookieMode {
592 get { Initialize (); return cookie_mode; }
595 public static bool CookiesSupported {
596 get { Initialize (); return cookies_supported; }
599 public static string DefaultUrl {
600 get { Initialize (); return default_url; }
603 public static bool EnableCrossAppRedirects {
604 get { Initialize (); return enable_crossapp_redirects; }
607 public static string LoginUrl {
608 get { Initialize (); return login_url; }
611 public static void RedirectToLoginPage ()
616 [MonoTODO ("needs more tests")]
617 public static void RedirectToLoginPage (string extraQueryString)
619 // TODO: if ? is in LoginUrl (legal?), ? in query (legal?) ...
620 Redirect (LoginUrl + "?" + extraQueryString);
623 static void Redirect (string url)
625 HttpContext.Current.Response.Redirect (url);
629 static void Redirect (string url, bool end)
631 HttpContext.Current.Response.Redirect (url, end);