PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/XmlMembership.Provider/XmlMembershipProvider.cs

https://github.com/dcargnino/XmlMembershipProvider
C# | 878 lines | 623 code | 142 blank | 113 comment | 90 complexity | ed73cbcece460738e214ebfaf9fb7bf3 MD5 | raw file
Possible License(s): MIT
  1. using System;
  2. using System.Xml;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Configuration.Provider;
  6. using System.Web.Security;
  7. using System.Web.Hosting;
  8. using System.Web.Management;
  9. using System.Security.Permissions;
  10. using System.Web;
  11. using System.Text;
  12. using System.Security.Cryptography;
  13. using System.Diagnostics;
  14. using System.Xml.Linq;
  15. using System.Linq;
  16. using System.IO;
  17. using System.Web.Configuration;
  18. using System.Text.RegularExpressions;
  19. namespace Wcjj.Providers
  20. {
  21. public class XmlMembershipProvider : MembershipProvider
  22. {
  23. private string _XmlFileName;
  24. private XDocument _Document;
  25. private string _hashAlgorithm;
  26. private string _validationKey;
  27. private MembershipPasswordFormat _passwordFormat;
  28. private int _maxInvalidPasswordAttempts;
  29. private int _passwordAttemptWindow;
  30. private int _minRequiredNonAlphanumericCharacters;
  31. private int _minRequiredPasswordLength;
  32. private string _passwordStrengthRegularExpression;
  33. private bool _enablePasswordReset;
  34. private bool _enablePasswordRetrieval;
  35. private bool _requiresQuestionAndAnswer;
  36. private bool _requiresUniqueEmail;
  37. #region Properties
  38. public string ProviderName { get; set; }
  39. /// <summary>
  40. /// Used in testing for access to the private _Document variable when compile in debug mode.
  41. /// </summary>
  42. #if DEBUG
  43. public XDocument XDocument { get { return _Document; } set { _Document = value; } }
  44. #endif
  45. // MembershipProvider Properties
  46. public override string ApplicationName { get; set; }
  47. public override MembershipPasswordFormat PasswordFormat { get { return _passwordFormat; } }
  48. public string XmlFileName { get { return _XmlFileName; } set { _XmlFileName = value; } }
  49. public override bool EnablePasswordRetrieval
  50. {
  51. get { return _enablePasswordRetrieval; }
  52. }
  53. public override bool EnablePasswordReset
  54. {
  55. get { return _enablePasswordReset; }
  56. }
  57. public override int MaxInvalidPasswordAttempts
  58. {
  59. get { return MaxInvalidPasswordAttempts; }
  60. }
  61. public override int MinRequiredNonAlphanumericCharacters
  62. {
  63. get { return _minRequiredNonAlphanumericCharacters; }
  64. }
  65. public override int MinRequiredPasswordLength
  66. {
  67. get { return _minRequiredPasswordLength; }
  68. }
  69. public override int PasswordAttemptWindow
  70. {
  71. get { return _passwordAttemptWindow; }
  72. }
  73. public override string PasswordStrengthRegularExpression
  74. {
  75. get { return _passwordStrengthRegularExpression; }
  76. }
  77. public override bool RequiresQuestionAndAnswer
  78. {
  79. get { return _requiresQuestionAndAnswer; }
  80. }
  81. public override bool RequiresUniqueEmail
  82. {
  83. get { return _requiresUniqueEmail; }
  84. }
  85. #endregion
  86. #region Supported methods
  87. public override void Initialize(string name, NameValueCollection config)
  88. {
  89. InitConfigSettings(name, ref config);
  90. InitPasswordEncryptionSettings(config);
  91. base.Initialize(name, config);
  92. string path = config["xmlFileName"];
  93. if (string.IsNullOrEmpty(XmlFileName))
  94. {
  95. if (String.IsNullOrEmpty(path))
  96. path = "~/App_Data/Membership.xml";
  97. if (!VirtualPathUtility.IsAppRelative(path))
  98. {
  99. this.XmlFileName = Path.GetFullPath(path);
  100. }
  101. else
  102. {
  103. string fullyQualifiedPath;
  104. fullyQualifiedPath = VirtualPathUtility.Combine
  105. (VirtualPathUtility.AppendTrailingSlash
  106. (HttpRuntime.AppDomainAppVirtualPath), path);
  107. this.XmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath);
  108. }
  109. // Make sure we have permission to read the XML data source and
  110. // throw an exception if we don't
  111. FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Write, this.XmlFileName);
  112. permission.Demand();
  113. }
  114. config.Remove("xmlFileName");
  115. if (!File.Exists(XmlFileName))
  116. {
  117. File.AppendAllText(XmlFileName, @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""no"" ?>
  118. <XmlProvider>
  119. <Users>
  120. <!--
  121. <User>
  122. <ApplicationId>/</ApplicationId>
  123. <UserName></UserName>
  124. <PasswordSalt></PasswordSalt>
  125. <Password></Password>
  126. <Email></Email>
  127. <PasswordQuestion></PasswordQuestion>
  128. <PasswordAnswer></PasswordAnswer>
  129. <IsApproved></IsApproved>
  130. <IsLockedOut></IsLockedOut>
  131. <CreateDate></CreateDate>
  132. <LastLoginDate></LastLoginDate>
  133. <LastActivityDate></LastActivityDate>
  134. <LastPasswordChangeDate></LastPasswordChangeDate>
  135. <LastLockoutDate></LastLockoutDate>
  136. <FailedPasswordAttemptCount></FailedPasswordAttemptCount>
  137. <FailedPasswordAnswerAttemptCount></FailedPasswordAnswerAttemptCount>
  138. <Comment></Comment>
  139. </User>
  140. -->
  141. </Users>
  142. <Roles>
  143. <!-- <Role>
  144. <ApplicationId>/</ApplicationId>
  145. <RoleName></RoleName>
  146. <Description></Description>
  147. </Role> -->
  148. </Roles>
  149. <UserRoles>
  150. <!-- <UserRole>
  151. <ApplicationId></ApplicationId>
  152. <UserName></UserName>
  153. <RoleName></RoleName>
  154. <UserRole> -->
  155. </UserRoles>
  156. </XmlProvider>
  157. ");
  158. }
  159. }
  160. /// <summary>
  161. /// Returns true if the username and password match an exsisting user.
  162. /// </summary>
  163. public override bool ValidateUser(string username, string password)
  164. {
  165. if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
  166. return false;
  167. try
  168. {
  169. // Validate the user name and password
  170. XElement xUser = GetXlementUser(username);
  171. if (xUser == null)
  172. return false;
  173. MembershipUser user = MembershipUserFromXElement(xUser);
  174. string userHashedPassword = xUser.Descendants("Password").FirstOrDefault().Value ?? "";
  175. string passwordSalt = (xUser.Descendants("PasswordSalt").FirstOrDefault().Value ?? "").Trim();
  176. password = password.Trim();
  177. if (!string.IsNullOrEmpty(userHashedPassword) && !string.IsNullOrEmpty(passwordSalt) && userHashedPassword == EncodePassword(password, passwordSalt)) // Case-sensitive
  178. {
  179. user.LastLoginDate = DateTime.Now;
  180. user.LastActivityDate = DateTime.Now;
  181. UpdateUser(user);
  182. return true;
  183. }
  184. return false;
  185. }
  186. catch (Exception ex)
  187. {
  188. Debug.WriteLine("Wcjj.Providers.XmlMembershipProvider StackTrace: {0}", ex.StackTrace);
  189. return false;
  190. }
  191. }
  192. /// <summary>
  193. /// The GetUser method returns a MembershipUser object populated with current values from the data source for the specified user.
  194. /// If the user name is not found in the data source, the GetUser method returns null (Nothing in Visual Basic)
  195. /// </summary>
  196. public override MembershipUser GetUser(string username, bool userIsOnline)
  197. {
  198. if (String.IsNullOrEmpty(username))
  199. return null;
  200. MembershipUser user;
  201. var xUser = GetXlementUser(username);
  202. if (xUser != null)
  203. {
  204. user = XElementToMembershipUser(xUser);
  205. if (user.IsOnline)
  206. {
  207. var date = DateTime.Now;
  208. user.LastActivityDate = date;
  209. UpdateUser(user);
  210. }
  211. return user;
  212. }
  213. return null;
  214. }
  215. /// <summary>
  216. /// Get a user based on the username parameter.
  217. /// the userIsOnline parameter is ignored.
  218. /// </summary>
  219. public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
  220. {
  221. string username = providerUserKey.ToString();
  222. return GetUser(username, userIsOnline);
  223. }
  224. /// <summary>
  225. /// Retrieves a collection of all the users.
  226. /// This implementation ignores pageIndex and pageSize,
  227. /// and it doesn't sort the MembershipUser objects returned.
  228. /// </summary>
  229. public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
  230. {
  231. InitializeDataStore();
  232. MembershipUserCollection users = new MembershipUserCollection();
  233. var xUsers = _Document.Descendants("User").Skip(pageIndex).Take(pageSize);
  234. foreach (XElement xuser in xUsers)
  235. {
  236. var muser = XElementToMembershipUser(xuser);
  237. users.Add(muser);
  238. }
  239. totalRecords = users.Count;
  240. return users;
  241. }
  242. /// <summary>
  243. /// Takes, as input, a user name, a current password, and a new password, and updates the password in the data source if the supplied user name and current password are valid.
  244. /// The ChangePassword method returns true if the password was updated successfully; otherwise, false.
  245. /// </summary>
  246. public override bool ChangePassword(string username, string oldPassword, string newPassword)
  247. {
  248. var xUser = GetXlementUser(username);
  249. if (xUser == null)
  250. return false;
  251. string salt = xUser.Element("PasswordSalt").Value;
  252. string oldPassEncoded = EncodePassword(oldPassword, salt);
  253. string dsOldPassEncoded = xUser.Element("Password").Value;
  254. if (oldPassEncoded != dsOldPassEncoded)
  255. return false;
  256. var passMeetsReqs = PasswordMeetsMinimumRequirements(newPassword);
  257. if (passMeetsReqs)
  258. {
  259. string newPasswordEncoded = EncodePassword(newPassword, salt);
  260. xUser.Element("Password").Value = newPasswordEncoded;
  261. _Document.Save(XmlFileName);
  262. return true;
  263. }
  264. return false;
  265. }
  266. /// <summary>
  267. /// Creates a new user store he/she in the XML file
  268. /// </summary>
  269. public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
  270. {
  271. string salt = PasswordUtil.CreateRandomSalt();
  272. string hashedPass = string.Empty;
  273. try
  274. {
  275. hashedPass = EncodePassword(password, salt);
  276. }
  277. catch (Exception ex)
  278. {
  279. throw new MembershipPasswordException("There was a problem with the given password", ex);
  280. }
  281. XElement xUser;
  282. xUser = GetXlementUser(username);
  283. if (xUser != null)
  284. {
  285. status = MembershipCreateStatus.DuplicateUserName;
  286. return null;
  287. }
  288. xUser = _Document.Descendants("User").Where(x => x.Element("Email").Value == email.ToLower() && x.Element("ApplicationId").Value == ApplicationName).FirstOrDefault();
  289. if (xUser != null && RequiresUniqueEmail)
  290. {
  291. status = MembershipCreateStatus.DuplicateEmail;
  292. return null;
  293. }
  294. if(!PasswordMeetsMinimumRequirements(password))
  295. {
  296. status = MembershipCreateStatus.InvalidPassword;
  297. return null;
  298. }
  299. //TODO: Check the Minimun Non AlpahNumeric Characters int the password.
  300. xUser = new XElement("User",
  301. new XElement("ApplicationId", this.ApplicationName),
  302. new XElement("UserName", username),
  303. new XElement("PasswordSalt", salt),
  304. new XElement("Password", hashedPass),
  305. new XElement("Email", email),
  306. new XElement("PasswordQuestion", passwordQuestion),
  307. new XElement("PasswordAnswer", passwordAnswer),
  308. new XElement("IsApproved", Convert.ToString(isApproved)),
  309. new XElement("IsLockedOut", Convert.ToString(false)),
  310. new XElement("CreateDate", Convert.ToString(DateTime.Now)),
  311. new XElement("LastLoginDate", Convert.ToString(DateTime.MinValue)),
  312. new XElement("LastActivityDate", Convert.ToString(DateTime.MinValue)),
  313. new XElement("LastPasswordChangeDate", Convert.ToString(DateTime.MinValue)),
  314. new XElement("LastLockoutDate", Convert.ToString(DateTime.MinValue)),
  315. new XElement("FailedPasswordAttemptCount", Convert.ToString(0)),
  316. new XElement("FailedPasswordAnswerAttemptCount", Convert.ToString(0)),
  317. new XElement("Comment", "")
  318. );
  319. _Document.Descendants("Users").FirstOrDefault().Add(xUser);
  320. _Document.Save(XmlFileName);
  321. var user = XElementToMembershipUser(xUser);
  322. status = MembershipCreateStatus.Success;
  323. return user;
  324. }
  325. /// <summary>
  326. /// Deletes the user from the XML file and
  327. /// removes him/her from the internal cache.
  328. /// </summary>
  329. public override bool DeleteUser(string username, bool deleteAllRelatedData)
  330. {
  331. var user = GetXlementUser(username);
  332. if (user != null)
  333. {
  334. user.Remove();
  335. _Document.Save(XmlFileName);
  336. if (deleteAllRelatedData)
  337. {
  338. _Document.Descendants("UserRole").Where(x => x.Element("UserName").Value == username.ToLower()).Remove();
  339. }
  340. return true;
  341. }
  342. return false;
  343. }
  344. /// <summary>
  345. /// Retrieves the first username based on a matching email.
  346. /// </summary>
  347. public override string GetUserNameByEmail(string email)
  348. {
  349. InitializeDataStore();
  350. var xUser = _Document.Descendants("User").Where(x => x.Element("Email").Value == email.ToLower()
  351. && x.Element("ApplicationId").Value == ApplicationName).FirstOrDefault();
  352. if (xUser == null)
  353. return string.Empty;
  354. string username = xUser.Element("UserName").Value;
  355. return username;
  356. }
  357. /// <summary>
  358. /// Updates a user. The username will not be changed.
  359. /// </summary>
  360. public override void UpdateUser(MembershipUser user)
  361. {
  362. UpdateUser(user, null, null);
  363. }
  364. /// <summary>
  365. /// Updates a user. The username will not be changed.
  366. /// </summary>
  367. public void UpdateUser(MembershipUser user, string password)
  368. {
  369. UpdateUser(user, password, null);
  370. }
  371. /// <summary>
  372. /// Updates a user. The username will not be changed.
  373. /// </summary>
  374. public void UpdateUser(MembershipUser user, string password, string passwordQuestionAnswer)
  375. {
  376. var xuser = GetXlementUser(user.UserName);
  377. if (xuser == null)
  378. throw new NullReferenceException(string.Format("Cannot update user {0}. They don't exist in the data store.", user.UserName));
  379. if (!string.IsNullOrEmpty(password))
  380. {
  381. var passMeetsReqs = PasswordMeetsMinimumRequirements(password);
  382. if (!passMeetsReqs)
  383. throw new MembershipPasswordException("The password does not meet the minimum requirements.");
  384. var salt = xuser.Element("PasswordSalt").Value;
  385. xuser.Element("Password").Value = EncodePassword(password, salt);
  386. }
  387. xuser.Element("Email").Value = user.Email;
  388. xuser.Element("PasswordQuestion").Value = user.PasswordQuestion;
  389. if (!string.IsNullOrEmpty(passwordQuestionAnswer))
  390. xuser.Element("PasswordAnswer").Value = passwordQuestionAnswer;
  391. xuser.Element("IsApproved").Value = Convert.ToString(user.IsApproved);
  392. xuser.Element("IsLockedOut").Value = Convert.ToString(user.IsLockedOut);
  393. xuser.Element("LastLoginDate").Value = Convert.ToString(user.LastLoginDate);
  394. xuser.Element("LastPasswordChangeDate").Value = Convert.ToString(user.LastPasswordChangedDate);
  395. xuser.Element("LastLockoutDate").Value = Convert.ToString(user.LastLockoutDate);
  396. xuser.Element("Comment").Value = user.Comment;
  397. _Document.Save(_XmlFileName);
  398. }
  399. /// <summary>
  400. /// Returns the number of users online.
  401. /// These are users who's last activity date is greater than the current time minus Memberships UserIsOnlineTimeWindow property.
  402. /// </summary>
  403. /// <returns></returns>
  404. public override int GetNumberOfUsersOnline()
  405. {
  406. var now = DateTime.Now;
  407. int timewindow = System.Web.Security.Membership.UserIsOnlineTimeWindow;
  408. var onlineTime = now.Subtract(new TimeSpan(0, timewindow, 0));
  409. InitializeDataStore();
  410. var nbrUsersOnline = _Document.Descendants("User").Where(x => Convert.ToDateTime(x.Element("LastActivityDate").Value) > onlineTime
  411. && x.Element("ApplicationId").Value == ApplicationName).Count();
  412. return nbrUsersOnline;
  413. }
  414. /// <summary>
  415. /// Takes, as input, a user name and a password answer and generates a new, random password for the specified user.
  416. /// The ResetPassword method updates the user information in the data source with the new password value and returns the new password as a string
  417. /// </summary>
  418. /// <param name="username"></param>
  419. /// <param name="answer"></param>
  420. /// <returns></returns>
  421. public override string ResetPassword(string username, string answer)
  422. {
  423. if (!EnablePasswordReset)
  424. throw new NotSupportedException("EnablePasswordReset is false.");
  425. var xUser = GetXlementUser(username);
  426. if (xUser == null)
  427. throw new NullReferenceException(string.Format("The user {0} does not exists", username));
  428. if (RequiresQuestionAndAnswer)
  429. {
  430. var dsAnswer = xUser.Element("PasswordAnswer").Value;
  431. if (answer.Trim() != dsAnswer)
  432. throw new MembershipPasswordException("The answer is incorrect.");
  433. }
  434. var newSalt = PasswordUtil.CreateRandomSalt();
  435. var newPassword = System.Web.Security.Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters);
  436. var encodedNewPassword = EncodePassword(newPassword, newSalt);
  437. xUser.Element("Password").Value = encodedNewPassword;
  438. xUser.Element("PasswordSalt").Value = newSalt;
  439. _Document.Save(XmlFileName);
  440. return newPassword;
  441. }
  442. public override string GetPassword(string username, string answer)
  443. {
  444. if (PasswordFormat == MembershipPasswordFormat.Hashed)
  445. throw new MembershipPasswordException("Hashed passwords cannot be retrieved.");
  446. if (!EnablePasswordRetrieval)
  447. throw new ProviderException("EnablePasswordRetrieval is false.");
  448. var xUser = GetXlementUser(username);
  449. if (xUser == null)
  450. throw new NullReferenceException(string.Format("The user {0} does not exists", username));
  451. if (RequiresQuestionAndAnswer)
  452. {
  453. var dsAnswer = xUser.Element("PasswordAnswer").Value;
  454. if (answer.Trim() != dsAnswer)
  455. throw new MembershipPasswordException("The answer is incorrect.");
  456. }
  457. var encodedPassword = xUser.Element("Password").Value;
  458. if (PasswordFormat == MembershipPasswordFormat.Clear)
  459. return encodedPassword;
  460. var salt = xUser.Element("PasswordSalt").Value;
  461. return UnEncodePassword(encodedPassword, salt);
  462. }
  463. public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
  464. {
  465. if (!ValidateUser(username, password))
  466. return false;
  467. var xUser = GetXlementUser(username);
  468. if(xUser == null)
  469. return false;
  470. xUser.Element("PasswordQuestion").Value = newPasswordQuestion;
  471. xUser.Element("PasswordAnswer").Value = newPasswordQuestion;
  472. _Document.Save(XmlFileName);
  473. return true;
  474. }
  475. public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
  476. {
  477. InitializeDataStore();
  478. MembershipUserCollection users = new MembershipUserCollection();
  479. var xUsers = _Document.Descendants("User").Where(x => x.Element("Email").IsMatch(emailToMatch)
  480. && x.Element("ApplicationId").Value == ApplicationName).OrderBy(x => x.Element("UserName").Value).Skip(pageIndex).Take(pageSize);
  481. foreach (XElement xuser in xUsers)
  482. {
  483. var muser = XElementToMembershipUser(xuser);
  484. users.Add(muser);
  485. }
  486. totalRecords = users.Count;
  487. return users;
  488. }
  489. public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
  490. {
  491. InitializeDataStore();
  492. MembershipUserCollection users = new MembershipUserCollection();
  493. var xUsers = _Document.Descendants("User").Where(x => x.Element("UserName").IsMatch(usernameToMatch)
  494. && x.Element("ApplicationId").Value == ApplicationName).OrderBy(x => x.Element("UserName").Value).Skip(pageIndex).Take(pageSize);
  495. foreach (XElement xuser in xUsers)
  496. {
  497. var muser = XElementToMembershipUser(xuser);
  498. users.Add(muser);
  499. }
  500. totalRecords = users.Count;
  501. return users;
  502. }
  503. public override bool UnlockUser(string userName)
  504. {
  505. var xUser = GetXlementUser(userName);
  506. xUser.Element("IsLockedOut").Value = Convert.ToString(false);
  507. _Document.Save(XmlFileName);
  508. return true;
  509. }
  510. #endregion
  511. #region Helper methods
  512. /// <summary>
  513. /// Retrieves a user from the Datastore as an XElement
  514. /// </summary>
  515. /// <param name="username"></param>
  516. /// <returns></returns>
  517. private XElement GetXlementUser(string username)
  518. {
  519. InitializeDataStore();
  520. return _Document.Descendants("User").Where(x => x.Element("UserName").Value == username.ToLower()
  521. && x.Element("ApplicationId").Value == ApplicationName).FirstOrDefault();
  522. }
  523. /// <summary>
  524. /// Converts a MembershipUser to an XElement
  525. /// </summary>
  526. /// <param name="xUser"></param>
  527. /// <returns></returns>
  528. private MembershipUser MembershipUserFromXElement(XElement xUser)
  529. {
  530. MembershipUser user = new MembershipUser(this.Name,
  531. xUser.Descendants("UserName").FirstOrDefault().Value ?? "",
  532. xUser.Descendants("UserName").FirstOrDefault().Value ?? "",
  533. xUser.Descendants("Email").FirstOrDefault().Value ?? "",
  534. xUser.Descendants("PasswordQuestion").FirstOrDefault().Value ?? "",
  535. xUser.Descendants("Comment").FirstOrDefault().Value ?? "",
  536. Convert.ToBoolean(xUser.Descendants("IsApproved").FirstOrDefault().Value ?? "0"),
  537. Convert.ToBoolean(xUser.Descendants("IsLockedOut").FirstOrDefault().Value ?? "0"),
  538. Convert.ToDateTime(xUser.Descendants("CreateDate").FirstOrDefault().Value ?? DateTime.MinValue.ToString()),
  539. Convert.ToDateTime(xUser.Descendants("LastLoginDate").FirstOrDefault().Value ?? DateTime.MinValue.ToString()),
  540. Convert.ToDateTime(xUser.Descendants("LastActivityDate").FirstOrDefault().Value ?? DateTime.MinValue.ToString()),
  541. Convert.ToDateTime(xUser.Descendants("LastPasswordChangeDate").FirstOrDefault().Value ?? DateTime.MinValue.ToString()),
  542. Convert.ToDateTime(xUser.Descendants("LastLockoutDate").FirstOrDefault().Value ?? DateTime.MinValue.ToString()));
  543. return user;
  544. }
  545. /// <summary>
  546. /// Convert an XElement to a membership user.
  547. /// </summary>
  548. /// <param name="user"></param>
  549. /// <returns></returns>
  550. private MembershipUser XElementToMembershipUser(XElement user)
  551. {
  552. return new MembershipUser(this.ProviderName,
  553. user.Element("UserName").Value,
  554. user.Element("UserName").Value,
  555. user.Element("Email").Value,
  556. user.Element("PasswordQuestion").Value ?? "",
  557. user.Element("Comment").Value ?? "",
  558. Convert.ToBoolean(user.Element("IsApproved").Value ?? "False"),
  559. Convert.ToBoolean(user.Element("IsLockedOut").Value ?? "False"),
  560. Convert.ToDateTime(user.Element("CreateDate").Value ?? DateTime.MinValue.ToLongDateString()),
  561. Convert.ToDateTime(user.Element("LastLoginDate").Value ?? DateTime.MinValue.ToLongDateString()),
  562. Convert.ToDateTime(user.Element("LastActivityDate").Value ?? DateTime.MinValue.ToLongDateString()),
  563. Convert.ToDateTime(user.Element("LastPasswordChangeDate").Value ?? DateTime.MinValue.ToLongDateString()),
  564. Convert.ToDateTime(user.Element("LastLockoutDate").Value ?? DateTime.MinValue.ToLongDateString()));
  565. }
  566. /// <summary>
  567. /// Loads the Xml Document into memory
  568. /// </summary>
  569. ///
  570. private void InitializeDataStore()
  571. {
  572. lock (this)
  573. {
  574. if (_Document == null)
  575. {
  576. _Document = XDocument.Load(this.XmlFileName);
  577. }
  578. }
  579. }
  580. /// <summary>
  581. /// Takes a configuration value and tests for null or empty. If it is returns the given default value.
  582. /// </summary>
  583. /// <param name="value"></param>
  584. /// <param name="defaultValue"></param>
  585. /// <returns></returns>
  586. private string GetConfigValue(string value, string defaultValue)
  587. {
  588. if (string.IsNullOrEmpty(value))
  589. return defaultValue;
  590. return value;
  591. }
  592. /// <summary>
  593. /// Extract property configuration setting from the Web.Config
  594. /// </summary>
  595. /// <param name="name"></param>
  596. /// <param name="config"></param>
  597. private void InitConfigSettings(string name, ref NameValueCollection config)
  598. {
  599. if (config == null)
  600. throw new ArgumentNullException("config");
  601. if (String.IsNullOrEmpty(name))
  602. name = "XmlMembershipProvider";
  603. this.ProviderName = name;
  604. if (string.IsNullOrEmpty(config["description"]))
  605. {
  606. config.Remove("description");
  607. config.Add("description", "XML membership provider");
  608. }
  609. ApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
  610. _maxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
  611. _passwordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
  612. _minRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonAlphanumericCharacters"], "1"));
  613. _minRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
  614. _passwordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], String.Empty));
  615. _enablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
  616. _enablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
  617. _requiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
  618. _requiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));
  619. }
  620. /// <summary>
  621. /// Initialize settings the pertain to password hashing / encrypting
  622. /// </summary>
  623. /// <param name="config"></param>
  624. private void InitPasswordEncryptionSettings(NameValueCollection config)
  625. {
  626. System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
  627. MachineKeySection machineKey = cfg.GetSection("system.web/machineKey") as MachineKeySection;
  628. _hashAlgorithm = machineKey.ValidationAlgorithm;
  629. _validationKey = machineKey.ValidationKey;
  630. if (_validationKey.Contains("AutoGenerate"))
  631. {
  632. if (PasswordFormat != MembershipPasswordFormat.Clear)
  633. {
  634. throw new ProviderException("Hashed or Encrypted passwords are not supported with auto-generated keys.");
  635. }
  636. }
  637. string passFormat = config["passwordFormat"];
  638. if (passFormat == null)
  639. {
  640. passFormat = "Hashed";
  641. }
  642. switch (passFormat)
  643. {
  644. case "Hashed":
  645. _passwordFormat = MembershipPasswordFormat.Hashed;
  646. break;
  647. case "Encrypted":
  648. _passwordFormat = MembershipPasswordFormat.Encrypted;
  649. break;
  650. case "Clear":
  651. _passwordFormat = MembershipPasswordFormat.Clear;
  652. break;
  653. default:
  654. throw new ProviderException("The password format from the custom provider is not supported.");
  655. }
  656. }
  657. /// <summary>
  658. /// Encode the password
  659. /// </summary>
  660. /// <param name="password"></param>
  661. /// <param name="salt"></param>
  662. /// <returns></returns>
  663. private string EncodePassword(string password, string salt)
  664. {
  665. string encodedPassword = password;
  666. switch (_passwordFormat)
  667. {
  668. case MembershipPasswordFormat.Clear:
  669. break;
  670. case MembershipPasswordFormat.Encrypted:
  671. encodedPassword =
  672. Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
  673. break;
  674. case MembershipPasswordFormat.Hashed:
  675. if (string.IsNullOrEmpty(salt))
  676. throw new ProviderException("A random salt is required with hashed passwords.");
  677. encodedPassword = PasswordUtil.HashPassword(password, salt, _hashAlgorithm, _validationKey);
  678. break;
  679. default:
  680. throw new ProviderException("Unsupported password format.");
  681. }
  682. return encodedPassword;
  683. }
  684. /// <summary>
  685. /// UnEncode the password
  686. /// </summary>
  687. /// <param name="encodedPassword"></param>
  688. /// <param name="salt"></param>
  689. /// <returns></returns>
  690. private string UnEncodePassword(string encodedPassword, string salt)
  691. {
  692. string password = encodedPassword;
  693. switch (_passwordFormat)
  694. {
  695. case MembershipPasswordFormat.Clear:
  696. break;
  697. case MembershipPasswordFormat.Encrypted:
  698. password =
  699. Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
  700. break;
  701. case MembershipPasswordFormat.Hashed:
  702. throw new ProviderException("Hashed passwords do not require decoding, just compare hashes.");
  703. default:
  704. throw new ProviderException("Unsupported password format.");
  705. }
  706. return password;
  707. }
  708. /// <summary>
  709. /// Encrypts a string using the SHA256 algorithm.
  710. /// </summary>
  711. private static string Encrypt(string plainMessage)
  712. {
  713. byte[] data = Encoding.UTF8.GetBytes(plainMessage);
  714. using (HashAlgorithm sha = new SHA256Managed())
  715. {
  716. byte[] encryptedBytes = sha.TransformFinalBlock(data, 0, data.Length);
  717. return Convert.ToBase64String(sha.Hash);
  718. }
  719. }
  720. /// <summary>
  721. /// Tests that a given password meets the minimum length and non alpha numeric contraints.
  722. /// </summary>
  723. /// <param name="password"></param>
  724. /// <returns></returns>
  725. private bool PasswordMeetsMinimumRequirements(string password)
  726. {
  727. var nonAlphaNumericCharacters = password.Where(c => !char.IsLetterOrDigit(c)).SelectMany(x => x.ToString());
  728. if (password.Length < MinRequiredPasswordLength || nonAlphaNumericCharacters.Count() < MinRequiredNonAlphanumericCharacters)
  729. {
  730. return false;
  731. }
  732. return true;
  733. }
  734. #endregion
  735. }
  736. }