PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/aspclassiccompiler/AzureStoreAsp/Assets/AspProviders/TableStorageMembershipProvider.cs

#
C# | 1586 lines | 1302 code | 161 blank | 123 comment | 178 complexity | 65f212217bf73eb1cb5e8cbf444d519f MD5 | raw file
Possible License(s): Apache-2.0, AGPL-3.0

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

  1. // ----------------------------------------------------------------------------------
  2. // Microsoft Developer & Platform Evangelism
  3. //
  4. // Copyright (c) Microsoft Corporation. All rights reserved.
  5. //
  6. // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
  7. // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
  8. // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
  9. // ----------------------------------------------------------------------------------
  10. // The example companies, organizations, products, domain names,
  11. // e-mail addresses, logos, people, places, and events depicted
  12. // herein are fictitious. No association with any real company,
  13. // organization, product, domain name, email address, logo, person,
  14. // places, or events is intended or should be inferred.
  15. // ----------------------------------------------------------------------------------
  16. //
  17. // <copyright file="TableStorageMembershipProvider.cs" company="Microsoft">
  18. // Copyright (c) Microsoft Corporation. All rights reserved.
  19. // </copyright>
  20. //
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Collections.Specialized;
  24. using System.Configuration.Provider;
  25. using System.Data.Services.Client;
  26. using System.Diagnostics;
  27. using System.Globalization;
  28. using System.Linq;
  29. using System.Net;
  30. using System.Security;
  31. using System.Security.Cryptography;
  32. using System.Text;
  33. using System.Text.RegularExpressions;
  34. using System.Web;
  35. using System.Web.Security;
  36. using Microsoft.Samples.ServiceHosting.StorageClient;
  37. namespace Microsoft.Samples.ServiceHosting.AspProviders
  38. {
  39. /// <summary>
  40. /// This class allows DevtableGen to generate the correct table (named 'Membership')
  41. /// </summary>
  42. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses",
  43. Justification="Class is used by devtablegen to generate database for the development storage tool")]
  44. internal class MembershipDataServiceContext : TableStorageDataServiceContext
  45. {
  46. public IQueryable<MembershipRow> Membership
  47. {
  48. get
  49. {
  50. return this.CreateQuery<MembershipRow>("Membership");
  51. }
  52. }
  53. }
  54. internal class MembershipRow : TableStorageEntity, IComparable
  55. {
  56. private string _applicationName;
  57. private string _userName;
  58. private string _password;
  59. private string _passwordSalt;
  60. private string _email;
  61. private string _passwordAnswer;
  62. private string _passwordQuestion;
  63. private string _comment;
  64. private string _profileBlobName;
  65. private DateTime _createDate;
  66. private DateTime _lastLoginDate;
  67. private DateTime _lastPasswordChangedDate;
  68. private DateTime _lastLockoutDate;
  69. private DateTime _lastActivityDate;
  70. private DateTime _failedPasswordAttemptWindowStart;
  71. private DateTime _failedPasswordAnswerAttemptWindowStart;
  72. private DateTime _profileLastUpdated;
  73. // partition key is applicationName + userName
  74. // rowKey is empty
  75. public MembershipRow(string applicationName, string userName)
  76. : base()
  77. {
  78. if (string.IsNullOrEmpty(applicationName)) {
  79. throw new ProviderException("Partition key cannot be empty!");
  80. }
  81. if (string.IsNullOrEmpty(userName))
  82. {
  83. throw new ProviderException("RowKey cannot be empty!");
  84. }
  85. // applicationName + userName is partitionKey
  86. // the reasoning behind this is that we want to strive for the best scalability possible
  87. // chosing applicationName as the partition key and userName as row key would not give us that because
  88. // it would mean that a site with millions of users had all users on a single partition
  89. // having the applicationName and userName inside the partition key is important for queries as queries
  90. // for users in a single application are the most frequent
  91. // these queries are faster because application name and user name are part of the key
  92. PartitionKey = SecUtility.CombineToKey(applicationName, userName);
  93. RowKey = string.Empty;
  94. ApplicationName = applicationName;
  95. UserName = userName;
  96. Password = string.Empty;
  97. PasswordSalt = string.Empty;
  98. Email = string.Empty;
  99. PasswordAnswer = string.Empty;
  100. PasswordQuestion = string.Empty;
  101. Comment = string.Empty;
  102. ProfileBlobName = string.Empty;
  103. CreateDateUtc = TableStorageConstants.MinSupportedDateTime;
  104. LastLoginDateUtc = TableStorageConstants.MinSupportedDateTime;
  105. LastActivityDateUtc = TableStorageConstants.MinSupportedDateTime;
  106. LastLockoutDateUtc = TableStorageConstants.MinSupportedDateTime;
  107. LastPasswordChangedDateUtc = TableStorageConstants.MinSupportedDateTime;
  108. FailedPasswordAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
  109. FailedPasswordAnswerAttemptWindowStartUtc = TableStorageConstants.MinSupportedDateTime;
  110. ProfileLastUpdatedUtc = TableStorageConstants.MinSupportedDateTime;
  111. ProfileIsCreatedByProfileProvider = false;
  112. ProfileSize = 0;
  113. }
  114. public MembershipRow()
  115. : base()
  116. {
  117. }
  118. public string ApplicationName
  119. {
  120. set
  121. {
  122. if (value == null)
  123. {
  124. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  125. }
  126. _applicationName = value;
  127. PartitionKey = SecUtility.CombineToKey(ApplicationName, UserName);
  128. }
  129. get
  130. {
  131. return _applicationName;
  132. }
  133. }
  134. public string UserName
  135. {
  136. set {
  137. if (value == null)
  138. {
  139. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  140. }
  141. _userName = value;
  142. PartitionKey = SecUtility.CombineToKey(ApplicationName, UserName);
  143. }
  144. get
  145. {
  146. return _userName;
  147. }
  148. }
  149. public Guid UserId {
  150. set;
  151. get;
  152. }
  153. public string Password
  154. {
  155. set
  156. {
  157. if (value == null)
  158. {
  159. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  160. }
  161. _password = value;
  162. }
  163. get
  164. {
  165. return _password;
  166. }
  167. }
  168. public int PasswordFormat
  169. {
  170. set;
  171. get;
  172. }
  173. public string PasswordSalt
  174. {
  175. set
  176. {
  177. if (value == null)
  178. {
  179. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  180. }
  181. _passwordSalt = value;
  182. }
  183. get
  184. {
  185. return _passwordSalt;
  186. }
  187. }
  188. public string Email
  189. {
  190. set
  191. {
  192. if (value == null)
  193. {
  194. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  195. }
  196. _email = value;
  197. }
  198. get
  199. {
  200. return _email;
  201. }
  202. }
  203. public string PasswordQuestion
  204. {
  205. set
  206. {
  207. if (value == null)
  208. {
  209. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  210. }
  211. _passwordQuestion = value;
  212. }
  213. get
  214. {
  215. return _passwordQuestion;
  216. }
  217. }
  218. public string PasswordAnswer
  219. {
  220. set
  221. {
  222. if (value == null)
  223. {
  224. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  225. }
  226. _passwordAnswer = value;
  227. }
  228. get
  229. {
  230. return _passwordAnswer;
  231. }
  232. }
  233. public bool IsApproved
  234. {
  235. set;
  236. get;
  237. }
  238. public bool IsAnonymous
  239. {
  240. set;
  241. get;
  242. }
  243. public bool IsLockedOut
  244. {
  245. set;
  246. get;
  247. }
  248. public DateTime CreateDateUtc {
  249. set {
  250. SecUtility.SetUtcTime(value, out _createDate);
  251. }
  252. get {
  253. return _createDate;
  254. }
  255. }
  256. public DateTime LastLoginDateUtc
  257. {
  258. set
  259. {
  260. SecUtility.SetUtcTime(value, out _lastLoginDate);
  261. }
  262. get
  263. {
  264. return _lastLoginDate;
  265. }
  266. }
  267. public DateTime LastPasswordChangedDateUtc
  268. {
  269. set {
  270. SecUtility.SetUtcTime(value, out _lastPasswordChangedDate);
  271. }
  272. get {
  273. return _lastPasswordChangedDate;
  274. }
  275. }
  276. public DateTime LastLockoutDateUtc
  277. {
  278. set
  279. {
  280. SecUtility.SetUtcTime(value, out _lastLockoutDate);
  281. }
  282. get
  283. {
  284. return _lastLockoutDate;
  285. }
  286. }
  287. public DateTime LastActivityDateUtc {
  288. set {
  289. SecUtility.SetUtcTime(value, out _lastActivityDate);
  290. }
  291. get {
  292. return _lastActivityDate;
  293. }
  294. }
  295. public int FailedPasswordAttemptCount
  296. {
  297. set;
  298. get;
  299. }
  300. public DateTime FailedPasswordAttemptWindowStartUtc
  301. {
  302. set {
  303. SecUtility.SetUtcTime(value, out _failedPasswordAttemptWindowStart);
  304. }
  305. get {
  306. return _failedPasswordAttemptWindowStart;
  307. }
  308. }
  309. public int FailedPasswordAnswerAttemptCount
  310. {
  311. set;
  312. get;
  313. }
  314. public DateTime FailedPasswordAnswerAttemptWindowStartUtc
  315. {
  316. set {
  317. SecUtility.SetUtcTime(value, out _failedPasswordAnswerAttemptWindowStart);
  318. }
  319. get {
  320. return _failedPasswordAnswerAttemptWindowStart;
  321. }
  322. }
  323. public string Comment
  324. {
  325. set
  326. {
  327. if (value == null)
  328. {
  329. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value.");
  330. }
  331. _comment = value;
  332. }
  333. get
  334. {
  335. return _comment;
  336. }
  337. }
  338. #region Profile provider related properties
  339. public DateTime ProfileLastUpdatedUtc
  340. {
  341. set {
  342. SecUtility.SetUtcTime(value, out _profileLastUpdated);
  343. }
  344. get {
  345. return _profileLastUpdated;
  346. }
  347. }
  348. public int ProfileSize
  349. {
  350. set;
  351. get;
  352. }
  353. public string ProfileBlobName
  354. {
  355. set
  356. {
  357. if (value == null)
  358. {
  359. throw new ArgumentException("To ensure string values are always updated, this implementation does not allow null as a string value..");
  360. }
  361. _profileBlobName = value;
  362. }
  363. get
  364. {
  365. return _profileBlobName;
  366. }
  367. }
  368. public bool ProfileIsCreatedByProfileProvider
  369. {
  370. set;
  371. get;
  372. }
  373. #endregion
  374. public int CompareTo(object obj)
  375. {
  376. if (obj == null)
  377. {
  378. return 1;
  379. }
  380. MembershipRow row = obj as MembershipRow;
  381. if (row == null)
  382. {
  383. throw new ArgumentException("The parameter obj is not of type MembershipRow.");
  384. }
  385. return string.Compare(this.UserName, row.UserName, StringComparison.Ordinal);
  386. }
  387. }
  388. internal class EmailComparer: IComparer<MembershipRow> {
  389. public int Compare(MembershipRow x, MembershipRow y)
  390. {
  391. if (x == null)
  392. {
  393. if (y == null)
  394. {
  395. return 0;
  396. }
  397. else
  398. {
  399. return -1;
  400. }
  401. }
  402. else
  403. {
  404. return string.Compare(x.Email, y.Email, StringComparison.Ordinal);
  405. }
  406. }
  407. }
  408. public class TableStorageMembershipProvider : MembershipProvider
  409. {
  410. #region Member variables and constants
  411. private const int MaxTablePasswordSize = 128;
  412. private const int MaxTableEmailLength = 256;
  413. private const int MaxTablePasswordQuestionLength = 256;
  414. private const int MaxTablePasswordAnswerLength = 128;
  415. private const int MaxFindUserSize = 10000;
  416. // this is the absolute minimum password size when generating new password
  417. // the number is chosen so that it corresponds to the SQL membership provider implementation
  418. private const int MinGeneratedPasswordSize = 14;
  419. private const int NumRetries = 3;
  420. // member variables shared between most providers
  421. private string _applicationName;
  422. private string _accountName;
  423. private string _sharedKey;
  424. private string _tableName;
  425. private string _tableServiceBaseUri;
  426. private TableStorage _tableStorage;
  427. private object _lock = new object();
  428. // retry policies are used sparingly throughout this class because we often want to be
  429. // very conservative when it comes to security-related functions
  430. private RetryPolicy _tableRetry = RetryPolicies.RetryN(NumRetries, TimeSpan.FromSeconds(1));
  431. // membership provider specific member variables
  432. private bool _enablePasswordRetrieval;
  433. private bool _enablePasswordReset;
  434. private bool _requiresQuestionAndAnswer;
  435. private bool _requiresUniqueEmail;
  436. private int _maxInvalidPasswordAttempts;
  437. private int _passwordAttemptWindow;
  438. private int _minRequiredPasswordLength;
  439. private int _minRequiredNonalphanumericCharacters;
  440. private string _passwordStrengthRegularExpression;
  441. private MembershipPasswordFormat _passwordFormat;
  442. #endregion
  443. #region Properties
  444. /// <summary>
  445. /// The app name is not used in this implementation.
  446. /// </summary>
  447. public override string ApplicationName
  448. {
  449. get { return _applicationName; }
  450. set
  451. {
  452. lock (_lock)
  453. {
  454. SecUtility.CheckParameter(ref value, true, true, true, Constants.MaxTableApplicationNameLength, "ApplicationName");
  455. _applicationName = value;
  456. }
  457. }
  458. }
  459. public override bool EnablePasswordRetrieval
  460. {
  461. get
  462. {
  463. return _enablePasswordRetrieval;
  464. }
  465. }
  466. public override bool EnablePasswordReset
  467. {
  468. get
  469. {
  470. return _enablePasswordReset;
  471. }
  472. }
  473. public override bool RequiresQuestionAndAnswer
  474. {
  475. get
  476. {
  477. return _requiresQuestionAndAnswer;
  478. }
  479. }
  480. public override bool RequiresUniqueEmail
  481. {
  482. get
  483. {
  484. return _requiresUniqueEmail;
  485. }
  486. }
  487. public override MembershipPasswordFormat PasswordFormat
  488. {
  489. get
  490. {
  491. return _passwordFormat;
  492. }
  493. }
  494. public override int MaxInvalidPasswordAttempts
  495. {
  496. get
  497. {
  498. return _maxInvalidPasswordAttempts;
  499. }
  500. }
  501. public override int PasswordAttemptWindow
  502. {
  503. get
  504. {
  505. return _passwordAttemptWindow;
  506. }
  507. }
  508. public override int MinRequiredPasswordLength
  509. {
  510. get
  511. {
  512. return _minRequiredPasswordLength;
  513. }
  514. }
  515. public override int MinRequiredNonAlphanumericCharacters
  516. {
  517. get
  518. {
  519. return _minRequiredNonalphanumericCharacters;
  520. }
  521. }
  522. public override string PasswordStrengthRegularExpression
  523. {
  524. get
  525. {
  526. return _passwordStrengthRegularExpression;
  527. }
  528. }
  529. #endregion
  530. #region Public methods
  531. /// <summary>
  532. /// Initializes the membership provider. This is the only function that cannot be accessed
  533. /// in parallel by multiple applications. The function reads the properties of the
  534. /// provider specified in the Web.config file and stores them in member variables.
  535. /// </summary>
  536. public override void Initialize(string name, NameValueCollection config)
  537. {
  538. if (config == null)
  539. {
  540. throw new ArgumentNullException("config");
  541. }
  542. if (String.IsNullOrEmpty(name))
  543. {
  544. name = "TableStorageMembershipProvider";
  545. }
  546. if (string.IsNullOrEmpty(config["description"]))
  547. {
  548. config.Remove("description");
  549. config.Add("description", "Table storage-based membership provider");
  550. }
  551. base.Initialize(name, config);
  552. bool allowInsecureRemoteEndpoints = Configuration.GetBooleanValue(config, "allowInsecureRemoteEndpoints", false);
  553. _enablePasswordRetrieval = Configuration.GetBooleanValue(config, "enablePasswordRetrieval", false);
  554. _enablePasswordReset = Configuration.GetBooleanValue(config, "enablePasswordReset", true);
  555. _requiresQuestionAndAnswer = Configuration.GetBooleanValue(config, "requiresQuestionAndAnswer", true);
  556. _requiresUniqueEmail = Configuration.GetBooleanValue(config, "requiresUniqueEmail", true);
  557. _maxInvalidPasswordAttempts = Configuration.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
  558. _passwordAttemptWindow = Configuration.GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
  559. _minRequiredPasswordLength = Configuration.GetIntValue(config, "minRequiredPasswordLength", 7, false, MaxTablePasswordSize);
  560. _minRequiredNonalphanumericCharacters = Configuration.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, MaxTablePasswordSize);
  561. _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
  562. if (_passwordStrengthRegularExpression != null)
  563. {
  564. _passwordStrengthRegularExpression = _passwordStrengthRegularExpression.Trim();
  565. if (_passwordStrengthRegularExpression.Length != 0)
  566. {
  567. try
  568. {
  569. Regex testIfRegexIsValid = new Regex(_passwordStrengthRegularExpression);
  570. }
  571. catch (ArgumentException e)
  572. {
  573. throw new ProviderException(e.Message, e);
  574. }
  575. }
  576. }
  577. else
  578. {
  579. _passwordStrengthRegularExpression = string.Empty;
  580. }
  581. if (_minRequiredNonalphanumericCharacters > _minRequiredPasswordLength)
  582. {
  583. throw new HttpException("The minRequiredNonalphanumericCharacters can not be greater than minRequiredPasswordLength.");
  584. }
  585. string strTemp = config["passwordFormat"];
  586. if (strTemp == null)
  587. {
  588. strTemp = "Hashed";
  589. }
  590. switch (strTemp)
  591. {
  592. case "Clear":
  593. _passwordFormat = MembershipPasswordFormat.Clear;
  594. break;
  595. case "Encrypted":
  596. _passwordFormat = MembershipPasswordFormat.Encrypted;
  597. break;
  598. case "Hashed":
  599. _passwordFormat = MembershipPasswordFormat.Hashed;
  600. break;
  601. default:
  602. throw new ProviderException("Password format specified is invalid.");
  603. }
  604. if (PasswordFormat == MembershipPasswordFormat.Hashed && EnablePasswordRetrieval)
  605. throw new ProviderException("Configured settings are invalid: Hashed passwords cannot be retrieved. Either set the password format to different type, or set supportsPasswordRetrieval to false.");
  606. // Table storage-related properties
  607. _applicationName = Configuration.GetStringValueWithGlobalDefault(config, "applicationName",
  608. Configuration.DefaultProviderApplicationNameConfigurationString,
  609. Configuration.DefaultProviderApplicationName, false);
  610. _accountName = Configuration.GetStringValue(config, "accountName", null, true);
  611. _sharedKey = Configuration.GetStringValue(config, "sharedKey", null, true);
  612. _tableName = Configuration.GetStringValueWithGlobalDefault(config, "membershipTableName",
  613. Configuration.DefaultMembershipTableNameConfigurationString,
  614. Configuration.DefaultMembershipTableName, false);
  615. _tableServiceBaseUri = Configuration.GetStringValue(config, "tableServiceBaseUri", null, true);
  616. config.Remove("allowInsecureRemoteEndpoints");
  617. config.Remove("enablePasswordRetrieval");
  618. config.Remove("enablePasswordReset");
  619. config.Remove("requiresQuestionAndAnswer");
  620. config.Remove("requiresUniqueEmail");
  621. config.Remove("maxInvalidPasswordAttempts");
  622. config.Remove("passwordAttemptWindow");
  623. config.Remove("passwordFormat");
  624. config.Remove("minRequiredPasswordLength");
  625. config.Remove("minRequiredNonalphanumericCharacters");
  626. config.Remove("passwordStrengthRegularExpression");
  627. config.Remove("applicationName");
  628. config.Remove("accountName");
  629. config.Remove("sharedKey");
  630. config.Remove("membershipTableName");
  631. config.Remove("tableServiceBaseUri");
  632. // Throw an exception if unrecognized attributes remain
  633. if (config.Count > 0)
  634. {
  635. string attr = config.GetKey(0);
  636. if (!String.IsNullOrEmpty(attr))
  637. throw new ProviderException("Unrecognized attribute: " + attr);
  638. }
  639. StorageAccountInfo info = null;
  640. try
  641. {
  642. info = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration(true);
  643. if (_tableServiceBaseUri != null)
  644. {
  645. info.BaseUri = new Uri(_tableServiceBaseUri);
  646. }
  647. if (_accountName != null)
  648. {
  649. info.AccountName = _accountName;
  650. }
  651. if (_sharedKey != null)
  652. {
  653. info.Base64Key = _sharedKey;
  654. }
  655. info.CheckComplete();
  656. SecUtility.CheckAllowInsecureEndpoints(allowInsecureRemoteEndpoints, info);
  657. _tableStorage = TableStorage.Create(info);
  658. _tableStorage.RetryPolicy = _tableRetry;
  659. _tableStorage.TryCreateTable(_tableName);
  660. }
  661. catch (SecurityException)
  662. {
  663. throw;
  664. }
  665. catch (Exception e)
  666. {
  667. string exceptionDescription = Configuration.GetInitExceptionDescription(info, "table storage configuration");
  668. string tableName = (_tableName == null) ? "no membership table name specified" : _tableName;
  669. Log.Write(EventKind.Error, "Could not create or find membership table: " + tableName + "!" + Environment.NewLine +
  670. exceptionDescription + Environment.NewLine +
  671. e.Message + Environment.NewLine + e.StackTrace);
  672. throw new ProviderException("Could not create or find membership table. The most probable reason for this is that " +
  673. "the storage endpoints are not configured correctly. Please look at the configuration settings " +
  674. "in your .cscfg and Web.config files. More information about this error " +
  675. "can be found in the logs when running inside the hosting environment or in the output " +
  676. "window of Visual Studio.", e);
  677. }
  678. }
  679. /// <summary>
  680. /// Returns true if the username and password match an exsisting user.
  681. /// This implementation does not update a user's login time and does
  682. /// not raise corresponding Web events
  683. /// </summary>
  684. public override bool ValidateUser(string username, string password)
  685. {
  686. if (SecUtility.ValidateParameter(ref username, true, true, true, Constants.MaxTableUsernameLength) &&
  687. SecUtility.ValidateParameter(ref password, true, true, false, MaxTablePasswordSize) &&
  688. CheckPassword(username, password, true, true))
  689. {
  690. return true;
  691. }
  692. else
  693. {
  694. return false;
  695. }
  696. }
  697. /// <summary>
  698. /// Get a user based on the username parameter.
  699. /// If the userIsOnline parameter is set the lastActivity flag of the user
  700. /// is changed in the data store
  701. /// </summary>
  702. public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
  703. {
  704. if (providerUserKey == null)
  705. {
  706. throw new ArgumentNullException("providerUserKey");
  707. }
  708. if (providerUserKey.GetType() != typeof(Guid)) {
  709. throw new ArgumentException("Provided key is not a Guid!");
  710. }
  711. Guid key = (Guid)providerUserKey;
  712. try
  713. {
  714. TableStorageDataServiceContext svc = CreateDataServiceContext();
  715. DataServiceQuery<MembershipRow> queryObj = svc.CreateQuery<MembershipRow>(_tableName);
  716. // we need an IQueryable here because we do a Top(2) in the ProcessGetUserQuery()
  717. // and cast it to DataServiceQuery object in this function
  718. // this does not work when we use IEnumerable as a type here
  719. IQueryable<MembershipRow> query = from user in queryObj
  720. where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
  721. user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
  722. user.UserId == key &&
  723. user.ProfileIsCreatedByProfileProvider == false
  724. select user;
  725. return ProcessGetUserQuery(svc, query, userIsOnline);
  726. }
  727. catch (InvalidOperationException e)
  728. {
  729. if (TableStorageHelpers.IsTableStorageException(e))
  730. {
  731. throw new ProviderException("Error accessing the data source.", e);
  732. }
  733. else
  734. {
  735. throw;
  736. }
  737. }
  738. }
  739. /// <summary>
  740. /// Retrieves a user based on his/her username.
  741. /// The userIsOnline parameter determines whether to update the lastActivityDate of
  742. /// the user in the data store
  743. /// </summary>
  744. public override MembershipUser GetUser(string username, bool userIsOnline)
  745. {
  746. SecUtility.CheckParameter(
  747. ref username,
  748. true,
  749. false,
  750. true,
  751. Constants.MaxTableUsernameLength,
  752. "username");
  753. try
  754. {
  755. TableStorageDataServiceContext svc = CreateDataServiceContext();
  756. DataServiceQuery<MembershipRow> queryObj = svc.CreateQuery<MembershipRow>(_tableName);
  757. // we need an IQueryable here because we do a Top(2) in the ProcessGetUserQuery()
  758. // and cast it to DataServiceQuery object in this function
  759. // this does not work when we use IEnumerable as a type here
  760. IQueryable<MembershipRow> query = from user in queryObj
  761. where user.PartitionKey == SecUtility.CombineToKey(_applicationName, username) &&
  762. user.ProfileIsCreatedByProfileProvider == false
  763. select user;
  764. return ProcessGetUserQuery(svc, query, userIsOnline);
  765. }
  766. catch (InvalidOperationException e)
  767. {
  768. if (TableStorageHelpers.IsTableStorageException(e))
  769. {
  770. throw new ProviderException("Error accessing the data source.", e);
  771. }
  772. else
  773. {
  774. throw;
  775. }
  776. }
  777. }
  778. /// <summary>
  779. /// Retrieves a collection of all the users.
  780. /// </summary>
  781. public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
  782. {
  783. if ( pageIndex < 0 ) {
  784. throw new ArgumentException("The page index cannot be negative.");
  785. }
  786. if ( pageSize < 1 ) {
  787. throw new ArgumentException("The page size can only be a positive integer.");
  788. }
  789. long upperBound = (long)pageIndex * pageSize + pageSize - 1;
  790. if ( upperBound > Int32.MaxValue ) {
  791. throw new ArgumentException("pageIndex and pageSize are too big.");
  792. }
  793. totalRecords = 0;
  794. MembershipUserCollection users = new MembershipUserCollection();
  795. TableStorageDataServiceContext svc = CreateDataServiceContext();
  796. try {
  797. DataServiceQuery<MembershipRow> queryObj = svc.CreateQuery<MembershipRow>(_tableName);
  798. IEnumerable<MembershipRow> query = from user in queryObj
  799. where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 &&
  800. user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_applicationName))) < 0 &&
  801. user.ProfileIsCreatedByProfileProvider == false
  802. select user;
  803. TableStorageDataServiceQuery<MembershipRow> q = new TableStorageDataServiceQuery<MembershipRow>(query as DataServiceQuery<MembershipRow>, _tableRetry);
  804. IEnumerable<MembershipRow> allUsers = q.ExecuteAllWithRetries();
  805. List<MembershipRow> allUsersSorted = new List<MembershipRow>(allUsers);
  806. // the result should already be sorted because the user name is part of the primary key
  807. // we have to sort anyway because we have encoded the user names in the key
  808. // this is also why we cannot use the table stoage pagination mechanism here and need to retrieve all elements
  809. // for every page
  810. allUsersSorted.Sort();
  811. int startIndex = pageIndex * pageSize;
  812. int endIndex = startIndex + pageSize;
  813. MembershipRow row;
  814. for (int i = startIndex; i < endIndex && i < allUsersSorted.Count; i++) {
  815. row = allUsersSorted.ElementAt<MembershipRow>(i);
  816. users.Add(new MembershipUser(this.Name,
  817. row.UserName,
  818. row.UserId,
  819. row.Email,
  820. row.PasswordQuestion,
  821. row.Comment,
  822. row.IsApproved,
  823. row.IsLockedOut,
  824. row.CreateDateUtc.ToLocalTime(),
  825. row.LastLoginDateUtc.ToLocalTime(),
  826. row.LastActivityDateUtc.ToLocalTime(),
  827. row.LastPasswordChangedDateUtc.ToLocalTime(),
  828. row.LastLockoutDateUtc.ToLocalTime()));
  829. }
  830. } catch (InvalidOperationException e) {
  831. if (TableStorageHelpers.IsTableStorageException(e))
  832. {
  833. throw new ProviderException("Error accessing the data source.", e);
  834. }
  835. else
  836. {
  837. throw;
  838. }
  839. }
  840. totalRecords = users.Count;
  841. return users;
  842. }
  843. /// <summary>
  844. /// Changes a users password. We don't use retries in this highly security-related function.
  845. /// All errors are exposed to the user of this function.
  846. /// </summary>
  847. public override bool ChangePassword(string username, string oldPassword, string newPassword)
  848. {
  849. SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
  850. SecUtility.CheckParameter(ref oldPassword, true, true, false, MaxTablePasswordSize, "oldPassword");
  851. SecUtility.CheckParameter(ref newPassword, true, true, false, MaxTablePasswordSize, "newPassword");
  852. try
  853. {
  854. string salt = null;
  855. int passwordFormat;
  856. MembershipRow member;
  857. TableStorageDataServiceContext svc = CreateDataServiceContext();
  858. if (!CheckPassword(svc, username, oldPassword, false, false, out member))
  859. {
  860. return false;
  861. }
  862. salt = member.PasswordSalt;
  863. passwordFormat = member.PasswordFormat;
  864. if (newPassword.Length < MinRequiredPasswordLength)
  865. {
  866. throw new ArgumentException("The new password is to short.");
  867. }
  868. int count = 0;
  869. for (int i = 0; i < newPassword.Length; i++)
  870. {
  871. if (!char.IsLetterOrDigit(newPassword, i))
  872. {
  873. count++;
  874. }
  875. }
  876. if (count < MinRequiredNonAlphanumericCharacters)
  877. {
  878. throw new ArgumentException("The new password does not have enough non-alphanumeric characters!");
  879. }
  880. if (PasswordStrengthRegularExpression.Length > 0)
  881. {
  882. if (!Regex.IsMatch(newPassword, PasswordStrengthRegularExpression))
  883. {
  884. throw new ArgumentException("The new password does not match the specified password strength regular expression.");
  885. }
  886. }
  887. string pass = EncodePassword(newPassword, (int)passwordFormat, salt);
  888. if (pass.Length > MaxTablePasswordSize)
  889. {
  890. throw new ArgumentException("Password is too long!");
  891. }
  892. ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, newPassword, false);
  893. OnValidatingPassword(e);
  894. if (e.Cancel)
  895. {
  896. if (e.FailureInformation != null)
  897. {
  898. throw e.FailureInformation;
  899. }
  900. else
  901. {
  902. throw new ArgumentException("Password validation failure!");
  903. }
  904. }
  905. member.Password = pass;
  906. member.PasswordSalt = salt;
  907. member.PasswordFormat = passwordFormat;
  908. member.LastPasswordChangedDateUtc = DateTime.UtcNow;
  909. svc.UpdateObject(member);
  910. svc.SaveChanges();
  911. return true;
  912. }
  913. catch (InvalidOperationException e)
  914. {
  915. if (TableStorageHelpers.IsTableStorageException(e))
  916. {
  917. throw new ProviderException("Error accessing the data source.", e);
  918. }
  919. else
  920. {
  921. throw;
  922. }
  923. }
  924. }
  925. /// <summary>
  926. /// Creates a new user and stores it in the membership table. We do not use retry policies in this
  927. /// highly security-related function. All error conditions are directly exposed to the user.
  928. /// </summary>
  929. public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion,
  930. string passwordAnswer, bool isApproved, object providerUserKey,
  931. out MembershipCreateStatus status)
  932. {
  933. if (!SecUtility.ValidateParameter(ref password, true, true, false, MaxTablePasswordSize))
  934. {
  935. status = MembershipCreateStatus.InvalidPassword;
  936. return null;
  937. }
  938. string salt = GenerateSalt();
  939. string pass = EncodePassword(password, (int)_passwordFormat, salt);
  940. if (pass.Length > MaxTablePasswordSize)
  941. {
  942. status = MembershipCreateStatus.InvalidPassword;
  943. return null;
  944. }
  945. string encodedPasswordAnswer;
  946. if (passwordAnswer != null)
  947. {
  948. passwordAnswer = passwordAnswer.Trim();
  949. }
  950. if (!string.IsNullOrEmpty(passwordAnswer))
  951. {
  952. if (passwordAnswer.Length > MaxTablePasswordSize)
  953. {
  954. status = MembershipCreateStatus.InvalidAnswer;
  955. return null;
  956. }
  957. encodedPasswordAnswer = EncodePassword(passwordAnswer.ToLowerInvariant(), (int)_passwordFormat, salt);
  958. }
  959. else
  960. {
  961. encodedPasswordAnswer = passwordAnswer;
  962. }
  963. if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, true, false, MaxTablePasswordSize))
  964. {
  965. status = MembershipCreateStatus.InvalidAnswer;
  966. return null;
  967. }
  968. if (!SecUtility.ValidateParameter(ref username, true, true, true, Constants.MaxTableUsernameLength))
  969. {
  970. status = MembershipCreateStatus.InvalidUserName;
  971. return null;
  972. }
  973. if (!SecUtility.ValidateParameter(ref email,
  974. RequiresUniqueEmail,
  975. RequiresUniqueEmail,
  976. false,
  977. Constants.MaxTableUsernameLength))
  978. {
  979. status = MembershipCreateStatus.InvalidEmail;
  980. return null;
  981. }
  982. if (!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, Constants.MaxTableUsernameLength))
  983. {
  984. status = MembershipCreateStatus.InvalidQuestion;
  985. return null;
  986. }
  987. if (providerUserKey != null)
  988. {
  989. if (!(providerUserKey is Guid))
  990. {
  991. status = MembershipCreateStatus.InvalidProviderUserKey;
  992. return null;
  993. }
  994. }
  995. if (!EvaluatePasswordRequirements(password))
  996. {
  997. status = MembershipCreateStatus.InvalidPassword;
  998. return null;
  999. }
  1000. ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true);
  1001. OnValidatingPassword(e);
  1002. if (e.Cancel)
  1003. {
  1004. status = MembershipCreateStatus.InvalidPassword;
  1005. return null;
  1006. }
  1007. // Check whether a user with the same email address already exists.
  1008. // The danger here is (as we don't have transaction support here) that
  1009. // there are overlapping requests for creating two users with the same email
  1010. // address at the same point in time.
  1011. // A solution for this would be to have a separate table for email addresses.
  1012. // At this point here in the code we would try to insert this user's email address into the
  1013. // table and thus check whether the email is unique (the email would be the primary key of the
  1014. // separate table). There are quite some problems
  1015. // associated with that. For example, what happens if the user creation fails etc., stale data in the
  1016. // email table etc.
  1017. // Another solution is to already insert the user at this point and then check at the end of this
  1018. // funcation whether the email is unique.
  1019. if (RequiresUniqueEmail && !IsUniqueEmail(email))
  1020. {
  1021. status = MembershipCreateStatus.DuplicateEmail;
  1022. return null;
  1023. }
  1024. try
  1025. {
  1026. TableStorageDataServiceContext svc = CreateDataServiceContext();
  1027. MembershipRow newUser = new MembershipRow(_applicationName, username);
  1028. if (providerUserKey == null)
  1029. {
  1030. providerUserKey = Guid.NewGuid();
  1031. }
  1032. newUser.UserId = (Guid)providerUserKey;
  1033. newUser.Password = pass;
  1034. newUser.PasswordSalt = salt;
  1035. newUser.Email = (email == null) ? string.Empty : email; ;
  1036. newUser.PasswordQuestion = (passwordQuestion == null) ? string.Empty : passwordQuestion;
  1037. newUser.PasswordAnswer = (encodedPasswordAnswer == null) ? string.Empty : encodedPasswordAnswer;
  1038. newUser.IsApproved = isApproved;
  1039. newUser.PasswordFormat = (int)_passwordFormat;
  1040. DateTime now = DateTime.UtcNow;
  1041. newUser.CreateDateUtc = now;
  1042. newUser.LastActivityDateUtc = now;
  1043. newUser.LastPasswordChangedDateUtc = now;
  1044. newUser.LastLoginDateUtc = now;
  1045. newUser.IsLockedOut = false;
  1046. svc.AddObject(_tableName, newUser);
  1047. svc.SaveChanges();
  1048. status = MembershipCreateStatus.Success;
  1049. return new MembershipUser(this.Name,
  1050. username,
  1051. providerUserKey,
  1052. email,
  1053. passwordQuestion,
  1054. null,
  1055. isApproved,
  1056. false,
  1057. now.ToLocalTime(),
  1058. now.ToLocalTime(),
  1059. now.ToLocalTime(),
  1060. now.ToLocalTime(),
  1061. TableStorageConstants.MinSupportedDateTime);
  1062. }
  1063. catch (InvalidOperationException ex)
  1064. {
  1065. HttpStatusCode httpStatus;
  1066. if (TableStorageHelpers.EvaluateException(ex, out httpStatus) && httpStatus == HttpStatusCode.Conflict)
  1067. {
  1068. // in this case, some membership providers update the last activity time of the user
  1069. // we don't do this in this implementation because it would add another roundtrip
  1070. status = MembershipCreateStatus.DuplicateUserName;
  1071. return null;
  1072. }
  1073. else if (TableStorageHelpers.IsTableStorageException(ex))
  1074. {
  1075. throw new ProviderException("Cannot add user to membership data store because of problems when accessing the data store.", ex);
  1076. }
  1077. else
  1078. {
  1079. throw;
  1080. }
  1081. }
  1082. }
  1083. /// <summary>
  1084. /// Deletes the user from the membership table.
  1085. /// This implementation ignores the deleteAllRelatedData argument
  1086. /// </summary>
  1087. public override bool DeleteUser(string username, bool deleteAllRelatedData)
  1088. {
  1089. SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username");
  1090. try
  1091. {
  1092. TableStorageDataServiceContext svc = CreateDataServiceContext();
  1093. MembershipRow user = new MembershipRow(_applicationName, username);
  1094. svc.AttachTo(_tableName, user, "*");
  1095. svc.DeleteObject(user);
  1096. svc.SaveChangesWithRetries();
  1097. return true;
  1098. }
  1099. catch (InvalidOperationException e)
  1100. {
  1101. HttpStatusCode status;
  1102. if (TableStorageHelpers.EvaluateException(e, out status)) {
  1103. if (status == HttpStatusCode.NotFound)
  1104. {
  1105. return false;
  1106. }
  1107. else
  1108. {
  1109. throw new ProviderException("Error accessing the data source.", e);
  1110. }
  1111. }
  1112. else
  1113. {
  1114. throw;
  1115. }
  1116. }
  1117. }
  1118. /// <summary>
  1119. /// Retrieves a username based on a matching email.
  1120. /// </summary>
  1121. public override string GetUserNameByEmail(string email)
  1122. {
  1123. SecUtility.CheckParameter(ref email, false, false, false, MaxTableEmailLength, "email");
  1124. string nonNullEmail = (email == null) ? string.Empty : email;
  1125. try
  1126. {
  1127. DataServiceContext svc = CreateDataServiceContext();
  1128. DataSe

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