SqlMembershipProvider.cs 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  1. //
  2. // System.Web.Security.SqlMembershipProvider
  3. //
  4. // Authors:
  5. // Ben Maurer ([email protected])
  6. // Lluis Sanchez Gual ([email protected])
  7. // Chris Toshok ([email protected])
  8. //
  9. // (C) 2003 Ben Maurer
  10. // Copyright (c) 2005,2006 Novell, Inc (http://www.novell.com)
  11. //
  12. // Permission is hereby granted, free of charge, to any person obtaining
  13. // a copy of this software and associated documentation files (the
  14. // "Software"), to deal in the Software without restriction, including
  15. // without limitation the rights to use, copy, modify, merge, publish,
  16. // distribute, sublicense, and/or sell copies of the Software, and to
  17. // permit persons to whom the Software is furnished to do so, subject to
  18. // the following conditions:
  19. //
  20. // The above copyright notice and this permission notice shall be
  21. // included in all copies or substantial portions of the Software.
  22. //
  23. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  27. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  28. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  29. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  30. //
  31. #if NET_2_0
  32. using System.Collections;
  33. using System.Collections.Specialized;
  34. using System.Configuration;
  35. using System.Configuration.Provider;
  36. using System.Data;
  37. using System.Data.Common;
  38. using System.Text;
  39. using System.Web.Configuration;
  40. using System.Security.Cryptography;
  41. namespace System.Web.Security {
  42. public class SqlMembershipProvider : MembershipProvider
  43. {
  44. /* can this be done just by setting the datetime fields to 0? */
  45. DateTime DefaultDateTime = new DateTime (1754, 1, 1).ToUniversalTime ();
  46. bool enablePasswordReset;
  47. bool enablePasswordRetrieval;
  48. int maxInvalidPasswordAttempts;
  49. MembershipPasswordFormat passwordFormat;
  50. bool requiresQuestionAndAnswer;
  51. bool requiresUniqueEmail;
  52. int minRequiredNonAlphanumericCharacters;
  53. int minRequiredPasswordLength;
  54. int passwordAttemptWindow;
  55. string passwordStrengthRegularExpression;
  56. TimeSpan userIsOnlineTimeWindow;
  57. ConnectionStringSettings connectionString;
  58. DbProviderFactory factory;
  59. string applicationName;
  60. DbConnection CreateConnection ()
  61. {
  62. DbConnection connection = factory.CreateConnection ();
  63. connection.ConnectionString = connectionString.ConnectionString;
  64. connection.Open ();
  65. return connection;
  66. }
  67. DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
  68. {
  69. return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
  70. }
  71. DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
  72. {
  73. DbParameter dbp = command.CreateParameter ();
  74. dbp.ParameterName = parameterName;
  75. dbp.Value = parameterValue;
  76. dbp.Direction = direction;
  77. command.Parameters.Add (dbp);
  78. return dbp;
  79. }
  80. static int GetReturnValue (DbParameter returnValue)
  81. {
  82. object value = returnValue.Value;
  83. return value is int ? (int) value : -1;
  84. }
  85. void CheckParam (string pName, string p, int length)
  86. {
  87. if (p == null)
  88. throw new ArgumentNullException (pName);
  89. if (p.Length == 0 || p.Length > length || p.IndexOf (",") != -1)
  90. throw new ArgumentException (String.Format ("invalid format for {0}", pName));
  91. }
  92. public override bool ChangePassword (string username, string oldPwd, string newPwd)
  93. {
  94. if (username != null) username = username.Trim ();
  95. if (oldPwd != null) oldPwd = oldPwd.Trim ();
  96. if (newPwd != null) newPwd = newPwd.Trim ();
  97. CheckParam ("username", username, 256);
  98. CheckParam ("oldPwd", oldPwd, 128);
  99. CheckParam ("newPwd", newPwd, 128);
  100. using (DbConnection connection = CreateConnection ()) {
  101. PasswordInfo pi = ValidateUsingPassword (username, oldPwd);
  102. if (pi != null) {
  103. EmitValidatingPassword (username, newPwd, false);
  104. string db_password = EncodePassword (newPwd, pi.PasswordFormat, pi.PasswordSalt);
  105. DbCommand command = factory.CreateCommand ();
  106. command.Connection = connection;
  107. command.CommandText = @"aspnet_Membership_SetPassword";
  108. command.CommandType = CommandType.StoredProcedure;
  109. AddParameter (command, "ApplicationName", ApplicationName);
  110. AddParameter (command, "UserName", username);
  111. AddParameter (command, "NewPassword", db_password);
  112. AddParameter (command, "PasswordFormat", pi.PasswordFormat);
  113. AddParameter (command, "PasswordSalt", pi.PasswordSalt);
  114. AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
  115. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  116. command.ExecuteNonQuery ();
  117. if (GetReturnValue (returnValue) != 0)
  118. return false;
  119. return true;
  120. }
  121. return false;
  122. }
  123. }
  124. public override bool ChangePasswordQuestionAndAnswer (string username, string password, string newPwdQuestion, string newPwdAnswer)
  125. {
  126. if (username != null) username = username.Trim ();
  127. if (newPwdQuestion != null) newPwdQuestion = newPwdQuestion.Trim ();
  128. if (newPwdAnswer != null) newPwdAnswer = newPwdAnswer.Trim ();
  129. CheckParam ("username", username, 256);
  130. if (RequiresQuestionAndAnswer)
  131. CheckParam ("newPwdQuestion", newPwdQuestion, 128);
  132. if (RequiresQuestionAndAnswer)
  133. CheckParam ("newPwdAnswer", newPwdAnswer, 128);
  134. using (DbConnection connection = CreateConnection ()) {
  135. PasswordInfo pi = ValidateUsingPassword (username, password);
  136. if (pi != null) {
  137. string db_passwordAnswer = EncodePassword (newPwdAnswer, pi.PasswordFormat, pi.PasswordSalt);
  138. DbCommand command = factory.CreateCommand ();
  139. command.Connection = connection;
  140. command.CommandType = CommandType.StoredProcedure;
  141. command.CommandText = @"aspnet_Membership_ChangePasswordQuestionAndAnswer";
  142. AddParameter (command, "ApplicationName", ApplicationName);
  143. AddParameter (command, "UserName", username);
  144. AddParameter (command, "NewPasswordQuestion", newPwdQuestion);
  145. AddParameter (command, "NewPasswordAnswer", db_passwordAnswer);
  146. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  147. command.ExecuteNonQuery ();
  148. if (GetReturnValue (returnValue) != 0)
  149. return false;
  150. return true;
  151. }
  152. return false;
  153. }
  154. }
  155. public override MembershipUser CreateUser (string username,
  156. string password,
  157. string email,
  158. string pwdQuestion,
  159. string pwdAnswer,
  160. bool isApproved,
  161. object providerUserKey,
  162. out MembershipCreateStatus status)
  163. {
  164. if (username != null) username = username.Trim ();
  165. if (password != null) password = password.Trim ();
  166. if (email != null) email = email.Trim ();
  167. if (pwdQuestion != null) pwdQuestion = pwdQuestion.Trim ();
  168. if (pwdAnswer != null) pwdAnswer = pwdAnswer.Trim ();
  169. /* some initial validation */
  170. if (username == null || username.Length == 0 || username.Length > 256 || username.IndexOf (",") != -1) {
  171. status = MembershipCreateStatus.InvalidUserName;
  172. return null;
  173. }
  174. if (password == null || password.Length == 0 || password.Length > 128) {
  175. status = MembershipCreateStatus.InvalidPassword;
  176. return null;
  177. }
  178. if (!CheckPassword (password)) {
  179. status = MembershipCreateStatus.InvalidPassword;
  180. return null;
  181. }
  182. EmitValidatingPassword (username, password, true);
  183. if (RequiresUniqueEmail && (email == null || email.Length == 0)) {
  184. status = MembershipCreateStatus.InvalidEmail;
  185. return null;
  186. }
  187. if (RequiresQuestionAndAnswer &&
  188. (pwdQuestion == null ||
  189. pwdQuestion.Length == 0 || pwdQuestion.Length > 256)) {
  190. status = MembershipCreateStatus.InvalidQuestion;
  191. return null;
  192. }
  193. if (RequiresQuestionAndAnswer &&
  194. (pwdAnswer == null ||
  195. pwdAnswer.Length == 0 || pwdAnswer.Length > 128)) {
  196. status = MembershipCreateStatus.InvalidAnswer;
  197. return null;
  198. }
  199. if (providerUserKey != null && !(providerUserKey is Guid)) {
  200. status = MembershipCreateStatus.InvalidProviderUserKey;
  201. return null;
  202. }
  203. if (providerUserKey == null)
  204. providerUserKey = Guid.NewGuid();
  205. /* encode our password/answer using the
  206. * "passwordFormat" configuration option */
  207. string passwordSalt = "";
  208. RandomNumberGenerator rng = RandomNumberGenerator.Create ();
  209. byte [] salt = new byte [SALT_BYTES];
  210. rng.GetBytes (salt);
  211. passwordSalt = Convert.ToBase64String (salt);
  212. password = EncodePassword (password, PasswordFormat, passwordSalt);
  213. if (RequiresQuestionAndAnswer)
  214. pwdAnswer = EncodePassword (pwdAnswer, PasswordFormat, passwordSalt);
  215. /* make sure the hashed/encrypted password and
  216. * answer are still under 128 characters. */
  217. if (password.Length > 128) {
  218. status = MembershipCreateStatus.InvalidPassword;
  219. return null;
  220. }
  221. if (RequiresQuestionAndAnswer) {
  222. if (pwdAnswer.Length > 128) {
  223. status = MembershipCreateStatus.InvalidAnswer;
  224. return null;
  225. }
  226. }
  227. status = MembershipCreateStatus.Success;
  228. using (DbConnection connection = CreateConnection ()) {
  229. DbTransaction trans = connection.BeginTransaction ();
  230. try {
  231. DbCommand command = factory.CreateCommand ();
  232. command.Transaction = trans;
  233. command.Connection = connection;
  234. command.CommandText = @"aspnet_Membership_CreateUser";
  235. command.CommandType = CommandType.StoredProcedure;
  236. DateTime Now = DateTime.UtcNow;
  237. AddParameter (command, "ApplicationName", ApplicationName);
  238. AddParameter (command, "UserName", username);
  239. AddParameter (command, "Password", password);
  240. AddParameter (command, "PasswordSalt", passwordSalt);
  241. AddParameter (command, "Email", email);
  242. AddParameter (command, "PasswordQuestion", pwdQuestion);
  243. AddParameter (command, "PasswordAnswer", pwdAnswer);
  244. AddParameter (command, "IsApproved", isApproved);
  245. AddParameter (command, "CurrentTimeUtc", Now);
  246. AddParameter (command, "CreateDate", Now);
  247. AddParameter (command, "UniqueEmail", RequiresUniqueEmail);
  248. AddParameter (command, "PasswordFormat", PasswordFormat);
  249. AddParameter (command, "UserId", ParameterDirection.InputOutput, providerUserKey);
  250. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  251. command.ExecuteNonQuery ();
  252. trans.Commit ();
  253. int st = GetReturnValue (returnValue);
  254. if (st == 0)
  255. return GetUser (username, false);
  256. else if (st == 6)
  257. status = MembershipCreateStatus.DuplicateUserName;
  258. else if (st == 7)
  259. status = MembershipCreateStatus.DuplicateEmail;
  260. else if (st == 10)
  261. status = MembershipCreateStatus.DuplicateProviderUserKey;
  262. else
  263. status = MembershipCreateStatus.ProviderError;
  264. return null;
  265. }
  266. catch (Exception) {
  267. status = MembershipCreateStatus.ProviderError;
  268. trans.Rollback ();
  269. return null;
  270. }
  271. }
  272. }
  273. private bool CheckPassword (string password)
  274. {
  275. if (password.Length < MinRequiredPasswordLength)
  276. return false;
  277. if (MinRequiredNonAlphanumericCharacters > 0) {
  278. int nonAlphanumeric = 0;
  279. for (int i = 0; i < password.Length; i++) {
  280. if (!Char.IsLetterOrDigit (password [i]))
  281. nonAlphanumeric++;
  282. }
  283. return nonAlphanumeric >= MinRequiredNonAlphanumericCharacters;
  284. }
  285. return true;
  286. }
  287. public override bool DeleteUser (string username, bool deleteAllRelatedData)
  288. {
  289. CheckParam ("username", username, 256);
  290. DeleteUserTableMask deleteBitmask = DeleteUserTableMask.MembershipUsers;
  291. if (deleteAllRelatedData)
  292. deleteBitmask |=
  293. DeleteUserTableMask.Profiles |
  294. DeleteUserTableMask.UsersInRoles |
  295. DeleteUserTableMask.WebPartStateUser;
  296. using (DbConnection connection = CreateConnection ()) {
  297. DbCommand command = factory.CreateCommand ();
  298. command.Connection = connection;
  299. command.CommandText = @"aspnet_Users_DeleteUser";
  300. command.CommandType = CommandType.StoredProcedure;
  301. AddParameter (command, "ApplicationName", ApplicationName);
  302. AddParameter (command, "UserName", username);
  303. AddParameter (command, "TablesToDeleteFrom", (int) deleteBitmask);
  304. AddParameter (command, "NumTablesDeletedFrom", ParameterDirection.Output, 0);
  305. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  306. command.ExecuteNonQuery ();
  307. if (((int) command.Parameters ["NumTablesDeletedFrom"].Value) == 0)
  308. return false;
  309. if (GetReturnValue (returnValue) == 0)
  310. return true;
  311. return false;
  312. }
  313. }
  314. public virtual string GeneratePassword ()
  315. {
  316. return Membership.GeneratePassword (MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters);
  317. }
  318. public override MembershipUserCollection FindUsersByEmail (string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
  319. {
  320. CheckParam ("emailToMatch", emailToMatch, 256);
  321. if (pageIndex < 0)
  322. throw new ArgumentException ("pageIndex must be >= 0");
  323. if (pageSize < 0)
  324. throw new ArgumentException ("pageSize must be >= 0");
  325. if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
  326. throw new ArgumentException ("pageIndex and pageSize are too large");
  327. using (DbConnection connection = CreateConnection ()) {
  328. DbCommand command = factory.CreateCommand ();
  329. command.Connection = connection;
  330. command.CommandText = @"aspnet_Membership_FindUsersByEmail";
  331. command.CommandType = CommandType.StoredProcedure;
  332. AddParameter (command, "PageIndex", pageIndex);
  333. AddParameter (command, "PageSize", pageSize);
  334. AddParameter (command, "EmailToMatch", emailToMatch);
  335. AddParameter (command, "ApplicationName", ApplicationName);
  336. DbParameter returnValue = AddParameter (command, "ReturnValue", ParameterDirection.ReturnValue, null);
  337. MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
  338. return c;
  339. }
  340. }
  341. public override MembershipUserCollection FindUsersByName (string nameToMatch, int pageIndex, int pageSize, out int totalRecords)
  342. {
  343. CheckParam ("nameToMatch", nameToMatch, 256);
  344. if (pageIndex < 0)
  345. throw new ArgumentException ("pageIndex must be >= 0");
  346. if (pageSize < 0)
  347. throw new ArgumentException ("pageSize must be >= 0");
  348. if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
  349. throw new ArgumentException ("pageIndex and pageSize are too large");
  350. string commandText;
  351. using (DbConnection connection = CreateConnection ()) {
  352. DbCommand command = factory.CreateCommand ();
  353. command.Connection = connection;
  354. command.CommandText = @"aspnet_Membership_FindUsersByName";
  355. command.CommandType = CommandType.StoredProcedure;
  356. AddParameter (command, "PageIndex", pageIndex);
  357. AddParameter (command, "PageSize", pageSize);
  358. AddParameter (command, "UserNameToMatch", nameToMatch);
  359. AddParameter (command, "ApplicationName", ApplicationName);
  360. DbParameter returnValue = AddParameter (command, "ReturnValue", ParameterDirection.ReturnValue, null);
  361. MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
  362. return c;
  363. }
  364. }
  365. public override MembershipUserCollection GetAllUsers (int pageIndex, int pageSize, out int totalRecords)
  366. {
  367. if (pageIndex < 0)
  368. throw new ArgumentException ("pageIndex must be >= 0");
  369. if (pageSize < 0)
  370. throw new ArgumentException ("pageSize must be >= 0");
  371. if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
  372. throw new ArgumentException ("pageIndex and pageSize are too large");
  373. string commandText;
  374. using (DbConnection connection = CreateConnection ()) {
  375. DbCommand command = factory.CreateCommand ();
  376. command.Connection = connection;
  377. command.CommandText = @"aspnet_Membership_GetAllUsers";
  378. command.CommandType = CommandType.StoredProcedure;
  379. AddParameter (command, "ApplicationName", ApplicationName);
  380. AddParameter (command, "PageIndex", pageIndex);
  381. AddParameter (command, "PageSize", pageSize);
  382. DbParameter returnValue = AddParameter (command, "ReturnValue", ParameterDirection.ReturnValue, null);
  383. MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
  384. return c;
  385. }
  386. }
  387. MembershipUserCollection BuildMembershipUserCollection (DbCommand command, int pageIndex, int pageSize, out int totalRecords)
  388. {
  389. DbDataReader reader = null;
  390. try {
  391. MembershipUserCollection users = new MembershipUserCollection ();
  392. reader = command.ExecuteReader ();
  393. while (reader.Read ())
  394. users.Add (GetUserFromReader (reader, null, null));
  395. totalRecords = Convert.ToInt32 (command.Parameters ["ReturnValue"].Value);
  396. return users;
  397. }
  398. catch (Exception e) {
  399. totalRecords = 0;
  400. return null; /* should we let the exception through? */
  401. }
  402. finally {
  403. if (reader != null)
  404. reader.Close ();
  405. }
  406. }
  407. public override int GetNumberOfUsersOnline ()
  408. {
  409. using (DbConnection connection = CreateConnection ()) {
  410. DateTime now = DateTime.UtcNow;
  411. DbCommand command = factory.CreateCommand ();
  412. command.Connection = connection;
  413. command.CommandText = @"aspnet_Membership_GetNumberOfUsersOnline";
  414. command.CommandType = CommandType.StoredProcedure;
  415. AddParameter (command, "CurrentTimeUtc", now.ToString ());
  416. AddParameter (command, "ApplicationName", ApplicationName);
  417. AddParameter (command, "MinutesSinceLastInActive", userIsOnlineTimeWindow.Minutes);
  418. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  419. command.ExecuteScalar ();
  420. return GetReturnValue (returnValue);
  421. }
  422. }
  423. public override string GetPassword (string username, string answer)
  424. {
  425. if (!EnablePasswordRetrieval)
  426. throw new NotSupportedException ("this provider has not been configured to allow the retrieval of passwords");
  427. CheckParam ("username", username, 256);
  428. if (RequiresQuestionAndAnswer)
  429. CheckParam ("answer", answer, 128);
  430. PasswordInfo pi = GetPasswordInfo (username);
  431. if (pi == null)
  432. throw new ProviderException ("An error occurred while retrieving the password from the database");
  433. string user_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
  434. string password = null;
  435. using (DbConnection connection = CreateConnection ()) {
  436. DbCommand command = factory.CreateCommand ();
  437. command.Connection = connection;
  438. command.CommandText = @"aspnet_Membership_GetPassword";
  439. command.CommandType = CommandType.StoredProcedure;
  440. AddParameter (command, "ApplicationName", ApplicationName);
  441. AddParameter (command, "UserName", username);
  442. AddParameter (command, "MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
  443. AddParameter (command, "PasswordAttemptWindow", PasswordAttemptWindow);
  444. AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
  445. AddParameter (command, "PasswordAnswer", user_answer);
  446. DbParameter retValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  447. DbDataReader reader = command.ExecuteReader ();
  448. int returnValue = GetReturnValue (retValue);
  449. if (returnValue == 3)
  450. throw new MembershipPasswordException ("Password Answer is invalid");
  451. if (returnValue == 99)
  452. throw new MembershipPasswordException ("The user account is currently locked out");
  453. if (reader.Read ()) {
  454. password = reader.GetString (0);
  455. reader.Close ();
  456. }
  457. if (pi.PasswordFormat == MembershipPasswordFormat.Clear)
  458. return password;
  459. else if (pi.PasswordFormat == MembershipPasswordFormat.Encrypted)
  460. return DecodePassword (password, pi.PasswordFormat);
  461. return password;
  462. }
  463. }
  464. MembershipUser GetUserFromReader (DbDataReader reader, string username, object userId)
  465. {
  466. int i = 0;
  467. if (username == null)
  468. i = 1;
  469. if (userId != null)
  470. username = reader.GetString (8);
  471. return new MembershipUser (this.Name, /* XXX is this right? */
  472. (username == null ? reader.GetString (0) : username), /* name */
  473. (userId == null ? reader.GetGuid (8 + i) : userId), /* providerUserKey */
  474. reader.IsDBNull (0 + i) ? null : reader.GetString (0 + i), /* email */
  475. reader.IsDBNull (1 + i) ? null : reader.GetString (1 + i), /* passwordQuestion */
  476. reader.IsDBNull (2 + i) ? null : reader.GetString (2 + i), /* comment */
  477. reader.GetBoolean (3 + i), /* isApproved */
  478. reader.GetBoolean (9 + i), /* isLockedOut */
  479. reader.GetDateTime (4 + i).ToLocalTime (), /* creationDate */
  480. reader.GetDateTime (5 + i).ToLocalTime (), /* lastLoginDate */
  481. reader.GetDateTime (6 + i).ToLocalTime (), /* lastActivityDate */
  482. reader.GetDateTime (7 + i).ToLocalTime (), /* lastPasswordChangedDate */
  483. reader.GetDateTime (10 + i).ToLocalTime () /* lastLockoutDate */);
  484. }
  485. MembershipUser BuildMembershipUser (DbCommand query, string username, object userId)
  486. {
  487. try {
  488. using (DbConnection connection = CreateConnection ()) {
  489. query.Connection = connection;
  490. using (DbDataReader reader = query.ExecuteReader ()) {
  491. if (!reader.Read ())
  492. return null;
  493. return GetUserFromReader (reader, username, userId);
  494. }
  495. }
  496. }
  497. catch (Exception e) {
  498. return null; /* should we let the exception through? */
  499. }
  500. finally {
  501. query.Connection = null;
  502. }
  503. }
  504. public override MembershipUser GetUser (string username, bool userIsOnline)
  505. {
  506. if (username == null)
  507. throw new ArgumentNullException ("username");
  508. if (username.Length == 0)
  509. return null;
  510. CheckParam ("username", username, 256);
  511. DbCommand command = factory.CreateCommand ();
  512. command.CommandText = @"aspnet_Membership_GetUserByName";
  513. command.CommandType = CommandType.StoredProcedure;
  514. AddParameter (command, "UserName", username);
  515. AddParameter (command, "ApplicationName", ApplicationName);
  516. AddParameter (command, "CurrentTimeUtc", DateTime.Now);
  517. AddParameter (command, "UpdateLastActivity", userIsOnline);
  518. MembershipUser u = BuildMembershipUser (command, username, null);
  519. return u;
  520. }
  521. public override MembershipUser GetUser (object providerUserKey, bool userIsOnline)
  522. {
  523. DbCommand command = factory.CreateCommand ();
  524. command.CommandText = @"aspnet_Membership_GetUserByUserId";
  525. command.CommandType = CommandType.StoredProcedure;
  526. AddParameter (command, "UserId", providerUserKey);
  527. AddParameter (command, "CurrentTimeUtc", DateTime.Now);
  528. AddParameter (command, "UpdateLastActivity", userIsOnline);
  529. MembershipUser u = BuildMembershipUser (command, string.Empty, providerUserKey);
  530. return u;
  531. }
  532. public override string GetUserNameByEmail (string email)
  533. {
  534. CheckParam ("email", email, 256);
  535. using (DbConnection connection = CreateConnection ()) {
  536. DbCommand command = factory.CreateCommand ();
  537. command.Connection = connection;
  538. command.CommandText = @"aspnet_Membership_GetUserByEmail";
  539. command.CommandType = CommandType.StoredProcedure;
  540. AddParameter (command, "ApplicationName", ApplicationName);
  541. AddParameter (command, "Email", email);
  542. DbDataReader reader = command.ExecuteReader ();
  543. string rv = null;
  544. if (reader.Read ())
  545. rv = reader.GetString (0);
  546. reader.Close ();
  547. return rv;
  548. }
  549. }
  550. bool GetBoolConfigValue (NameValueCollection config, string name, bool def)
  551. {
  552. bool rv = def;
  553. string val = config [name];
  554. if (val != null) {
  555. try { rv = Boolean.Parse (val); }
  556. catch (Exception e) {
  557. throw new ProviderException (String.Format ("{0} must be true or false", name), e);
  558. }
  559. }
  560. return rv;
  561. }
  562. int GetIntConfigValue (NameValueCollection config, string name, int def)
  563. {
  564. int rv = def;
  565. string val = config [name];
  566. if (val != null) {
  567. try { rv = Int32.Parse (val); }
  568. catch (Exception e) {
  569. throw new ProviderException (String.Format ("{0} must be an integer", name), e);
  570. }
  571. }
  572. return rv;
  573. }
  574. int GetEnumConfigValue (NameValueCollection config, string name, Type enumType, int def)
  575. {
  576. int rv = def;
  577. string val = config [name];
  578. if (val != null) {
  579. try { rv = (int) Enum.Parse (enumType, val); }
  580. catch (Exception e) {
  581. throw new ProviderException (String.Format ("{0} must be one of the following values: {1}", name, String.Join (",", Enum.GetNames (enumType))), e);
  582. }
  583. }
  584. return rv;
  585. }
  586. string GetStringConfigValue (NameValueCollection config, string name, string def)
  587. {
  588. string rv = def;
  589. string val = config [name];
  590. if (val != null)
  591. rv = val;
  592. return rv;
  593. }
  594. void EmitValidatingPassword (string username, string password, bool isNewUser)
  595. {
  596. ValidatePasswordEventArgs args = new ValidatePasswordEventArgs (username, password, isNewUser);
  597. OnValidatingPassword (args);
  598. /* if we're canceled.. */
  599. if (args.Cancel) {
  600. if (args.FailureInformation == null)
  601. throw new ProviderException ("Password validation canceled");
  602. else
  603. throw args.FailureInformation;
  604. }
  605. }
  606. public override void Initialize (string name, NameValueCollection config)
  607. {
  608. if (config == null)
  609. throw new ArgumentNullException ("config");
  610. base.Initialize (name, config);
  611. applicationName = GetStringConfigValue (config, "applicationName", "/");
  612. enablePasswordReset = GetBoolConfigValue (config, "enablePasswordReset", true);
  613. enablePasswordRetrieval = GetBoolConfigValue (config, "enablePasswordRetrieval", false);
  614. requiresQuestionAndAnswer = GetBoolConfigValue (config, "requiresQuestionAndAnswer", true);
  615. requiresUniqueEmail = GetBoolConfigValue (config, "requiresUniqueEmail", false);
  616. passwordFormat = (MembershipPasswordFormat) GetEnumConfigValue (config, "passwordFormat", typeof (MembershipPasswordFormat),
  617. (int) MembershipPasswordFormat.Hashed);
  618. maxInvalidPasswordAttempts = GetIntConfigValue (config, "maxInvalidPasswordAttempts", 5);
  619. minRequiredPasswordLength = GetIntConfigValue (config, "minRequiredPasswordLength", 7);
  620. minRequiredNonAlphanumericCharacters = GetIntConfigValue (config, "minRequiredNonalphanumericCharacters", 1);
  621. passwordAttemptWindow = GetIntConfigValue (config, "passwordAttemptWindow", 10);
  622. passwordStrengthRegularExpression = GetStringConfigValue (config, "passwordStrengthRegularExpression", "");
  623. MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
  624. userIsOnlineTimeWindow = section.UserIsOnlineTimeWindow;
  625. /* we can't support password retrieval with hashed passwords */
  626. if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval)
  627. throw new ProviderException ("password retrieval cannot be used with hashed passwords");
  628. string connectionStringName = config ["connectionStringName"];
  629. if (applicationName.Length > 256)
  630. throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
  631. if (connectionStringName == null || connectionStringName.Length == 0)
  632. throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
  633. connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];
  634. factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ?
  635. System.Data.SqlClient.SqlClientFactory.Instance :
  636. ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName);
  637. }
  638. public override string ResetPassword (string username, string answer)
  639. {
  640. if (!EnablePasswordReset)
  641. throw new NotSupportedException ("this provider has not been configured to allow the resetting of passwords");
  642. CheckParam ("username", username, 256);
  643. if (RequiresQuestionAndAnswer)
  644. CheckParam ("answer", answer, 128);
  645. using (DbConnection connection = CreateConnection ()) {
  646. PasswordInfo pi = GetPasswordInfo (username);
  647. if (pi == null)
  648. throw new ProviderException (username + "is not found in the membership database");
  649. string newPassword = GeneratePassword ();
  650. EmitValidatingPassword (username, newPassword, false);
  651. string db_password = EncodePassword (newPassword, pi.PasswordFormat, pi.PasswordSalt);
  652. string db_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
  653. DbCommand command = factory.CreateCommand ();
  654. command.Connection = connection;
  655. command.CommandText = @"aspnet_Membership_ResetPassword";
  656. command.CommandType = CommandType.StoredProcedure;
  657. AddParameter (command, "ApplicationName", ApplicationName);
  658. AddParameter (command, "UserName", username);
  659. AddParameter (command, "NewPassword", db_password);
  660. AddParameter (command, "MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
  661. AddParameter (command, "PasswordAttemptWindow", PasswordAttemptWindow);
  662. AddParameter (command, "PasswordSalt", pi.PasswordSalt);
  663. AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
  664. AddParameter (command, "PasswordFormat", pi.PasswordFormat);
  665. AddParameter (command, "PasswordAnswer", db_answer);
  666. DbParameter retValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  667. command.ExecuteNonQuery ();
  668. int returnValue = GetReturnValue (retValue);
  669. if (returnValue == 0)
  670. return newPassword;
  671. else if (returnValue == 3)
  672. throw new MembershipPasswordException ("Password Answer is invalid");
  673. else if (returnValue == 99)
  674. throw new MembershipPasswordException ("The user account is currently locked out");
  675. else
  676. throw new ProviderException ("Failed to reset password");
  677. }
  678. }
  679. public override void UpdateUser (MembershipUser user)
  680. {
  681. if (user == null)
  682. throw new ArgumentNullException ("user");
  683. if (user.UserName == null)
  684. throw new ArgumentNullException ("user.UserName");
  685. if (RequiresUniqueEmail && user.Email == null)
  686. throw new ArgumentNullException ("user.Email");
  687. CheckParam ("user.UserName", user.UserName, 256);
  688. if (user.Email.Length > 256 || (RequiresUniqueEmail && user.Email.Length == 0))
  689. throw new ArgumentException ("invalid format for user.Email");
  690. using (DbConnection connection = CreateConnection ()) {
  691. int returnValue = 0;
  692. DbCommand command = factory.CreateCommand ();
  693. command.Connection = connection;
  694. command.CommandText = @"aspnet_Membership_UpdateUser";
  695. command.CommandType = CommandType.StoredProcedure;
  696. AddParameter (command, "ApplicationName", ApplicationName);
  697. AddParameter (command, "UserName", user.UserName);
  698. AddParameter (command, "Email", user.Email == null ? (object) DBNull.Value : (object) user.Email);
  699. AddParameter (command, "Comment", user.Comment == null ? (object) DBNull.Value : (object) user.Comment);
  700. AddParameter (command, "IsApproved", user.IsApproved);
  701. AddParameter (command, "LastLoginDate", DateTime.UtcNow);
  702. AddParameter (command, "LastActivityDate", DateTime.UtcNow);
  703. AddParameter (command, "UniqueEmail", RequiresUniqueEmail);
  704. AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
  705. DbParameter retValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  706. command.ExecuteNonQuery ();
  707. returnValue = GetReturnValue (retValue);
  708. if (returnValue == 1)
  709. throw new ProviderException ("The UserName property of user was not found in the database.");
  710. if (returnValue == 7)
  711. throw new ProviderException ("The Email property of user was equal to an existing e-mail address in the database and RequiresUniqueEmail is set to true.");
  712. if (returnValue != 0)
  713. throw new ProviderException ("Failed to update user");
  714. }
  715. }
  716. public override bool ValidateUser (string username, string password)
  717. {
  718. if (username.Length == 0)
  719. return false;
  720. CheckParam ("username", username, 256);
  721. EmitValidatingPassword (username, password, false);
  722. PasswordInfo pi = ValidateUsingPassword (username, password);
  723. if (pi != null) {
  724. pi.LastLoginDate = DateTime.UtcNow;
  725. UpdateUserInfo (username, pi, true, true);
  726. return true;
  727. }
  728. return false;
  729. }
  730. public override bool UnlockUser (string username)
  731. {
  732. CheckParam ("username", username, 256);
  733. using (DbConnection connection = CreateConnection ()) {
  734. try {
  735. DbCommand command = factory.CreateCommand ();
  736. command.Connection = connection;
  737. command.CommandText = @"aspnet_Membership_UnlockUser"; ;
  738. command.CommandType = CommandType.StoredProcedure;
  739. AddParameter (command, "ApplicationName", ApplicationName);
  740. AddParameter (command, "UserName", username);
  741. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  742. command.ExecuteNonQuery ();
  743. if (GetReturnValue (returnValue) != 0)
  744. return false;
  745. }
  746. catch (Exception e) {
  747. throw new ProviderException ("Failed to unlock user", e);
  748. }
  749. }
  750. return true;
  751. }
  752. void UpdateUserInfo (string username, PasswordInfo pi, bool isPasswordCorrect, bool updateLoginActivity)
  753. {
  754. CheckParam ("username", username, 256);
  755. using (DbConnection connection = CreateConnection ()) {
  756. try {
  757. DbCommand command = factory.CreateCommand ();
  758. command.Connection = connection;
  759. command.CommandText = @"aspnet_Membership_UpdateUserInfo"; ;
  760. command.CommandType = CommandType.StoredProcedure;
  761. AddParameter (command, "ApplicationName", ApplicationName);
  762. AddParameter (command, "UserName", username);
  763. AddParameter (command, "IsPasswordCorrect", isPasswordCorrect);
  764. AddParameter (command, "UpdateLastLoginActivityDate", updateLoginActivity);
  765. AddParameter (command, "MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
  766. AddParameter (command, "PasswordAttemptWindow", PasswordAttemptWindow);
  767. AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
  768. AddParameter (command, "LastLoginDate", pi.LastLoginDate);
  769. AddParameter (command, "LastActivityDate", pi.LastActivityDate);
  770. DbParameter retValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  771. command.ExecuteNonQuery ();
  772. int returnValue = GetReturnValue (retValue);
  773. if (returnValue != 0)
  774. return;
  775. }
  776. catch (Exception e) {
  777. throw new ProviderException ("Failed to update Membership table", e);
  778. }
  779. }
  780. }
  781. PasswordInfo ValidateUsingPassword (string username, string password)
  782. {
  783. MembershipUser user = GetUser (username, true);
  784. if (user == null)
  785. return null;
  786. if (!user.IsApproved || user.IsLockedOut)
  787. return null;
  788. PasswordInfo pi = GetPasswordInfo (username);
  789. if (pi == null)
  790. return null;
  791. /* do the actual validation */
  792. string user_password = EncodePassword (password, pi.PasswordFormat, pi.PasswordSalt);
  793. if (user_password != pi.Password) {
  794. UpdateUserInfo (username, pi, false, false);
  795. return null;
  796. }
  797. return pi;
  798. }
  799. private PasswordInfo GetPasswordInfo (string username)
  800. {
  801. using (DbConnection connection = CreateConnection ()) {
  802. DbCommand command = factory.CreateCommand ();
  803. command.Connection = connection;
  804. command.CommandType = CommandType.StoredProcedure;
  805. command.CommandText = @"aspnet_Membership_GetPasswordWithFormat";
  806. AddParameter (command, "ApplicationName", ApplicationName);
  807. AddParameter (command, "UserName", username);
  808. AddParameter (command, "UpdateLastLoginActivityDate", false);
  809. AddParameter (command, "CurrentTimeUtc", DateTime.Now);
  810. DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
  811. DbDataReader reader = command.ExecuteReader ();
  812. if (!reader.Read ())
  813. return null;
  814. PasswordInfo pi = new PasswordInfo (
  815. reader.GetString (0),
  816. (MembershipPasswordFormat) reader.GetInt32 (1),
  817. reader.GetString (2),
  818. reader.GetInt32 (3),
  819. reader.GetInt32 (4),
  820. reader.GetBoolean (5),
  821. reader.GetDateTime (6),
  822. reader.GetDateTime (7));
  823. return pi;
  824. }
  825. }
  826. private string EncodePassword (string password, MembershipPasswordFormat passwordFormat, string salt)
  827. {
  828. byte [] password_bytes;
  829. byte [] salt_bytes;
  830. switch (passwordFormat) {
  831. case MembershipPasswordFormat.Clear:
  832. return password;
  833. case MembershipPasswordFormat.Hashed:
  834. password_bytes = Encoding.Unicode.GetBytes (password);
  835. salt_bytes = Convert.FromBase64String (salt);
  836. byte [] hashBytes = new byte [salt_bytes.Length + password_bytes.Length];
  837. Buffer.BlockCopy (salt_bytes, 0, hashBytes, 0, salt_bytes.Length);
  838. Buffer.BlockCopy (password_bytes, 0, hashBytes, salt_bytes.Length, password_bytes.Length);
  839. MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
  840. string alg_type = section.HashAlgorithmType;
  841. if (alg_type == "") {
  842. MachineKeySection keysection = (MachineKeySection) WebConfigurationManager.GetSection ("system.web/machineKey");
  843. alg_type = keysection.Validation.ToString ();
  844. }
  845. using (HashAlgorithm hash = HashAlgorithm.Create (alg_type)) {
  846. hash.TransformFinalBlock (hashBytes, 0, hashBytes.Length);
  847. return Convert.ToBase64String (hash.Hash);
  848. }
  849. case MembershipPasswordFormat.Encrypted:
  850. password_bytes = Encoding.Unicode.GetBytes (password);
  851. salt_bytes = Convert.FromBase64String (salt);
  852. byte [] buf = new byte [password_bytes.Length + salt_bytes.Length];
  853. Array.Copy (salt_bytes, 0, buf, 0, salt_bytes.Length);
  854. Array.Copy (password_bytes, 0, buf, salt_bytes.Length, password_bytes.Length);
  855. return Convert.ToBase64String (EncryptPassword (buf));
  856. default:
  857. /* not reached.. */
  858. return null;
  859. }
  860. }
  861. private string DecodePassword (string password, MembershipPasswordFormat passwordFormat)
  862. {
  863. switch (passwordFormat) {
  864. case MembershipPasswordFormat.Clear:
  865. return password;
  866. case MembershipPasswordFormat.Hashed:
  867. throw new ProviderException ("Hashed passwords cannot be decoded.");
  868. case MembershipPasswordFormat.Encrypted:
  869. return Encoding.Unicode.GetString (DecryptPassword (Convert.FromBase64String (password)));
  870. default:
  871. /* not reached.. */
  872. return null;
  873. }
  874. }
  875. public override string ApplicationName
  876. {
  877. get { return applicationName; }
  878. set { applicationName = value; }
  879. }
  880. public override bool EnablePasswordReset
  881. {
  882. get { return enablePasswordReset; }
  883. }
  884. public override bool EnablePasswordRetrieval
  885. {
  886. get { return enablePasswordRetrieval; }
  887. }
  888. public override MembershipPasswordFormat PasswordFormat
  889. {
  890. get { return passwordFormat; }
  891. }
  892. public override bool RequiresQuestionAndAnswer
  893. {
  894. get { return requiresQuestionAndAnswer; }
  895. }
  896. public override bool RequiresUniqueEmail
  897. {
  898. get { return requiresUniqueEmail; }
  899. }
  900. public override int MaxInvalidPasswordAttempts
  901. {
  902. get { return maxInvalidPasswordAttempts; }
  903. }
  904. public override int MinRequiredNonAlphanumericCharacters
  905. {
  906. get { return minRequiredNonAlphanumericCharacters; }
  907. }
  908. public override int MinRequiredPasswordLength
  909. {
  910. get { return minRequiredPasswordLength; }
  911. }
  912. public override int PasswordAttemptWindow
  913. {
  914. get { return passwordAttemptWindow; }
  915. }
  916. public override string PasswordStrengthRegularExpression
  917. {
  918. get { return passwordStrengthRegularExpression; }
  919. }
  920. [Flags]
  921. private enum DeleteUserTableMask
  922. {
  923. MembershipUsers = 1,
  924. UsersInRoles = 2,
  925. Profiles = 4,
  926. WebPartStateUser = 8
  927. }
  928. private sealed class PasswordInfo
  929. {
  930. private string _password;
  931. private MembershipPasswordFormat _passwordFormat;
  932. private string _passwordSalt;
  933. private int _failedPasswordAttemptCount;
  934. private int _failedPasswordAnswerAttemptCount;
  935. private bool _isApproved;
  936. private DateTime _lastLoginDate;
  937. private DateTime _lastActivityDate;
  938. internal PasswordInfo (
  939. string password,
  940. MembershipPasswordFormat passwordFormat,
  941. string passwordSalt,
  942. int failedPasswordAttemptCount,
  943. int failedPasswordAnswerAttemptCount,
  944. bool isApproved,
  945. DateTime lastLoginDate,
  946. DateTime lastActivityDate)
  947. {
  948. _password = password;
  949. _passwordFormat = passwordFormat;
  950. _passwordSalt = passwordSalt;
  951. _failedPasswordAttemptCount = failedPasswordAttemptCount;
  952. _failedPasswordAnswerAttemptCount = failedPasswordAnswerAttemptCount;
  953. _isApproved = isApproved;
  954. _lastLoginDate = lastLoginDate;
  955. _lastActivityDate = lastActivityDate;
  956. }
  957. public string Password
  958. {
  959. get { return _password; }
  960. set { _password = value; }
  961. }
  962. public MembershipPasswordFormat PasswordFormat
  963. {
  964. get { return _passwordFormat; }
  965. set { _passwordFormat = value; }
  966. }
  967. public string PasswordSalt
  968. {
  969. get { return _passwordSalt; }
  970. set { _passwordSalt = value; }
  971. }
  972. public int FailedPasswordAttemptCount
  973. {
  974. get { return _failedPasswordAttemptCount; }
  975. set { _failedPasswordAttemptCount = value; }
  976. }
  977. public int FailedPasswordAnswerAttemptCount
  978. {
  979. get { return _failedPasswordAnswerAttemptCount; }
  980. set { _failedPasswordAnswerAttemptCount = value; }
  981. }
  982. public bool IsApproved
  983. {
  984. get { return _isApproved; }
  985. set { _isApproved = value; }
  986. }
  987. public DateTime LastLoginDate
  988. {
  989. get { return _lastLoginDate; }
  990. set { _lastLoginDate = value; }
  991. }
  992. public DateTime LastActivityDate
  993. {
  994. get { return _lastActivityDate; }
  995. set { _lastActivityDate = value; }
  996. }
  997. }
  998. }
  999. }
  1000. #endif