PageRenderTime 370ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 1ms

/MongoProviders/MembershipProvider.cs

https://github.com/ddo88/MongoProviders
C# | 1551 lines | 936 code | 256 blank | 359 comment | 169 complexity | f43ab0d7a968bc400ff6fb52d3f9133c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2011 Adrian Lanning <adrian@nimblejump.com>
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software
  6. // and associated documentation files (the "Software"), to deal in the Software without restriction,
  7. // including without limitation the rights to use, copy, modify, merge, publish, distribute,
  8. // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. // The above copyright notice and this permission notice shall be included in all copies or
  11. // substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  14. // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  15. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  16. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Web;
  20. using System.Web.Configuration;
  21. using System.Web.Security;
  22. using System.Collections.Generic;
  23. using System.Linq;
  24. using System.Text;
  25. using System.Configuration;
  26. using System.Configuration.Provider;
  27. using System.Collections.Specialized;
  28. using System.Text.RegularExpressions;
  29. using System.Security.Cryptography;
  30. using System.Diagnostics;
  31. using System.Globalization;
  32. using MongoDB.Driver;
  33. using MongoDB.Driver.Builders;
  34. using MongoDB.Bson;
  35. using MongoDB.Bson.Serialization;
  36. using MongoDB.Driver.Linq;
  37. namespace MongoProviders
  38. {
  39. /// <summary>
  40. /// ASP.NET Membership Provider that uses MongoDB
  41. ///
  42. /// Non-standard configuration attributes:
  43. ///
  44. /// invalidUsernameCharacters - characters that are illegal in Usernames. Default: ",%"
  45. /// Ex: invalidUsernameCharacters=",%&"
  46. /// Note: the percent character, "%", should generally always be illegal since it is used in the FindUsersBy*
  47. /// methods to indicate a wildcard. This matches the behavior of the SQL Membership provider in supporting
  48. /// basic SQL Like syntax, although only the "%" is supported (not "_" or "[]")
  49. ///
  50. /// invalidEmailCharacters - characters that are illegal in Email addresses. Default: ",%"
  51. /// Ex: invalidEmailCharacters=",!%*"
  52. /// Note: the percent character, "%", should generally always be illegal since it is used in the FindUsersBy*
  53. /// methods to indicate a wildcard. This matches the behavior of the SQL Membership provider in supporting
  54. /// basic SQL Like syntax, although only the "%" is supported (not "_" or "[]")
  55. ///
  56. /// writeExceptionsToEventLog - boolean indicating whether database exceptions should be
  57. /// written to the EventLog rather than returned to UI. Default: "true"
  58. /// Ex: writeExceptionsToEventLog="false"
  59. ///
  60. /// collectionSuffix - suffix of the collection to use for Membership User data. Default: "users"
  61. /// Ex: collectionSuffix="users"
  62. /// Note: the actual collection name used will be the combination of the ApplicationName and the CollectionSuffix.
  63. /// For example, if the ApplicationName is "/", then the default Collection name will be "/users".
  64. /// This relieves us from having to include the ApplicationName in every query and also saves space in two ways:
  65. /// 1. ApplicationName does not need to be stored with the User data
  66. /// 2. Indexes no longer need to be composite. ie. "LowercaseUsername" rather than "ApplicationName", "LowercaseUsername"
  67. ///
  68. /// </summary>
  69. public class MembershipProvider : System.Web.Security.MembershipProvider
  70. {
  71. #region Custom Public Properties
  72. public const string DEFAULT_NAME = "MongoMembershipProvider";
  73. public const string DEFAULT_DATABASE_NAME = "test";
  74. public const string DEFAULT_USER_COLLECTION_SUFFIX = "users";
  75. public const string DEFAULT_INVALID_CHARACTERS = ",%";
  76. public const int NEW_PASSWORD_LENGTH = 8;
  77. public const int MAX_USERNAME_LENGTH = 256;
  78. public const int MAX_PASSWORD_LENGTH = 128;
  79. public const int MAX_PASSWORD_ANSWER_LENGTH = 128;
  80. public const int MAX_EMAIL_LENGTH = 256;
  81. public const int MAX_PASSWORD_QUESTION_LENGTH = 256;
  82. public struct MembershipElements
  83. {
  84. public string LowercaseUsername;
  85. public string LowercaseEmail;
  86. public string LastActivityDate;
  87. }
  88. public MembershipElements ElementNames { get; protected set; }
  89. //
  90. // If false, exceptions are thrown to the caller. If true,
  91. // exceptions are written to the event log.
  92. //
  93. public bool WriteExceptionsToEventLog { get; set; }
  94. public string InvalidUsernameCharacters { get; protected set; }
  95. public string InvalidEmailCharacters { get; protected set; }
  96. public string CollectionName { get; protected set; }
  97. public MongoCollection<User> Collection { get; protected set; }
  98. public MongoDatabase Database { get; protected set; }
  99. #endregion
  100. #region Protected Properties
  101. protected string _connectionString;
  102. protected MachineKeySection _machineKey;
  103. protected string _collectionSuffix;
  104. protected int _newPasswordLength = 8;
  105. protected string _eventSource = DEFAULT_NAME;
  106. protected string _eventLog = "Application";
  107. protected string _exceptionMessage = Resources.ProviderException;
  108. #endregion
  109. #region MembershipProvider properties
  110. // System.Web.Security.MembershipProvider properties.
  111. protected string _applicationName;
  112. protected bool _enablePasswordReset;
  113. protected bool _enablePasswordRetrieval;
  114. protected bool _requiresQuestionAndAnswer;
  115. protected bool _requiresUniqueEmail;
  116. protected int _maxInvalidPasswordAttempts;
  117. protected int _passwordAttemptWindow;
  118. protected MembershipPasswordFormat _passwordFormat;
  119. protected int _minRequiredNonAlphanumericCharacters;
  120. protected int _minRequiredPasswordLength;
  121. protected string _passwordStrengthRegularExpression;
  122. /// <summary>
  123. /// The name of the application using the custom membership provider.
  124. /// </summary>
  125. /// <value></value>
  126. /// <returns>
  127. /// The name of the application using the custom membership provider.
  128. /// </returns>
  129. public override string ApplicationName
  130. {
  131. get { return _applicationName; }
  132. set
  133. {
  134. _applicationName = value;
  135. CollectionName = Helper.GenerateCollectionName(_applicationName, _collectionSuffix);
  136. Collection = Database.GetCollection<User>(CollectionName);
  137. }
  138. }
  139. /// <summary>
  140. /// Indicates whether the membership provider is configured to allow users to reset their passwords.
  141. /// </summary>
  142. /// <value></value>
  143. /// <returns>true if the membership provider supports password reset; otherwise, false. The default is true.
  144. /// </returns>
  145. public override bool EnablePasswordReset
  146. {
  147. get { return _enablePasswordReset; }
  148. }
  149. /// <summary>
  150. /// Indicates whether the membership provider is configured to allow users to retrieve their passwords.
  151. /// </summary>
  152. /// <value></value>
  153. /// <returns>true if the membership provider is configured to support password retrieval; otherwise, false. The default is false.
  154. /// </returns>
  155. public override bool EnablePasswordRetrieval
  156. {
  157. get { return _enablePasswordRetrieval; }
  158. }
  159. /// <summary>
  160. /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval.
  161. /// </summary>
  162. /// <value></value>
  163. /// <returns>true if a password answer is required for password reset and retrieval; otherwise, false. The default is true.
  164. /// </returns>
  165. public override bool RequiresQuestionAndAnswer
  166. {
  167. get { return _requiresQuestionAndAnswer; }
  168. }
  169. /// <summary>
  170. /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
  171. /// </summary>
  172. /// <value></value>
  173. /// <returns>true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.
  174. /// </returns>
  175. public override bool RequiresUniqueEmail
  176. {
  177. get { return _requiresUniqueEmail; }
  178. }
  179. /// <summary>
  180. /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
  181. /// </summary>
  182. /// <value></value>
  183. /// <returns>
  184. /// The number of invalid password or password-answer attempts allowed before the membership user is locked out.
  185. /// </returns>
  186. public override int MaxInvalidPasswordAttempts
  187. {
  188. get { return _maxInvalidPasswordAttempts; }
  189. }
  190. /// <summary>
  191. /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
  192. /// </summary>
  193. /// <value></value>
  194. /// <returns>
  195. /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
  196. /// </returns>
  197. public override int PasswordAttemptWindow
  198. {
  199. get { return _passwordAttemptWindow; }
  200. }
  201. /// <summary>
  202. /// Gets a value indicating the format for storing passwords in the membership data store.
  203. /// </summary>
  204. /// <value></value>
  205. /// <returns>
  206. /// One of the <see cref="T:System.Web.Security.MembershipPasswordFormat"/> values indicating the format for storing passwords in the data store.
  207. /// </returns>
  208. public override MembershipPasswordFormat PasswordFormat
  209. {
  210. get { return _passwordFormat; }
  211. }
  212. /// <summary>
  213. /// Gets the minimum number of special characters that must be present in a valid password.
  214. /// </summary>
  215. /// <value></value>
  216. /// <returns>
  217. /// The minimum number of special characters that must be present in a valid password.
  218. /// </returns>
  219. public override int MinRequiredNonAlphanumericCharacters
  220. {
  221. get { return _minRequiredNonAlphanumericCharacters; }
  222. }
  223. /// <summary>
  224. /// Gets the minimum length required for a password.
  225. /// </summary>
  226. /// <value></value>
  227. /// <returns>
  228. /// The minimum length required for a password.
  229. /// </returns>
  230. public override int MinRequiredPasswordLength
  231. {
  232. get { return _minRequiredPasswordLength; }
  233. }
  234. /// <summary>
  235. /// Gets the regular expression used to evaluate a password.
  236. /// </summary>
  237. /// <value></value>
  238. /// <returns>
  239. /// A regular expression used to evaluate a password.
  240. /// </returns>
  241. public override string PasswordStrengthRegularExpression
  242. {
  243. get { return _passwordStrengthRegularExpression; }
  244. }
  245. #endregion
  246. #region Public Methods
  247. /// <summary>
  248. /// Initializes the provider.
  249. /// </summary>
  250. /// <param name="name">The friendly name of the provider.</param>
  251. /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
  252. /// <exception cref="T:System.ArgumentNullException">
  253. /// The config collection provided is null.
  254. /// </exception>
  255. /// <exception cref="T:System.InvalidOperationException">
  256. /// An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.
  257. /// </exception>
  258. /// <exception cref="T:System.Configuration.Provider.ProviderException">
  259. /// </exception>
  260. public override void Initialize(string name, NameValueCollection config)
  261. {
  262. if (null == config)
  263. throw new ArgumentNullException("config");
  264. if (String.IsNullOrWhiteSpace(name))
  265. name = DEFAULT_NAME;
  266. if (String.IsNullOrEmpty(config["description"]))
  267. {
  268. config.Remove("description");
  269. config.Add("description", Resources.MembershipProvider_description);
  270. }
  271. base.Initialize(name, config);
  272. // get config values
  273. _applicationName = config["applicationName"] ?? System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
  274. _maxInvalidPasswordAttempts = Helper.GetConfigValue(config["maxInvalidPasswordAttempts"], 5);
  275. _passwordAttemptWindow = Helper.GetConfigValue(config["passwordAttemptWindow"], 10);
  276. _minRequiredNonAlphanumericCharacters = Helper.GetConfigValue(config["minRequiredNonAlphanumericCharacters"], 1);
  277. _minRequiredPasswordLength = Helper.GetConfigValue(config["minRequiredPasswordLength"], 7);
  278. _passwordStrengthRegularExpression = Helper.GetConfigValue(config["passwordStrengthRegularExpression"], "");
  279. _enablePasswordReset = Helper.GetConfigValue(config["enablePasswordReset"], true);
  280. _enablePasswordRetrieval = Helper.GetConfigValue(config["enablePasswordRetrieval"], false);
  281. _requiresQuestionAndAnswer = Helper.GetConfigValue(config["requiresQuestionAndAnswer"], false);
  282. _requiresUniqueEmail = Helper.GetConfigValue(config["requiresUniqueEmail"], true);
  283. InvalidUsernameCharacters = Helper.GetConfigValue(config["invalidUsernameCharacters"], DEFAULT_INVALID_CHARACTERS);
  284. InvalidEmailCharacters = Helper.GetConfigValue(config["invalidEmailCharacters"], DEFAULT_INVALID_CHARACTERS);
  285. WriteExceptionsToEventLog = Helper.GetConfigValue(config["writeExceptionsToEventLog"], true);
  286. _collectionSuffix = Helper.GetConfigValue(config["collectionSuffix"], DEFAULT_USER_COLLECTION_SUFFIX);
  287. ValidatePwdStrengthRegularExpression();
  288. if (_minRequiredNonAlphanumericCharacters > _minRequiredPasswordLength)
  289. throw new ProviderException(Resources.MinRequiredNonalphanumericCharacters_can_not_be_more_than_MinRequiredPasswordLength);
  290. string temp_format = config["passwordFormat"];
  291. if (null == temp_format)
  292. {
  293. temp_format = "Hashed";
  294. }
  295. switch (temp_format.ToLowerInvariant())
  296. {
  297. case "hashed":
  298. _passwordFormat = MembershipPasswordFormat.Hashed;
  299. break;
  300. case "encrypted":
  301. _passwordFormat = MembershipPasswordFormat.Encrypted;
  302. break;
  303. case "clear":
  304. _passwordFormat = MembershipPasswordFormat.Clear;
  305. break;
  306. default:
  307. throw new ProviderException(Resources.Provider_bad_password_format);
  308. }
  309. if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval)
  310. {
  311. throw new ProviderException(Resources.Provider_can_not_retrieve_hashed_password);
  312. }
  313. // Initialize Connection String
  314. string temp = config["connectionStringName"];
  315. if (String.IsNullOrWhiteSpace(temp))
  316. throw new ProviderException(Resources.Connection_name_not_specified);
  317. ConnectionStringSettings ConnectionStringSettings = ConfigurationManager.ConnectionStrings[temp];
  318. if (null == ConnectionStringSettings || String.IsNullOrWhiteSpace(ConnectionStringSettings.ConnectionString))
  319. throw new ProviderException(String.Format(Resources.Connection_string_not_found, temp));
  320. _connectionString = ConnectionStringSettings.ConnectionString;
  321. // Check for invalid parameters in the config
  322. config.Remove("connectionStringName");
  323. config.Remove("enablePasswordRetrieval");
  324. config.Remove("enablePasswordReset");
  325. config.Remove("requiresQuestionAndAnswer");
  326. config.Remove("applicationName");
  327. config.Remove("requiresUniqueEmail");
  328. config.Remove("maxInvalidPasswordAttempts");
  329. config.Remove("passwordAttemptWindow");
  330. config.Remove("commandTimeout");
  331. config.Remove("passwordFormat");
  332. config.Remove("name");
  333. config.Remove("minRequiredPasswordLength");
  334. config.Remove("minRequiredNonAlphanumericCharacters");
  335. config.Remove("passwordStrengthRegularExpression");
  336. config.Remove("writeExceptionsToEventLog");
  337. config.Remove("invalidUsernameCharacters");
  338. config.Remove("invalidEmailCharacters");
  339. config.Remove("collectionSuffix");
  340. if (config.Count > 0)
  341. {
  342. string key = config.GetKey(0);
  343. if (!string.IsNullOrEmpty(key))
  344. {
  345. throw new ProviderException(String.Format(Resources.Provider_unrecognized_attribute, key));
  346. }
  347. }
  348. // Get encryption and decryption key information from the configuration.
  349. Configuration cfg =
  350. WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
  351. _machineKey = (MachineKeySection)ConfigurationManager.GetSection("system.web/machineKey");
  352. if (_machineKey.ValidationKey.Contains("AutoGenerate"))
  353. if (PasswordFormat != MembershipPasswordFormat.Clear)
  354. throw new ProviderException(Resources.Provider_can_not_autogenerate_machine_key_with_encrypted_or_hashed_password_format);
  355. // Initialize MongoDB Server
  356. UserClassMap.Register();
  357. Database = MongoDatabase.Create(_connectionString);
  358. CollectionName = Helper.GenerateCollectionName(_applicationName, _collectionSuffix);
  359. Collection = Database.GetCollection<User>(CollectionName);
  360. // store element names
  361. var names = new MembershipElements();
  362. names.LowercaseUsername = Helper.GetElementNameFor<User>(p => p.LowercaseUsername);
  363. names.LastActivityDate = Helper.GetElementNameFor<User, DateTime>(p => p.LastActivityDate);
  364. names.LowercaseEmail = Helper.GetElementNameFor<User>(p => p.LowercaseEmail);
  365. ElementNames = names;
  366. // ensure indexes
  367. this.Collection.EnsureIndex(ElementNames.LowercaseUsername);
  368. this.Collection.EnsureIndex(ElementNames.LowercaseEmail);
  369. }
  370. /// <summary>
  371. /// Processes a request to update the password for a membership user.
  372. /// </summary>
  373. /// <param name="username">The user to update the password for.</param>
  374. /// <param name="oldPassword">The current password for the specified user.</param>
  375. /// <param name="newPassword">The new password for the specified user.</param>
  376. /// <returns>
  377. /// true if the password was updated successfully; otherwise, false.
  378. /// </returns>
  379. public override bool ChangePassword(string username, string oldPassword, string newPassword)
  380. {
  381. SecUtility.CheckParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH, "username");
  382. SecUtility.CheckParameter(ref oldPassword, true, true, null, MAX_PASSWORD_LENGTH, "oldPassword");
  383. SecUtility.CheckParameter(ref newPassword, true, true, null, MAX_PASSWORD_LENGTH, "newPassword");
  384. User user = GetUserByName(username, "ChangePassword");
  385. if (!CheckPassword(user, oldPassword, true))
  386. return false;
  387. if (newPassword.Length < this.MinRequiredPasswordLength)
  388. {
  389. throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
  390. Resources.Password_too_short,
  391. "newPassword", this.MinRequiredPasswordLength), "newPassword");
  392. }
  393. if (this.MinRequiredNonAlphanumericCharacters > 0)
  394. {
  395. int numNonAlphaNumericChars = 0;
  396. for (int i = 0; i < newPassword.Length; i++)
  397. {
  398. if (!char.IsLetterOrDigit(newPassword, i))
  399. {
  400. numNonAlphaNumericChars++;
  401. }
  402. }
  403. if (numNonAlphaNumericChars < this.MinRequiredNonAlphanumericCharacters)
  404. {
  405. throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
  406. Resources.Password_need_more_non_alpha_numeric_chars,
  407. "newPassword",
  408. this.MinRequiredNonAlphanumericCharacters), "newPassword");
  409. }
  410. }
  411. if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(newPassword, this.PasswordStrengthRegularExpression))
  412. {
  413. throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
  414. Resources.Password_does_not_match_regular_expression,
  415. "newPassword"), "newPassword");
  416. }
  417. // Raise event to let others check new username/password
  418. ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, false);
  419. OnValidatingPassword(args);
  420. if (args.Cancel)
  421. {
  422. if (args.FailureInformation != null)
  423. throw args.FailureInformation;
  424. else
  425. throw new MembershipPasswordException(Resources.Membership_Custom_Password_Validation_Failure);
  426. }
  427. // Save new password
  428. string encodedPwd = EncodePassword(newPassword, PasswordFormat, user.PasswordSalt);
  429. user.Password = encodedPwd;
  430. user.PasswordFormat = PasswordFormat;
  431. user.LastPasswordChangedDate = DateTime.UtcNow;
  432. var msg = String.Format("Unable to save new password for user '{0}'", username);
  433. Save(user, msg, "ChangePassword");
  434. return true;
  435. }
  436. /// <summary>
  437. /// Processes a request to update the password question and answer for a membership user.
  438. /// </summary>
  439. /// <param name="username">The user to change the password question and answer for.</param>
  440. /// <param name="password">The password for the specified user.</param>
  441. /// <param name="newPasswordQuestion">The new password question for the specified user.</param>
  442. /// <param name="newPasswordAnswer">The new password answer for the specified user.</param>
  443. /// <returns>
  444. /// true if the password question and answer are updated successfully; otherwise, false.
  445. /// </returns>
  446. public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
  447. {
  448. SecUtility.CheckParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH, "username");
  449. SecUtility.CheckParameter(ref password, true, true, null, MAX_PASSWORD_LENGTH, "password");
  450. User user = GetUserByName(username, "ChangePasswordQuestionAndAnswer");
  451. if (!CheckPassword(user, password, true))
  452. return false;
  453. SecUtility.CheckParameter(ref newPasswordQuestion, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, null, MAX_PASSWORD_QUESTION_LENGTH, "newPasswordQuestion");
  454. if (newPasswordAnswer != null)
  455. {
  456. newPasswordAnswer = newPasswordAnswer.Trim();
  457. }
  458. SecUtility.CheckParameter(ref newPasswordAnswer, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, null, MAX_PASSWORD_ANSWER_LENGTH, "newPasswordAnswer");
  459. string encodedPasswordAnswer = null;
  460. if (!string.IsNullOrEmpty(newPasswordAnswer))
  461. {
  462. encodedPasswordAnswer = EncodePassword(newPasswordAnswer.ToLowerInvariant(), user.PasswordFormat, user.PasswordSalt);
  463. }
  464. //SecUtility.CheckParameter(ref encodedPasswordAnswer, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, false, MAX_PASSWORD_ANSWER_LENGTH, "newPasswordAnswer");
  465. user.PasswordQuestion = newPasswordQuestion;
  466. user.PasswordAnswer = encodedPasswordAnswer;
  467. var msg = String.Format("Unable to save new password question and answer for user '{0}'", username);
  468. Save(user, msg, "ChangePasswordQuestionAndAnswer");
  469. return true;
  470. }
  471. /// <summary>
  472. /// Adds a new membership user to the data source.
  473. /// </summary>
  474. /// <param name="username">The user name for the new user.</param>
  475. /// <param name="password">The password for the new user.</param>
  476. /// <param name="email">The e-mail address for the new user.</param>
  477. /// <param name="passwordQuestion">The password question for the new user.</param>
  478. /// <param name="passwordAnswer">The password answer for the new user</param>
  479. /// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
  480. /// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
  481. /// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"/> enumeration value indicating whether the user was created successfully.</param>
  482. /// <returns>
  483. /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the information for the newly created user.
  484. /// </returns>
  485. public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
  486. {
  487. #region Validation
  488. if (!SecUtility.ValidateParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH))
  489. {
  490. status = MembershipCreateStatus.InvalidUserName;
  491. return null;
  492. }
  493. if (!SecUtility.ValidateParameter(ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, InvalidEmailCharacters, MAX_EMAIL_LENGTH))
  494. {
  495. status = MembershipCreateStatus.InvalidEmail;
  496. return null;
  497. }
  498. if (!SecUtility.ValidateParameter(ref password, true, true, null, MAX_PASSWORD_LENGTH))
  499. {
  500. status = MembershipCreateStatus.InvalidPassword;
  501. return null;
  502. }
  503. if (password.Length > MAX_PASSWORD_LENGTH)
  504. {
  505. status = MembershipCreateStatus.InvalidPassword;
  506. return null;
  507. }
  508. if (null != passwordAnswer)
  509. {
  510. passwordAnswer = passwordAnswer.Trim();
  511. }
  512. if (string.IsNullOrEmpty(passwordAnswer))
  513. {
  514. if (RequiresQuestionAndAnswer)
  515. {
  516. status = MembershipCreateStatus.InvalidAnswer;
  517. return null;
  518. }
  519. }
  520. else
  521. {
  522. if (passwordAnswer.Length > MAX_PASSWORD_ANSWER_LENGTH)
  523. {
  524. status = MembershipCreateStatus.InvalidAnswer;
  525. return null;
  526. }
  527. }
  528. if (!SecUtility.ValidateParameter(ref passwordQuestion, this.RequiresQuestionAndAnswer, true, null, MAX_PASSWORD_QUESTION_LENGTH))
  529. {
  530. status = MembershipCreateStatus.InvalidQuestion;
  531. return null;
  532. }
  533. if ((null != providerUserKey) && !(providerUserKey is Guid))
  534. {
  535. status = MembershipCreateStatus.InvalidProviderUserKey;
  536. return null;
  537. }
  538. if (password.Length < this.MinRequiredPasswordLength)
  539. {
  540. status = MembershipCreateStatus.InvalidPassword;
  541. return null;
  542. }
  543. if (this.MinRequiredNonAlphanumericCharacters > 0)
  544. {
  545. int numNonAlphaNumericChars = 0;
  546. for (int i = 0; i < password.Length; i++)
  547. {
  548. if (!char.IsLetterOrDigit(password, i))
  549. {
  550. numNonAlphaNumericChars++;
  551. }
  552. }
  553. if (numNonAlphaNumericChars < this.MinRequiredNonAlphanumericCharacters)
  554. {
  555. status = MembershipCreateStatus.InvalidPassword;
  556. return null;
  557. }
  558. }
  559. if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(password, this.PasswordStrengthRegularExpression))
  560. {
  561. status = MembershipCreateStatus.InvalidPassword;
  562. return null;
  563. }
  564. #endregion
  565. ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
  566. OnValidatingPassword(args);
  567. if (args.Cancel)
  568. {
  569. status = MembershipCreateStatus.InvalidPassword;
  570. return null;
  571. }
  572. if (RequiresUniqueEmail && !String.IsNullOrEmpty(GetUserNameByEmail(email)))
  573. {
  574. status = MembershipCreateStatus.DuplicateEmail;
  575. return null;
  576. }
  577. MembershipUser u = GetUser(username, false);
  578. if (null != u)
  579. {
  580. status = MembershipCreateStatus.DuplicateUserName;
  581. return null;
  582. }
  583. DateTime createDate = DateTime.UtcNow;
  584. if (null == providerUserKey)
  585. {
  586. providerUserKey = Guid.NewGuid();
  587. }
  588. else
  589. {
  590. if (!(providerUserKey is Guid))
  591. {
  592. status = MembershipCreateStatus.InvalidProviderUserKey;
  593. return null;
  594. }
  595. }
  596. var createAt = DateTime.UtcNow;
  597. string salt = GenerateSalt();
  598. var answer = passwordAnswer;
  599. if (null != answer)
  600. {
  601. answer = EncodePassword(passwordAnswer.ToLowerInvariant(), PasswordFormat, salt);
  602. }
  603. var user = new User();
  604. user.Id = (Guid)providerUserKey;
  605. user.Username = username;
  606. user.LowercaseUsername = username.ToLowerInvariant();
  607. user.DisplayName = username;
  608. user.Email = email;
  609. user.LowercaseEmail = (null == email) ? null : email.ToLowerInvariant();
  610. user.Password = EncodePassword(password, PasswordFormat, salt);
  611. user.PasswordQuestion = passwordQuestion;
  612. user.PasswordAnswer = answer;
  613. user.PasswordFormat = PasswordFormat;
  614. user.PasswordSalt = salt;
  615. user.IsApproved = isApproved;
  616. user.LastPasswordChangedDate = DateTime.MinValue;
  617. user.CreateDate = createAt;
  618. user.IsLockedOut = false;
  619. user.LastLockedOutDate = DateTime.MinValue;
  620. user.LastActivityDate = createAt;
  621. user.FailedPasswordAnswerAttemptCount = 0;
  622. user.FailedPasswordAnswerAttemptWindowStart = DateTime.MinValue;
  623. user.FailedPasswordAttemptCount = 0;
  624. user.FailedPasswordAttemptWindowStart = DateTime.MinValue;
  625. var msg = String.Format("Error creating new User '{0}'", username);
  626. Save(user, msg, "CreateUser");
  627. status = MembershipCreateStatus.Success;
  628. return GetUser(username, false);
  629. }
  630. /// <summary>
  631. /// Removes a user from the membership data source.
  632. /// </summary>
  633. /// <param name="username">The name of the user to delete.</param>
  634. /// <param name="deleteAllRelatedData">true to delete data related to the user from the database; false to leave data related to the user in the database.</param>
  635. /// <returns>
  636. /// true if the user was successfully deleted; otherwise, false.
  637. /// </returns>
  638. public override bool DeleteUser(string username, bool deleteAllRelatedData)
  639. {
  640. if (String.IsNullOrWhiteSpace(username)) return false;
  641. var query = Query.EQ(ElementNames.LowercaseUsername, username.ToLowerInvariant());
  642. var result = Collection.Remove(query, SafeMode.True);
  643. return result.Ok;
  644. }
  645. /// <summary>
  646. /// Gets a collection of membership users where the user name matches the specified string.
  647. /// </summary>
  648. /// <param name="usernameToMatch">The user name to search for.
  649. /// Match string may contain the standard SQL LIKE wildcard: %
  650. /// "sm%" -> StartsWith("sm")
  651. /// "%ith" -> EndsWith("ith")
  652. /// "%mit%" -> Contains("mit")
  653. /// </param>
  654. /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
  655. /// <param name="pageSize">The size of the page of results to return.</param>
  656. /// <param name="totalRecords">The total number of matched users.</param>
  657. /// <returns>
  658. /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
  659. /// </returns>
  660. public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
  661. {
  662. return FindUsersBy(ElementNames.LowercaseUsername, usernameToMatch, pageIndex, pageSize, out totalRecords);
  663. }
  664. /// <summary>
  665. /// Gets a collection of membership users where the e-mail address matches the specified string.
  666. /// </summary>
  667. /// <param name="emailToMatch">The e-mail address to search for.
  668. /// Match string may contain the standard SQL LIKE wildcard: %
  669. /// "sm%" -> StartsWith("sm")
  670. /// "%ith" -> EndsWith("ith")
  671. /// "%mit%" -> Contains("mit")
  672. /// </param>
  673. /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
  674. /// <param name="pageSize">The size of the page of results to return.</param>
  675. /// <param name="totalRecords">The total number of matched users.</param>
  676. /// <returns>
  677. /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
  678. /// </returns>
  679. public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
  680. {
  681. return FindUsersBy(ElementNames.LowercaseEmail, emailToMatch, pageIndex, pageSize, out totalRecords);
  682. }
  683. /// <summary>
  684. /// Gets a collection of all the users in the data source in pages of data.
  685. /// </summary>
  686. /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
  687. /// <param name="pageSize">The size of the page of results to return.</param>
  688. /// <param name="totalRecords">The total number of matched users.</param>
  689. /// <returns>
  690. /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
  691. /// </returns>
  692. public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
  693. {
  694. MembershipUserCollection users = new MembershipUserCollection();
  695. var matches = Collection.AsQueryable().Skip(pageIndex * pageSize)
  696. .Take(pageSize)
  697. .ToList();
  698. if (null == matches)
  699. {
  700. totalRecords = 0;
  701. return users;
  702. }
  703. // execute second query to get total count
  704. totalRecords = (int)Collection.Count();
  705. matches.ForEach(u => users.Add(ToMembershipUser(u)));
  706. return users;
  707. }
  708. /// <summary>
  709. /// Gets the number of users currently accessing the application.
  710. /// </summary>
  711. /// <returns>
  712. /// The number of users currently accessing the application.
  713. /// </returns>
  714. public override int GetNumberOfUsersOnline()
  715. {
  716. // http://msdn.microsoft.com/en-us/library/system.web.security.membership.userisonlinetimewindow.aspx
  717. TimeSpan onlineSpan = new TimeSpan(0, Membership.UserIsOnlineTimeWindow, 0);
  718. DateTime compareTime = DateTime.UtcNow.Subtract(onlineSpan);
  719. var count = Collection.AsQueryable().Where(u => u.LastActivityDate > compareTime).Count();
  720. return count;
  721. }
  722. /// <summary>
  723. /// Gets the password for the specified user name from the data source.
  724. /// </summary>
  725. /// <param name="username">The user to retrieve the password for.</param>
  726. /// <param name="answer">The password answer for the user.</param>
  727. /// <returns>
  728. /// The password for the specified user name.
  729. /// </returns>
  730. public override string GetPassword(string username, string answer)
  731. {
  732. if (!EnablePasswordRetrieval)
  733. {
  734. throw new ProviderException(Resources.Membership_PasswordRetrieval_not_supported);
  735. }
  736. User user = GetUserByName(username, "GetPassword");
  737. if (null == user) {
  738. throw new MembershipPasswordException(Resources.Membership_UserNotFound);
  739. }
  740. if (user.IsLockedOut)
  741. {
  742. throw new MembershipPasswordException(Resources.Membership_AccountLockOut);
  743. }
  744. if (RequiresQuestionAndAnswer && !ComparePasswords(answer, user.PasswordAnswer, user.PasswordSalt, user.PasswordFormat))
  745. {
  746. UpdateFailureCount(user, "passwordAnswer", false);
  747. throw new MembershipPasswordException(Resources.Membership_WrongAnswer);
  748. }
  749. var password = user.Password;
  750. if (user.PasswordFormat == MembershipPasswordFormat.Encrypted)
  751. {
  752. password = UnEncodePassword(password, user.PasswordFormat);
  753. }
  754. return password;
  755. }
  756. /// <summary>
  757. /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
  758. /// </summary>
  759. /// <param name="username">The name of the user to get information for.</param>
  760. /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
  761. /// <returns>
  762. /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
  763. /// </returns>
  764. public override MembershipUser GetUser(string username, bool userIsOnline)
  765. {
  766. if (String.IsNullOrWhiteSpace(username)) return null;
  767. if (userIsOnline)
  768. {
  769. // update the last-activity date
  770. var users = Collection;
  771. var map = BsonClassMap.LookupClassMap(typeof(User));
  772. var query = Query.EQ(ElementNames.LowercaseUsername, username.ToLowerInvariant());
  773. var update = Update.Set(ElementNames.LastActivityDate, DateTime.UtcNow);
  774. var result = users.FindAndModify(query, SortBy.Null, update, returnNew: true);
  775. if (!result.Ok)
  776. {
  777. HandleDataExceptionAndThrow(new ProviderException(result.ErrorMessage), "GetUser");
  778. }
  779. var user = BsonSerializer.Deserialize<User>(result.ModifiedDocument);
  780. return ToMembershipUser(user);
  781. }
  782. else
  783. {
  784. User user = Collection.AsQueryable().Where(u => u.LowercaseUsername == username.ToLowerInvariant()).FirstOrDefault();
  785. return ToMembershipUser(user);
  786. }
  787. }
  788. /// <summary>
  789. /// Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
  790. /// </summary>
  791. /// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param>
  792. /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
  793. /// <returns>
  794. /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
  795. /// </returns>
  796. public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
  797. {
  798. if (!(providerUserKey is Guid)){
  799. throw new ArgumentException(Resources.Membership_InvalidProviderUserKey, "providerUserKey");
  800. }
  801. if (userIsOnline)
  802. {
  803. // update the last-activity date
  804. var query = Query.EQ("_id", (Guid)providerUserKey);
  805. var update = Update.Set(ElementNames.LastActivityDate, DateTime.UtcNow);
  806. var result = Collection.FindAndModify(query, SortBy.Null, update, returnNew: true);
  807. if (!result.Ok)
  808. {
  809. HandleDataExceptionAndThrow(new ProviderException(result.ErrorMessage), "GetUser");
  810. }
  811. var user = BsonSerializer.Deserialize<User>(result.ModifiedDocument);
  812. return ToMembershipUser(user);
  813. }
  814. else
  815. {
  816. User user = Collection.AsQueryable().Where(u => u.Id == (Guid)providerUserKey).FirstOrDefault();
  817. return ToMembershipUser(user);
  818. }
  819. }
  820. /// <summary>
  821. /// Gets the user name associated with the specified e-mail address.
  822. /// </summary>
  823. /// <param name="email">The e-mail address to search for.</param>
  824. /// <returns>
  825. /// The user name associated with the specified e-mail address. If no match is found, return null.
  826. /// </returns>
  827. public override string GetUserNameByEmail(string email)
  828. {
  829. if (null == email)
  830. return null;
  831. var username = Collection.AsQueryable().Where(u => u.LowercaseEmail == email.ToLowerInvariant()).Select(u => u.Username).FirstOrDefault();
  832. return username;
  833. }
  834. /// <summary>
  835. /// Resets a user's password to a new, automatically generated password.
  836. /// </summary>
  837. /// <param name="username">The user to reset the password for.</param>
  838. /// <param name="passwordAnswer">The password answer for the specified user.</param>
  839. /// <returns>The new password for the specified user.</returns>
  840. /// <exception cref="T:System.Configuration.Provider.ProviderException">username is not found in the membership database.- or -The
  841. /// change password action was canceled by a subscriber to the System.Web.Security.Membership.ValidatePassword
  842. /// event and the <see cref="P:System.Web.Security.ValidatePasswordEventArgs.FailureInformation"></see> property was null.- or -An
  843. /// error occurred while retrieving the password from the database. </exception>
  844. /// <exception cref="T:System.NotSupportedException"><see cref="P:System.Web.Security.SqlMembershipProvider.EnablePasswordReset"></see>
  845. /// is set to false. </exception>
  846. /// <exception cref="T:System.ArgumentException">username is an empty string (""), contains a comma, or is longer than 256 characters.
  847. /// - or -passwordAnswer is an empty string or is longer than 128 characters and
  848. /// <see cref="P:System.Web.Security.SqlMembershipProvider.RequiresQuestionAndAnswer"></see> is true.- or -passwordAnswer is longer
  849. /// than 128 characters after encoding.</exception>
  850. /// <exception cref="T:System.ArgumentNullException">username is null.- or -passwordAnswer is null and
  851. /// <see cref="P:System.Web.Security.SqlMembershipProvider.RequiresQuestionAndAnswer"></see> is true.</exception>
  852. /// <exception cref="T:System.Web.Security.MembershipPasswordException">passwordAnswer is invalid. - or -The user account is currently locked out.</exception>
  853. public override string ResetPassword(string username, string answer)
  854. {
  855. if (!this.EnablePasswordReset)
  856. {
  857. throw new NotSupportedException(Resources.Not_configured_to_support_password_resets);
  858. }
  859. User user = GetUserByName(username, "ResetPassword");
  860. if (null == user)
  861. {
  862. throw new ProviderException(Resources.Membership_UserNotFound);
  863. }
  864. if (user.IsLockedOut)
  865. {
  866. throw new ProviderException(Resources.Membership_AccountLockOut);
  867. }
  868. if (RequiresQuestionAndAnswer &&
  869. (null == answer || !ComparePasswords(answer, user.PasswordAnswer, user.PasswordSalt, user.PasswordFormat)))
  870. {
  871. UpdateFailureCount(user, "passwordAnswer", false);
  872. throw new MembershipPasswordException(Resources.Membership_InvalidAnswer);
  873. }
  874. string newPassword = Membership.GeneratePassword(NEW_PASSWORD_LENGTH, MinRequiredNonAlphanumericCharacters);
  875. ValidatePasswordEventArgs args =
  876. new ValidatePasswordEventArgs(username, newPassword, true);
  877. OnValidatingPassword(args);
  878. if (args.Cancel)
  879. if (args.FailureInformation != null)
  880. throw args.FailureInformation;
  881. else
  882. throw new MembershipPasswordException(Resources.Membership_Custom_Password_Validation_Failure);
  883. user.Password = EncodePassword(newPassword, user.PasswordFormat, user.PasswordSalt);
  884. user.LastPasswordChangedDate = DateTime.UtcNow;
  885. {
  886. var msg = String.Format("Error saving User '{0}' while resetting password", username);
  887. Save(user, msg, "ResetPassword");
  888. }
  889. return newPassword;
  890. }
  891. /// <summary>
  892. /// Unlocks the user.
  893. /// </summary>
  894. /// <param name="username">The username.</param>
  895. /// <returns>Returns true if user was unlocked; otherwise returns false.</returns>
  896. public override bool UnlockUser(string username)
  897. {
  898. User user = GetUserByName(username, "UnlockUser");
  899. if (null == user)
  900. {
  901. return false;
  902. }
  903. user.IsLockedOut = false;
  904. user.LastLockedOutDate = DateTime.MinValue;
  905. user.FailedPasswordAnswerAttemptCount = 0;
  906. user.FailedPasswordAnswerAttemptWindowStart = DateTime.MinValue;
  907. user.FailedPasswordAttemptCount = 0;
  908. user.FailedPasswordAttemptWindowStart = DateTime.MinValue;
  909. var msg = String.Format("Error saving User '{0}' while attempting to remove account lock", username);
  910. Save(user, msg, "UnlockUser");
  911. return true;
  912. }
  913. /// <summary>
  914. /// Updates information about a user in the data source.
  915. /// </summary>
  916. /// <param name="user">A <see cref="T:System.Web.Security.MembershipUser"/> object that represents the user to update and the updated information for the user.</param>
  917. public override void UpdateUser(MembershipUser user)
  918. {
  919. User u = GetUserByName(user.UserName, "UpdateUser");
  920. if (null == user)
  921. {
  922. throw new ProviderException(Resources.Membership_UserNotFound);
  923. }
  924. u.Email = user.Email;
  925. u.Comment = user.Comment;
  926. u.IsApproved = user.IsApproved;
  927. u.LastLoginDate = user.LastLoginDate;
  928. u.LastActivityDate = user.LastActivityDate;
  929. {
  930. var msg = "Error saving user while attempting to update Email and IsApproved status";
  931. Save(u, msg, "UpdateUser");
  932. }
  933. }
  934. /// <summary>
  935. /// Verifies that the specified user name and password exist in the data source.
  936. /// </summary>
  937. /// <param name="username">The name of the user to validate.</param>
  938. /// <param name="password">The password for the specified user.</param>
  939. /// <returns>
  940. /// true if the specified username and password are valid; otherwise, false.
  941. /// </returns>
  942. public override bool ValidateUser(string username, string password)
  943. {
  944. if (!SecUtility.ValidateParameter(ref username, true, true, InvalidUsernameCharacters, MAX_USERNAME_LENGTH) || !SecUtility.ValidateParameter(ref password, true, true, null, MAX_PASSWORD_LENGTH))
  945. {
  946. return false;
  947. }
  948. User user = GetUserByName(username, "ValidateUser");
  949. if (null == user || user.IsLockedOut || !user.IsApproved)
  950. {
  951. return false;
  952. }
  953. bool passwordsMatch = ComparePasswords(password, user.Password, user.PasswordSalt, user.PasswordFormat);
  954. if (!passwordsMatch)
  955. {
  956. // update invalid try count
  957. UpdateFailureCount(user, "password", isAuthenticated: false);
  958. return false;
  959. }
  960. // User is authenticated. Update last activity and last login dates and failure counts.
  961. user.LastActivityDate = DateTime.UtcNow;
  962. user.LastLoginDate = DateTime.UtcNow;
  963. user.FailedPasswordAnswerAttemptCount = 0;
  964. user.FailedPasswordAttemptCount = 0;
  965. user.FailedPasswordAnswerAttemptWindowStart = DateTime.MinValue;
  966. user.FailedPasswordAttemptWindowStart = DateTime.MinValue;
  967. var msg = String.Format("Error updating User '{0}'s last login date while validating", username);
  968. Save(user, msg, "ValidateUser");
  969. return true;
  970. }
  971. #endregion
  972. #region Protected Methods
  973. protected void ValidatePwdStrengthRegularExpression()
  974. {
  975. // Validate regular expression, if supplied.
  976. if (null == _passwordStrengthRegularExpression)
  977. _passwordStrengthRegularExpression = String.Empty;
  978. _passwordStrengthRegularExpression = _passwordStrengthRegularExpression.Trim();
  979. if (_passwordStrengthRegularExpression.Length > 0)
  980. {
  981. try
  982. {
  983. new Regex(_passwordStrengthRegularExpression);
  984. }
  985. catch (ArgumentException ex)
  986. {
  987. throw new ProviderException(ex.Message, ex);
  988. }
  989. }
  990. }
  991. protected MembershipUser ToMembershipUser(User us

Large files files are truncated, but you can click here to view the full file