2
0

FormsAuthentication.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. //
  2. // System.Web.Security.FormsAuthentication
  3. //
  4. // Authors:
  5. // Gonzalo Paniagua Javier ([email protected])
  6. //
  7. // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
  8. // Copyright (c) 2005 Novell, Inc (http://www.novell.com)
  9. //
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System.Collections;
  31. using System.IO;
  32. using System.Security.Cryptography;
  33. using System.Text;
  34. using System.Web;
  35. using System.Web.Configuration;
  36. using System.Web.Util;
  37. namespace System.Web.Security
  38. {
  39. public sealed class FormsAuthentication
  40. {
  41. const int MD5_hash_size = 16;
  42. const int SHA1_hash_size = 20;
  43. static string authConfigPath = "system.web/authentication";
  44. static bool initialized;
  45. static string cookieName;
  46. static string cookiePath;
  47. static int timeout;
  48. static FormsProtectionEnum protection;
  49. static object locker = new object ();
  50. static byte [] init_vector; // initialization vector used for 3DES
  51. #if NET_1_1
  52. static bool requireSSL;
  53. static bool slidingExpiration;
  54. #endif
  55. #if NET_2_0
  56. static string cookie_domain;
  57. static HttpCookieMode cookie_mode;
  58. static bool cookies_supported;
  59. static string default_url;
  60. static bool enable_crossapp_redirects;
  61. static string login_url;
  62. #endif
  63. // same names and order used in xsp
  64. static string [] indexFiles = { "index.aspx",
  65. "Default.aspx",
  66. "default.aspx",
  67. "index.html",
  68. "index.htm" };
  69. #if NET_2_0
  70. [Obsolete]
  71. #endif
  72. public FormsAuthentication ()
  73. {
  74. }
  75. public static bool Authenticate (string name, string password)
  76. {
  77. if (name == null || password == null)
  78. return false;
  79. Initialize ();
  80. HttpContext context = HttpContext.Current;
  81. if (context == null)
  82. throw new HttpException ("Context is null!");
  83. AuthConfig config = context.GetConfig (authConfigPath) as AuthConfig;
  84. Hashtable users = config.CredentialUsers;
  85. string stored = users [name] as string;
  86. if (stored == null)
  87. return false;
  88. switch (config.PasswordFormat) {
  89. case FormsAuthPasswordFormat.Clear:
  90. /* Do nothing */
  91. break;
  92. case FormsAuthPasswordFormat.MD5:
  93. password = HashPasswordForStoringInConfigFile (password, "MD5");
  94. break;
  95. case FormsAuthPasswordFormat.SHA1:
  96. password = HashPasswordForStoringInConfigFile (password, "SHA1");
  97. break;
  98. }
  99. return (password == stored);
  100. }
  101. static FormsAuthenticationTicket Decrypt2 (byte [] bytes)
  102. {
  103. if (protection == FormsProtectionEnum.None)
  104. return FormsAuthenticationTicket.FromByteArray (bytes);
  105. MachineKeyConfig config = HttpContext.GetAppConfig ("system.web/machineKey") as MachineKeyConfig;
  106. bool all = (protection == FormsProtectionEnum.All);
  107. byte [] result = bytes;
  108. if (all || protection == FormsProtectionEnum.Encryption) {
  109. ICryptoTransform decryptor;
  110. decryptor = TripleDES.Create ().CreateDecryptor (config.DecryptionKey192Bits, init_vector);
  111. result = decryptor.TransformFinalBlock (bytes, 0, bytes.Length);
  112. bytes = null;
  113. }
  114. if (all || protection == FormsProtectionEnum.Validation) {
  115. int count;
  116. if (config.ValidationType == MachineKeyValidation.MD5)
  117. count = MD5_hash_size;
  118. else
  119. count = SHA1_hash_size; // 3DES and SHA1
  120. byte [] vk = config.ValidationKey;
  121. byte [] mix = new byte [result.Length - count + vk.Length];
  122. Buffer.BlockCopy (result, 0, mix, 0, result.Length - count);
  123. Buffer.BlockCopy (vk, 0, mix, result.Length - count, vk.Length);
  124. byte [] hash = null;
  125. switch (config.ValidationType) {
  126. case MachineKeyValidation.MD5:
  127. hash = MD5.Create ().ComputeHash (mix);
  128. break;
  129. // From MS docs: "When 3DES is specified, forms authentication defaults to SHA1"
  130. case MachineKeyValidation.TripleDES:
  131. case MachineKeyValidation.SHA1:
  132. hash = SHA1.Create ().ComputeHash (mix);
  133. break;
  134. }
  135. if (result.Length < count)
  136. throw new ArgumentException ("Error validating ticket (length).", "encryptedTicket");
  137. int i, k;
  138. for (i = result.Length - count, k = 0; k < count; i++, k++) {
  139. if (result [i] != hash [k])
  140. throw new ArgumentException ("Error validating ticket.", "encryptedTicket");
  141. }
  142. }
  143. return FormsAuthenticationTicket.FromByteArray (result);
  144. }
  145. public static FormsAuthenticationTicket Decrypt (string encryptedTicket)
  146. {
  147. if (encryptedTicket == null || encryptedTicket == String.Empty)
  148. throw new ArgumentException ("Invalid encrypted ticket", "encryptedTicket");
  149. Initialize ();
  150. FormsAuthenticationTicket ticket;
  151. byte [] bytes = MachineKeyConfig.GetBytes (encryptedTicket, encryptedTicket.Length);
  152. try {
  153. ticket = Decrypt2 (bytes);
  154. } catch (Exception) {
  155. ticket = null;
  156. }
  157. return ticket;
  158. }
  159. public static string Encrypt (FormsAuthenticationTicket ticket)
  160. {
  161. if (ticket == null)
  162. throw new ArgumentNullException ("ticket");
  163. Initialize ();
  164. byte [] ticket_bytes = ticket.ToByteArray ();
  165. if (protection == FormsProtectionEnum.None)
  166. return GetHexString (ticket_bytes);
  167. byte [] result = ticket_bytes;
  168. MachineKeyConfig config = HttpContext.GetAppConfig ("system.web/machineKey") as MachineKeyConfig;
  169. bool all = (protection == FormsProtectionEnum.All);
  170. if (all || protection == FormsProtectionEnum.Validation) {
  171. byte [] valid_bytes = null;
  172. byte [] vk = config.ValidationKey;
  173. byte [] mix = new byte [ticket_bytes.Length + vk.Length];
  174. Buffer.BlockCopy (ticket_bytes, 0, mix, 0, ticket_bytes.Length);
  175. Buffer.BlockCopy (vk, 0, mix, result.Length, vk.Length);
  176. switch (config.ValidationType) {
  177. case MachineKeyValidation.MD5:
  178. valid_bytes = MD5.Create ().ComputeHash (mix);
  179. break;
  180. // From MS docs: "When 3DES is specified, forms authentication defaults to SHA1"
  181. case MachineKeyValidation.TripleDES:
  182. case MachineKeyValidation.SHA1:
  183. valid_bytes = SHA1.Create ().ComputeHash (mix);
  184. break;
  185. }
  186. int tlen = ticket_bytes.Length;
  187. int vlen = valid_bytes.Length;
  188. result = new byte [tlen + vlen];
  189. Buffer.BlockCopy (ticket_bytes, 0, result, 0, tlen);
  190. Buffer.BlockCopy (valid_bytes, 0, result, tlen, vlen);
  191. }
  192. if (all || protection == FormsProtectionEnum.Encryption) {
  193. ICryptoTransform encryptor;
  194. encryptor = TripleDES.Create ().CreateEncryptor (config.DecryptionKey192Bits, init_vector);
  195. result = encryptor.TransformFinalBlock (result, 0, result.Length);
  196. }
  197. return GetHexString (result);
  198. }
  199. public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie)
  200. {
  201. return GetAuthCookie (userName, createPersistentCookie, null);
  202. }
  203. public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
  204. {
  205. Initialize ();
  206. if (userName == null)
  207. userName = String.Empty;
  208. if (strCookiePath == null || strCookiePath.Length == 0)
  209. strCookiePath = cookiePath;
  210. DateTime now = DateTime.Now;
  211. DateTime then;
  212. if (createPersistentCookie)
  213. then = now.AddYears (50);
  214. else
  215. then = now.AddMinutes (timeout);
  216. FormsAuthenticationTicket ticket = new FormsAuthenticationTicket (1,
  217. userName,
  218. now,
  219. then,
  220. createPersistentCookie,
  221. String.Empty,
  222. cookiePath);
  223. if (!createPersistentCookie)
  224. then = DateTime.MinValue;
  225. return new HttpCookie (cookieName, Encrypt (ticket), strCookiePath, then);
  226. }
  227. public static string GetRedirectUrl (string userName, bool createPersistentCookie)
  228. {
  229. if (userName == null)
  230. return null;
  231. Initialize ();
  232. HttpRequest request = HttpContext.Current.Request;
  233. string returnUrl = request ["RETURNURL"];
  234. if (returnUrl != null)
  235. return returnUrl;
  236. returnUrl = request.ApplicationPath;
  237. string apppath = request.PhysicalApplicationPath;
  238. bool found = false;
  239. foreach (string indexFile in indexFiles) {
  240. string filePath = Path.Combine (apppath, indexFile);
  241. if (File.Exists (filePath)) {
  242. returnUrl = UrlUtils.Combine (returnUrl, indexFile);
  243. found = true;
  244. break;
  245. }
  246. }
  247. if (!found)
  248. returnUrl = UrlUtils.Combine (returnUrl, "index.aspx");
  249. return returnUrl;
  250. }
  251. static string GetHexString (byte [] bytes)
  252. {
  253. StringBuilder result = new StringBuilder (bytes.Length * 2);
  254. foreach (byte b in bytes)
  255. result.AppendFormat ("{0:X2}", (int) b);
  256. return result.ToString ();
  257. }
  258. public static string HashPasswordForStoringInConfigFile (string password, string passwordFormat)
  259. {
  260. if (password == null)
  261. throw new ArgumentNullException ("password");
  262. if (passwordFormat == null)
  263. throw new ArgumentNullException ("passwordFormat");
  264. byte [] bytes;
  265. if (String.Compare (passwordFormat, "MD5", true) == 0) {
  266. bytes = MD5.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
  267. } else if (String.Compare (passwordFormat, "SHA1", true) == 0) {
  268. bytes = SHA1.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
  269. } else {
  270. throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
  271. }
  272. return GetHexString (bytes);
  273. }
  274. public static void Initialize ()
  275. {
  276. if (initialized)
  277. return;
  278. lock (locker) {
  279. if (initialized)
  280. return;
  281. HttpContext context = HttpContext.Current;
  282. #if NET_2_0
  283. AuthConfig authConfig = null;
  284. if (context != null)
  285. authConfig = context.GetConfig (authConfigPath) as AuthConfig;
  286. #else
  287. AuthConfig authConfig = context.GetConfig (authConfigPath) as AuthConfig;
  288. #endif
  289. if (authConfig != null) {
  290. cookieName = authConfig.CookieName;
  291. timeout = authConfig.Timeout;
  292. cookiePath = authConfig.CookiePath;
  293. protection = authConfig.Protection;
  294. #if NET_1_1
  295. requireSSL = authConfig.RequireSSL;
  296. slidingExpiration = authConfig.SlidingExpiration;
  297. #endif
  298. #if NET_2_0
  299. cookie_domain = authConfig.CookieDomain;
  300. cookie_mode = authConfig.CookieMode;
  301. cookies_supported = authConfig.CookiesSupported;
  302. default_url = authConfig.DefaultUrl;
  303. enable_crossapp_redirects = authConfig.EnableCrossAppRedirects;
  304. login_url = authConfig.LoginUrl;
  305. #endif
  306. } else {
  307. cookieName = ".MONOAUTH";
  308. timeout = 30;
  309. cookiePath = "/";
  310. protection = FormsProtectionEnum.All;
  311. #if NET_1_1
  312. slidingExpiration = true;
  313. #endif
  314. #if NET_2_0
  315. cookie_domain = String.Empty;
  316. cookie_mode = HttpCookieMode.UseDeviceProfile;
  317. cookies_supported = true;
  318. default_url = "/default.aspx";
  319. login_url = "/login.aspx";
  320. #endif
  321. }
  322. // IV is 8 bytes long for 3DES
  323. init_vector = new byte [8];
  324. int len = cookieName.Length;
  325. for (int i = 0; i < 8; i++) {
  326. if (i >= len)
  327. break;
  328. init_vector [i] = (byte) cookieName [i];
  329. }
  330. initialized = true;
  331. }
  332. }
  333. public static void RedirectFromLoginPage (string userName, bool createPersistentCookie)
  334. {
  335. RedirectFromLoginPage (userName, createPersistentCookie, null);
  336. }
  337. public static void RedirectFromLoginPage (string userName, bool createPersistentCookie, string strCookiePath)
  338. {
  339. if (userName == null)
  340. return;
  341. Initialize ();
  342. SetAuthCookie (userName, createPersistentCookie, strCookiePath);
  343. Redirect (GetRedirectUrl (userName, createPersistentCookie));
  344. }
  345. public static FormsAuthenticationTicket RenewTicketIfOld (FormsAuthenticationTicket tOld)
  346. {
  347. if (tOld == null)
  348. return null;
  349. DateTime now = DateTime.Now;
  350. TimeSpan toIssue = now - tOld.IssueDate;
  351. TimeSpan toExpiration = tOld.Expiration - now;
  352. if (toExpiration > toIssue)
  353. return tOld;
  354. FormsAuthenticationTicket tNew = tOld.Clone ();
  355. tNew.SetDates (now, now + (tOld.Expiration - tOld.IssueDate));
  356. return tNew;
  357. }
  358. public static void SetAuthCookie (string userName, bool createPersistentCookie)
  359. {
  360. Initialize ();
  361. SetAuthCookie (userName, createPersistentCookie, cookiePath);
  362. }
  363. public static void SetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
  364. {
  365. HttpContext context = HttpContext.Current;
  366. if (context == null)
  367. throw new HttpException ("Context is null!");
  368. HttpResponse response = context.Response;
  369. if (response == null)
  370. throw new HttpException ("Response is null!");
  371. response.Cookies.Add (GetAuthCookie (userName, createPersistentCookie, strCookiePath));
  372. }
  373. public static void SignOut ()
  374. {
  375. Initialize ();
  376. HttpContext context = HttpContext.Current;
  377. if (context == null)
  378. throw new HttpException ("Context is null!");
  379. HttpResponse response = context.Response;
  380. if (response == null)
  381. throw new HttpException ("Response is null!");
  382. HttpCookieCollection cc = response.Cookies;
  383. cc.Remove (cookieName);
  384. HttpCookie expiration_cookie = new HttpCookie (cookieName, "");
  385. expiration_cookie.Expires = new DateTime (1999, 10, 12);
  386. expiration_cookie.Path = cookiePath;
  387. cc.Add (expiration_cookie);
  388. }
  389. public static string FormsCookieName
  390. {
  391. get {
  392. Initialize ();
  393. return cookieName;
  394. }
  395. }
  396. public static string FormsCookiePath
  397. {
  398. get {
  399. Initialize ();
  400. return cookiePath;
  401. }
  402. }
  403. #if NET_1_1
  404. public static bool RequireSSL {
  405. get {
  406. Initialize ();
  407. return requireSSL;
  408. }
  409. }
  410. public static bool SlidingExpiration {
  411. get {
  412. Initialize ();
  413. return slidingExpiration;
  414. }
  415. }
  416. #endif
  417. #if NET_2_0
  418. public static string CookieDomain {
  419. get { return cookie_domain; }
  420. }
  421. public static HttpCookieMode CookieMode {
  422. get { return cookie_mode; }
  423. }
  424. public static bool CookiesSupported {
  425. get { return cookies_supported; }
  426. }
  427. public static string DefaultUrl {
  428. get { return default_url; }
  429. }
  430. public static bool EnableCrossAppRedirects {
  431. get { return enable_crossapp_redirects; }
  432. }
  433. public static string LoginUrl {
  434. get { return login_url; }
  435. }
  436. public static void RedirectToLoginPage ()
  437. {
  438. Redirect (LoginUrl);
  439. }
  440. [MonoTODO ("needs more tests")]
  441. public static void RedirectToLoginPage (string extraQueryString)
  442. {
  443. // TODO: if ? is in LoginUrl (legal?), ? in query (legal?) ...
  444. Redirect (LoginUrl + "?" + extraQueryString);
  445. }
  446. #endif
  447. private static void Redirect (string url)
  448. {
  449. HttpContext.Current.Response.Redirect (url);
  450. }
  451. }
  452. }