2 // System.Web.Security.SqlMembershipProvider
5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Lluis Sanchez Gual (lluis@novell.com)
7 // Chris Toshok (toshok@ximian.com)
10 // Copyright (c) 2005,2006 Novell, Inc (http://www.novell.com)
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections;
34 using System.Collections.Specialized;
35 using System.Configuration;
36 using System.Configuration.Provider;
38 using System.Data.Common;
40 using System.Web.Configuration;
41 using System.Security.Cryptography;
43 namespace System.Web.Security {
44 public class SqlMembershipProvider : MembershipProvider
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;
58 ConnectionStringSettings connectionString;
59 DbProviderFactory factory;
61 string applicationName;
63 DbConnection CreateConnection ()
65 DbConnection connection = factory.CreateConnection ();
66 connection.ConnectionString = connectionString.ConnectionString;
72 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
74 return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
77 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
79 DbParameter dbp = command.CreateParameter ();
80 dbp.ParameterName = parameterName;
81 dbp.Value = parameterValue;
82 dbp.Direction = direction;
83 command.Parameters.Add (dbp);
87 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, DbType type, object parameterValue)
89 DbParameter dbp = command.CreateParameter ();
90 dbp.ParameterName = parameterName;
91 dbp.Value = parameterValue;
92 dbp.Direction = direction;
94 command.Parameters.Add (dbp);
98 static int GetReturnValue (DbParameter returnValue)
100 object value = returnValue.Value;
101 return value is int ? (int) value : -1;
104 void CheckParam (string pName, string p, int length)
107 throw new ArgumentNullException (pName);
108 if (p.Length == 0 || p.Length > length || p.IndexOf (",") != -1)
109 throw new ArgumentException (String.Format ("invalid format for {0}", pName));
112 public override bool ChangePassword (string username, string oldPwd, string newPwd)
114 if (username != null) username = username.Trim ();
115 if (oldPwd != null) oldPwd = oldPwd.Trim ();
116 if (newPwd != null) newPwd = newPwd.Trim ();
118 CheckParam ("username", username, 256);
119 CheckParam ("oldPwd", oldPwd, 128);
120 CheckParam ("newPwd", newPwd, 128);
122 using (DbConnection connection = CreateConnection ()) {
123 PasswordInfo pi = ValidateUsingPassword (username, oldPwd);
126 EmitValidatingPassword (username, newPwd, false);
127 string db_password = EncodePassword (newPwd, pi.PasswordFormat, pi.PasswordSalt);
129 DbCommand command = factory.CreateCommand ();
130 command.Connection = connection;
131 command.CommandText = @"aspnet_Membership_SetPassword";
132 command.CommandType = CommandType.StoredProcedure;
134 AddParameter (command, "@ApplicationName", ApplicationName);
135 AddParameter (command, "@UserName", username);
136 AddParameter (command, "@NewPassword", db_password);
137 AddParameter (command, "@PasswordFormat", pi.PasswordFormat);
138 AddParameter (command, "@PasswordSalt", pi.PasswordSalt);
139 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
140 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
142 command.ExecuteNonQuery ();
144 if (GetReturnValue (returnValue) != 0)
153 public override bool ChangePasswordQuestionAndAnswer (string username, string password, string newPwdQuestion, string newPwdAnswer)
155 if (username != null) username = username.Trim ();
156 if (newPwdQuestion != null) newPwdQuestion = newPwdQuestion.Trim ();
157 if (newPwdAnswer != null) newPwdAnswer = newPwdAnswer.Trim ();
159 CheckParam ("username", username, 256);
160 if (RequiresQuestionAndAnswer)
161 CheckParam ("newPwdQuestion", newPwdQuestion, 128);
162 if (RequiresQuestionAndAnswer)
163 CheckParam ("newPwdAnswer", newPwdAnswer, 128);
165 using (DbConnection connection = CreateConnection ()) {
166 PasswordInfo pi = ValidateUsingPassword (username, password);
169 string db_passwordAnswer = EncodePassword (newPwdAnswer, pi.PasswordFormat, pi.PasswordSalt);
171 DbCommand command = factory.CreateCommand ();
172 command.Connection = connection;
173 command.CommandType = CommandType.StoredProcedure;
174 command.CommandText = @"aspnet_Membership_ChangePasswordQuestionAndAnswer";
176 AddParameter (command, "@ApplicationName", ApplicationName);
177 AddParameter (command, "@UserName", username);
178 AddParameter (command, "@NewPasswordQuestion", newPwdQuestion);
179 AddParameter (command, "@NewPasswordAnswer", db_passwordAnswer);
180 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
182 command.ExecuteNonQuery ();
184 if (GetReturnValue (returnValue) != 0)
193 public override MembershipUser CreateUser (string username,
199 object providerUserKey,
200 out MembershipCreateStatus status)
202 if (username != null) username = username.Trim ();
203 if (password != null) password = password.Trim ();
204 if (email != null) email = email.Trim ();
205 if (pwdQuestion != null) pwdQuestion = pwdQuestion.Trim ();
206 if (pwdAnswer != null) pwdAnswer = pwdAnswer.Trim ();
208 /* some initial validation */
209 if (username == null || username.Length == 0 || username.Length > 256 || username.IndexOf (",") != -1) {
210 status = MembershipCreateStatus.InvalidUserName;
213 if (password == null || password.Length == 0 || password.Length > 128) {
214 status = MembershipCreateStatus.InvalidPassword;
218 if (!CheckPassword (password)) {
219 status = MembershipCreateStatus.InvalidPassword;
222 EmitValidatingPassword (username, password, true);
224 if (RequiresUniqueEmail && (email == null || email.Length == 0)) {
225 status = MembershipCreateStatus.InvalidEmail;
228 if (RequiresQuestionAndAnswer &&
229 (pwdQuestion == null ||
230 pwdQuestion.Length == 0 || pwdQuestion.Length > 256)) {
231 status = MembershipCreateStatus.InvalidQuestion;
234 if (RequiresQuestionAndAnswer &&
235 (pwdAnswer == null ||
236 pwdAnswer.Length == 0 || pwdAnswer.Length > 128)) {
237 status = MembershipCreateStatus.InvalidAnswer;
240 if (providerUserKey != null && !(providerUserKey is Guid)) {
241 status = MembershipCreateStatus.InvalidProviderUserKey;
245 if (providerUserKey == null)
246 providerUserKey = Guid.NewGuid();
248 /* encode our password/answer using the
249 * "passwordFormat" configuration option */
250 string passwordSalt = "";
252 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
253 byte [] salt = new byte [SALT_BYTES];
255 passwordSalt = Convert.ToBase64String (salt);
257 password = EncodePassword (password, PasswordFormat, passwordSalt);
258 if (RequiresQuestionAndAnswer)
259 pwdAnswer = EncodePassword (pwdAnswer, PasswordFormat, passwordSalt);
261 /* make sure the hashed/encrypted password and
262 * answer are still under 128 characters. */
263 if (password.Length > 128) {
264 status = MembershipCreateStatus.InvalidPassword;
268 if (RequiresQuestionAndAnswer) {
269 if (pwdAnswer.Length > 128) {
270 status = MembershipCreateStatus.InvalidAnswer;
274 status = MembershipCreateStatus.Success;
276 using (DbConnection connection = CreateConnection ()) {
279 DbCommand command = factory.CreateCommand ();
280 command.Connection = connection;
281 command.CommandText = @"aspnet_Membership_CreateUser";
282 command.CommandType = CommandType.StoredProcedure;
284 DateTime Now = DateTime.UtcNow;
286 AddParameter (command, "@ApplicationName", ApplicationName);
287 AddParameter (command, "@UserName", username);
288 AddParameter (command, "@Password", password);
289 AddParameter (command, "@PasswordSalt", passwordSalt);
290 AddParameter (command, "@Email", email);
291 AddParameter (command, "@PasswordQuestion", pwdQuestion);
292 AddParameter (command, "@PasswordAnswer", pwdAnswer);
293 AddParameter (command, "@IsApproved", isApproved);
294 AddParameter (command, "@CurrentTimeUtc", Now);
295 AddParameter (command, "@CreateDate", Now);
296 AddParameter (command, "@UniqueEmail", RequiresUniqueEmail);
297 AddParameter (command, "@PasswordFormat", PasswordFormat);
298 AddParameter (command, "@UserId", ParameterDirection.InputOutput, providerUserKey);
299 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
301 command.ExecuteNonQuery ();
303 int st = GetReturnValue (returnValue);
306 return GetUser (username, false);
308 status = MembershipCreateStatus.DuplicateUserName;
310 status = MembershipCreateStatus.DuplicateEmail;
312 status = MembershipCreateStatus.DuplicateProviderUserKey;
314 status = MembershipCreateStatus.ProviderError;
319 status = MembershipCreateStatus.ProviderError;
325 private bool CheckPassword (string password)
327 if (password.Length < MinRequiredPasswordLength)
330 if (MinRequiredNonAlphanumericCharacters > 0) {
331 int nonAlphanumeric = 0;
332 for (int i = 0; i < password.Length; i++) {
333 if (!Char.IsLetterOrDigit (password [i]))
336 return nonAlphanumeric >= MinRequiredNonAlphanumericCharacters;
341 public override bool DeleteUser (string username, bool deleteAllRelatedData)
343 CheckParam ("username", username, 256);
345 DeleteUserTableMask deleteBitmask = DeleteUserTableMask.MembershipUsers;
347 if (deleteAllRelatedData)
349 DeleteUserTableMask.Profiles |
350 DeleteUserTableMask.UsersInRoles |
351 DeleteUserTableMask.WebPartStateUser;
353 using (DbConnection connection = CreateConnection ()) {
354 DbCommand command = factory.CreateCommand ();
355 command.Connection = connection;
356 command.CommandText = @"aspnet_Users_DeleteUser";
357 command.CommandType = CommandType.StoredProcedure;
359 AddParameter (command, "@ApplicationName", ApplicationName);
360 AddParameter (command, "@UserName", username);
361 AddParameter (command, "@TablesToDeleteFrom", (int) deleteBitmask);
362 AddParameter (command, "@NumTablesDeletedFrom", ParameterDirection.Output, 0);
363 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
365 command.ExecuteNonQuery ();
367 if (((int) command.Parameters ["NumTablesDeletedFrom"].Value) == 0)
370 if (GetReturnValue (returnValue) == 0)
377 public virtual string GeneratePassword ()
379 return Membership.GeneratePassword (MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters);
382 public override MembershipUserCollection FindUsersByEmail (string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
384 CheckParam ("emailToMatch", emailToMatch, 256);
387 throw new ArgumentException ("pageIndex must be >= 0");
389 throw new ArgumentException ("pageSize must be >= 0");
390 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
391 throw new ArgumentException ("pageIndex and pageSize are too large");
393 using (DbConnection connection = CreateConnection ()) {
395 DbCommand command = factory.CreateCommand ();
396 command.Connection = connection;
397 command.CommandText = @"aspnet_Membership_FindUsersByEmail";
398 command.CommandType = CommandType.StoredProcedure;
400 AddParameter (command, "@PageIndex", pageIndex);
401 AddParameter (command, "@PageSize", pageSize);
402 AddParameter (command, "@EmailToMatch", emailToMatch);
403 AddParameter (command, "@ApplicationName", ApplicationName);
405 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
407 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
413 public override MembershipUserCollection FindUsersByName (string nameToMatch, int pageIndex, int pageSize, out int totalRecords)
415 CheckParam ("nameToMatch", nameToMatch, 256);
418 throw new ArgumentException ("pageIndex must be >= 0");
420 throw new ArgumentException ("pageSize must be >= 0");
421 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
422 throw new ArgumentException ("pageIndex and pageSize are too large");
424 using (DbConnection connection = CreateConnection ()) {
426 DbCommand command = factory.CreateCommand ();
427 command.Connection = connection;
428 command.CommandText = @"aspnet_Membership_FindUsersByName";
429 command.CommandType = CommandType.StoredProcedure;
431 AddParameter (command, "@PageIndex", pageIndex);
432 AddParameter (command, "@PageSize", pageSize);
433 AddParameter (command, "@UserNameToMatch", nameToMatch);
434 AddParameter (command, "@ApplicationName", ApplicationName);
436 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
438 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
444 public override MembershipUserCollection GetAllUsers (int pageIndex, int pageSize, out int totalRecords)
447 throw new ArgumentException ("pageIndex must be >= 0");
449 throw new ArgumentException ("pageSize must be >= 0");
450 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
451 throw new ArgumentException ("pageIndex and pageSize are too large");
453 using (DbConnection connection = CreateConnection ()) {
454 DbCommand command = factory.CreateCommand ();
455 command.Connection = connection;
456 command.CommandText = @"aspnet_Membership_GetAllUsers";
457 command.CommandType = CommandType.StoredProcedure;
459 AddParameter (command, "@ApplicationName", ApplicationName);
460 AddParameter (command, "@PageIndex", pageIndex);
461 AddParameter (command, "@PageSize", pageSize);
463 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
465 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
471 MembershipUserCollection BuildMembershipUserCollection (DbCommand command, int pageIndex, int pageSize, out int totalRecords)
473 DbDataReader reader = null;
475 MembershipUserCollection users = new MembershipUserCollection ();
476 reader = command.ExecuteReader ();
477 while (reader.Read ())
478 users.Add (GetUserFromReader (reader, null, null));
480 totalRecords = Convert.ToInt32 (command.Parameters ["ReturnValue"].Value);
482 } catch (Exception) {
484 return null; /* should we let the exception through? */
493 public override int GetNumberOfUsersOnline ()
495 using (DbConnection connection = CreateConnection ()) {
496 DateTime now = DateTime.UtcNow;
498 DbCommand command = factory.CreateCommand ();
499 command.Connection = connection;
500 command.CommandText = @"aspnet_Membership_GetNumberOfUsersOnline";
501 command.CommandType = CommandType.StoredProcedure;
503 AddParameter (command, "@CurrentTimeUtc", now.ToString ());
504 AddParameter (command, "@ApplicationName", ApplicationName);
505 AddParameter (command, "@MinutesSinceLastInActive", userIsOnlineTimeWindow.Minutes);
506 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
508 command.ExecuteScalar ();
509 return GetReturnValue (returnValue);
513 public override string GetPassword (string username, string answer)
515 if (!EnablePasswordRetrieval)
516 throw new NotSupportedException ("this provider has not been configured to allow the retrieval of passwords");
518 CheckParam ("username", username, 256);
519 if (RequiresQuestionAndAnswer)
520 CheckParam ("answer", answer, 128);
522 PasswordInfo pi = GetPasswordInfo (username);
524 throw new ProviderException ("An error occurred while retrieving the password from the database");
526 string user_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
527 string password = null;
529 using (DbConnection connection = CreateConnection ()) {
530 DbCommand command = factory.CreateCommand ();
531 command.Connection = connection;
532 command.CommandText = @"aspnet_Membership_GetPassword";
533 command.CommandType = CommandType.StoredProcedure;
535 AddParameter (command, "@ApplicationName", ApplicationName);
536 AddParameter (command, "@UserName", username);
537 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
538 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
539 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
540 AddParameter (command, "@PasswordAnswer", user_answer);
541 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
543 DbDataReader reader = command.ExecuteReader ();
545 int returnValue = GetReturnValue (retValue);
546 if (returnValue == 3)
547 throw new MembershipPasswordException ("Password Answer is invalid");
548 if (returnValue == 99)
549 throw new MembershipPasswordException ("The user account is currently locked out");
551 if (reader.Read ()) {
552 password = reader.GetString (0);
556 if (pi.PasswordFormat == MembershipPasswordFormat.Clear)
558 else if (pi.PasswordFormat == MembershipPasswordFormat.Encrypted)
559 return DecodePassword (password, pi.PasswordFormat);
565 MembershipUser GetUserFromReader (DbDataReader reader, string username, object userId)
568 if (username == null)
572 username = reader.GetString (8);
574 return new MembershipUser (this.Name, /* XXX is this right? */
575 (username == null ? reader.GetString (0) : username), /* name */
576 (userId == null ? reader.GetGuid (8 + i) : userId), /* providerUserKey */
577 reader.IsDBNull (0 + i) ? null : reader.GetString (0 + i), /* email */
578 reader.IsDBNull (1 + i) ? null : reader.GetString (1 + i), /* passwordQuestion */
579 reader.IsDBNull (2 + i) ? null : reader.GetString (2 + i), /* comment */
580 reader.GetBoolean (3 + i), /* isApproved */
581 reader.GetBoolean (9 + i), /* isLockedOut */
582 reader.GetDateTime (4 + i).ToLocalTime (), /* creationDate */
583 reader.GetDateTime (5 + i).ToLocalTime (), /* lastLoginDate */
584 reader.GetDateTime (6 + i).ToLocalTime (), /* lastActivityDate */
585 reader.GetDateTime (7 + i).ToLocalTime (), /* lastPasswordChangedDate */
586 reader.GetDateTime (10 + i).ToLocalTime () /* lastLockoutDate */);
589 MembershipUser BuildMembershipUser (DbCommand query, string username, object userId)
592 using (DbConnection connection = CreateConnection ()) {
593 query.Connection = connection;
594 using (DbDataReader reader = query.ExecuteReader ()) {
598 return GetUserFromReader (reader, username, userId);
601 } catch (Exception) {
602 return null; /* should we let the exception through? */
605 query.Connection = null;
609 public override MembershipUser GetUser (string username, bool userIsOnline)
611 if (username == null)
612 throw new ArgumentNullException ("username");
614 if (username.Length == 0)
617 CheckParam ("username", username, 256);
619 DbCommand command = factory.CreateCommand ();
621 command.CommandText = @"aspnet_Membership_GetUserByName";
622 command.CommandType = CommandType.StoredProcedure;
624 AddParameter (command, "@UserName", username);
625 AddParameter (command, "@ApplicationName", ApplicationName);
626 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
627 AddParameter (command, "@UpdateLastActivity", userIsOnline);
629 MembershipUser u = BuildMembershipUser (command, username, null);
634 public override MembershipUser GetUser (object providerUserKey, bool userIsOnline)
636 DbCommand command = factory.CreateCommand ();
637 command.CommandText = @"aspnet_Membership_GetUserByUserId";
638 command.CommandType = CommandType.StoredProcedure;
640 AddParameter (command, "@UserId", providerUserKey);
641 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
642 AddParameter (command, "@UpdateLastActivity", userIsOnline);
644 MembershipUser u = BuildMembershipUser (command, string.Empty, providerUserKey);
648 public override string GetUserNameByEmail (string email)
650 CheckParam ("email", email, 256);
652 using (DbConnection connection = CreateConnection ()) {
654 DbCommand command = factory.CreateCommand ();
655 command.Connection = connection;
656 command.CommandText = @"aspnet_Membership_GetUserByEmail";
657 command.CommandType = CommandType.StoredProcedure;
659 AddParameter (command, "@ApplicationName", ApplicationName);
660 AddParameter (command, "@Email", email);
662 DbDataReader reader = command.ExecuteReader ();
665 rv = reader.GetString (0);
671 bool GetBoolConfigValue (NameValueCollection config, string name, bool def)
674 string val = config [name];
676 try { rv = Boolean.Parse (val); }
677 catch (Exception e) {
678 throw new ProviderException (String.Format ("{0} must be true or false", name), e);
684 int GetIntConfigValue (NameValueCollection config, string name, int def)
687 string val = config [name];
689 try { rv = Int32.Parse (val); }
690 catch (Exception e) {
691 throw new ProviderException (String.Format ("{0} must be an integer", name), e);
697 int GetEnumConfigValue (NameValueCollection config, string name, Type enumType, int def)
700 string val = config [name];
702 try { rv = (int) Enum.Parse (enumType, val); }
703 catch (Exception e) {
704 throw new ProviderException (String.Format ("{0} must be one of the following values: {1}", name, String.Join (",", Enum.GetNames (enumType))), e);
710 string GetStringConfigValue (NameValueCollection config, string name, string def)
713 string val = config [name];
719 void EmitValidatingPassword (string username, string password, bool isNewUser)
721 ValidatePasswordEventArgs args = new ValidatePasswordEventArgs (username, password, isNewUser);
722 OnValidatingPassword (args);
724 /* if we're canceled.. */
726 if (args.FailureInformation == null)
727 throw new ProviderException ("Password validation canceled");
729 throw args.FailureInformation;
733 public override void Initialize (string name, NameValueCollection config)
736 throw new ArgumentNullException ("config");
738 base.Initialize (name, config);
740 applicationName = GetStringConfigValue (config, "applicationName", "/");
741 enablePasswordReset = GetBoolConfigValue (config, "enablePasswordReset", true);
742 enablePasswordRetrieval = GetBoolConfigValue (config, "enablePasswordRetrieval", false);
743 requiresQuestionAndAnswer = GetBoolConfigValue (config, "requiresQuestionAndAnswer", true);
744 requiresUniqueEmail = GetBoolConfigValue (config, "requiresUniqueEmail", false);
745 passwordFormat = (MembershipPasswordFormat) GetEnumConfigValue (config, "passwordFormat", typeof (MembershipPasswordFormat),
746 (int) MembershipPasswordFormat.Hashed);
747 maxInvalidPasswordAttempts = GetIntConfigValue (config, "maxInvalidPasswordAttempts", 5);
748 minRequiredPasswordLength = GetIntConfigValue (config, "minRequiredPasswordLength", 7);
749 minRequiredNonAlphanumericCharacters = GetIntConfigValue (config, "minRequiredNonalphanumericCharacters", 1);
750 passwordAttemptWindow = GetIntConfigValue (config, "passwordAttemptWindow", 10);
751 passwordStrengthRegularExpression = GetStringConfigValue (config, "passwordStrengthRegularExpression", "");
753 MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
755 userIsOnlineTimeWindow = section.UserIsOnlineTimeWindow;
757 /* we can't support password retrieval with hashed passwords */
758 if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval)
759 throw new ProviderException ("password retrieval cannot be used with hashed passwords");
761 string connectionStringName = config ["connectionStringName"];
763 if (applicationName.Length > 256)
764 throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
765 if (connectionStringName == null || connectionStringName.Length == 0)
766 throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
768 connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];
769 factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ?
770 System.Data.SqlClient.SqlClientFactory.Instance :
771 ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName);
774 public override string ResetPassword (string username, string answer)
776 if (!EnablePasswordReset)
777 throw new NotSupportedException ("this provider has not been configured to allow the resetting of passwords");
779 CheckParam ("username", username, 256);
781 if (RequiresQuestionAndAnswer)
782 CheckParam ("answer", answer, 128);
784 using (DbConnection connection = CreateConnection ()) {
786 PasswordInfo pi = GetPasswordInfo (username);
788 throw new ProviderException (username + "is not found in the membership database");
790 string newPassword = GeneratePassword ();
791 EmitValidatingPassword (username, newPassword, false);
793 string db_password = EncodePassword (newPassword, pi.PasswordFormat, pi.PasswordSalt);
794 string db_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
796 DbCommand command = factory.CreateCommand ();
797 command.Connection = connection;
798 command.CommandText = @"aspnet_Membership_ResetPassword";
799 command.CommandType = CommandType.StoredProcedure;
801 AddParameter (command, "@ApplicationName", ApplicationName);
802 AddParameter (command, "@UserName", username);
803 AddParameter (command, "@NewPassword", db_password);
804 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
805 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
806 AddParameter (command, "@PasswordSalt", pi.PasswordSalt);
807 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
808 AddParameter (command, "@PasswordFormat", pi.PasswordFormat);
809 AddParameter (command, "@PasswordAnswer", db_answer);
810 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
812 command.ExecuteNonQuery ();
814 int returnValue = GetReturnValue (retValue);
816 if (returnValue == 0)
818 else if (returnValue == 3)
819 throw new MembershipPasswordException ("Password Answer is invalid");
820 else if (returnValue == 99)
821 throw new MembershipPasswordException ("The user account is currently locked out");
823 throw new ProviderException ("Failed to reset password");
827 public override void UpdateUser (MembershipUser user)
830 throw new ArgumentNullException ("user");
832 if (user.UserName == null)
833 throw new ArgumentNullException ("user.UserName");
835 if (RequiresUniqueEmail && user.Email == null)
836 throw new ArgumentNullException ("user.Email");
838 CheckParam ("user.UserName", user.UserName, 256);
840 if (user.Email.Length > 256 || (RequiresUniqueEmail && user.Email.Length == 0))
841 throw new ArgumentException ("invalid format for user.Email");
843 using (DbConnection connection = CreateConnection ()) {
846 DbCommand command = factory.CreateCommand ();
847 command.Connection = connection;
848 command.CommandText = @"aspnet_Membership_UpdateUser";
849 command.CommandType = CommandType.StoredProcedure;
851 AddParameter (command, "@ApplicationName", ApplicationName);
852 AddParameter (command, "@UserName", user.UserName);
853 AddParameter (command, "@Email", user.Email == null ? (object) DBNull.Value : (object) user.Email);
854 AddParameter (command, "@Comment", user.Comment == null ? (object) DBNull.Value : (object) user.Comment);
855 AddParameter (command, "@IsApproved", user.IsApproved);
856 AddParameter (command, "@LastLoginDate", DateTime.UtcNow);
857 AddParameter (command, "@LastActivityDate", DateTime.UtcNow);
858 AddParameter (command, "@UniqueEmail", RequiresUniqueEmail);
859 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
860 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
862 command.ExecuteNonQuery ();
864 returnValue = GetReturnValue (retValue);
866 if (returnValue == 1)
867 throw new ProviderException ("The UserName property of user was not found in the database.");
868 if (returnValue == 7)
869 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.");
870 if (returnValue != 0)
871 throw new ProviderException ("Failed to update user");
875 public override bool ValidateUser (string username, string password)
877 if (username.Length == 0)
880 CheckParam ("username", username, 256);
881 EmitValidatingPassword (username, password, false);
883 PasswordInfo pi = ValidateUsingPassword (username, password);
885 pi.LastLoginDate = DateTime.UtcNow;
886 UpdateUserInfo (username, pi, true, true);
893 public override bool UnlockUser (string username)
895 CheckParam ("username", username, 256);
897 using (DbConnection connection = CreateConnection ()) {
899 DbCommand command = factory.CreateCommand ();
900 command.Connection = connection;
901 command.CommandText = @"aspnet_Membership_UnlockUser"; ;
902 command.CommandType = CommandType.StoredProcedure;
904 AddParameter (command, "@ApplicationName", ApplicationName);
905 AddParameter (command, "@UserName", username);
906 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
908 command.ExecuteNonQuery ();
909 if (GetReturnValue (returnValue) != 0)
912 catch (Exception e) {
913 throw new ProviderException ("Failed to unlock user", e);
919 void UpdateUserInfo (string username, PasswordInfo pi, bool isPasswordCorrect, bool updateLoginActivity)
921 CheckParam ("username", username, 256);
923 using (DbConnection connection = CreateConnection ()) {
925 DbCommand command = factory.CreateCommand ();
926 command.Connection = connection;
927 command.CommandText = @"aspnet_Membership_UpdateUserInfo"; ;
928 command.CommandType = CommandType.StoredProcedure;
930 AddParameter (command, "@ApplicationName", ApplicationName);
931 AddParameter (command, "@UserName", username);
932 AddParameter (command, "@IsPasswordCorrect", isPasswordCorrect);
933 AddParameter (command, "@UpdateLastLoginActivityDate", updateLoginActivity);
934 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
935 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
936 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
937 AddParameter (command, "@LastLoginDate", pi.LastLoginDate);
938 AddParameter (command, "@LastActivityDate", pi.LastActivityDate);
939 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
941 command.ExecuteNonQuery ();
943 int returnValue = GetReturnValue (retValue);
944 if (returnValue != 0)
947 catch (Exception e) {
948 throw new ProviderException ("Failed to update Membership table", e);
954 PasswordInfo ValidateUsingPassword (string username, string password)
956 MembershipUser user = GetUser (username, true);
960 if (!user.IsApproved || user.IsLockedOut)
963 PasswordInfo pi = GetPasswordInfo (username);
968 /* do the actual validation */
969 string user_password = EncodePassword (password, pi.PasswordFormat, pi.PasswordSalt);
971 if (user_password != pi.Password) {
972 UpdateUserInfo (username, pi, false, false);
979 private PasswordInfo GetPasswordInfo (string username)
981 using (DbConnection connection = CreateConnection ()) {
982 DbCommand command = factory.CreateCommand ();
983 command.Connection = connection;
984 command.CommandType = CommandType.StoredProcedure;
985 command.CommandText = @"aspnet_Membership_GetPasswordWithFormat";
987 AddParameter (command, "@ApplicationName", ApplicationName);
988 AddParameter (command, "@UserName", username);
989 AddParameter (command, "@UpdateLastLoginActivityDate", false);
990 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
992 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
994 DbDataReader reader = command.ExecuteReader ();
998 PasswordInfo pi = new PasswordInfo (
999 reader.GetString (0),
1000 (MembershipPasswordFormat) reader.GetInt32 (1),
1001 reader.GetString (2),
1002 reader.GetInt32 (3),
1003 reader.GetInt32 (4),
1004 reader.GetBoolean (5),
1005 reader.GetDateTime (6),
1006 reader.GetDateTime (7));
1012 private string EncodePassword (string password, MembershipPasswordFormat passwordFormat, string salt)
1014 byte [] password_bytes;
1017 switch (passwordFormat) {
1018 case MembershipPasswordFormat.Clear:
1020 case MembershipPasswordFormat.Hashed:
1021 password_bytes = Encoding.Unicode.GetBytes (password);
1022 salt_bytes = Convert.FromBase64String (salt);
1024 byte [] hashBytes = new byte [salt_bytes.Length + password_bytes.Length];
1026 Buffer.BlockCopy (salt_bytes, 0, hashBytes, 0, salt_bytes.Length);
1027 Buffer.BlockCopy (password_bytes, 0, hashBytes, salt_bytes.Length, password_bytes.Length);
1029 MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
1030 string alg_type = section.HashAlgorithmType;
1031 if (alg_type == "") {
1032 MachineKeySection keysection = (MachineKeySection) WebConfigurationManager.GetSection ("system.web/machineKey");
1033 alg_type = keysection.Validation.ToString ();
1035 using (HashAlgorithm hash = HashAlgorithm.Create (alg_type)) {
1036 hash.TransformFinalBlock (hashBytes, 0, hashBytes.Length);
1037 return Convert.ToBase64String (hash.Hash);
1039 case MembershipPasswordFormat.Encrypted:
1040 password_bytes = Encoding.Unicode.GetBytes (password);
1041 salt_bytes = Convert.FromBase64String (salt);
1043 byte [] buf = new byte [password_bytes.Length + salt_bytes.Length];
1045 Array.Copy (salt_bytes, 0, buf, 0, salt_bytes.Length);
1046 Array.Copy (password_bytes, 0, buf, salt_bytes.Length, password_bytes.Length);
1048 return Convert.ToBase64String (EncryptPassword (buf));
1055 private string DecodePassword (string password, MembershipPasswordFormat passwordFormat)
1057 switch (passwordFormat) {
1058 case MembershipPasswordFormat.Clear:
1060 case MembershipPasswordFormat.Hashed:
1061 throw new ProviderException ("Hashed passwords cannot be decoded.");
1062 case MembershipPasswordFormat.Encrypted:
1063 return Encoding.Unicode.GetString (DecryptPassword (Convert.FromBase64String (password)));
1070 public override string ApplicationName
1072 get { return applicationName; }
1073 set { applicationName = value; }
1076 public override bool EnablePasswordReset
1078 get { return enablePasswordReset; }
1081 public override bool EnablePasswordRetrieval
1083 get { return enablePasswordRetrieval; }
1086 public override MembershipPasswordFormat PasswordFormat
1088 get { return passwordFormat; }
1091 public override bool RequiresQuestionAndAnswer
1093 get { return requiresQuestionAndAnswer; }
1096 public override bool RequiresUniqueEmail
1098 get { return requiresUniqueEmail; }
1101 public override int MaxInvalidPasswordAttempts
1103 get { return maxInvalidPasswordAttempts; }
1106 public override int MinRequiredNonAlphanumericCharacters
1108 get { return minRequiredNonAlphanumericCharacters; }
1111 public override int MinRequiredPasswordLength
1113 get { return minRequiredPasswordLength; }
1116 public override int PasswordAttemptWindow
1118 get { return passwordAttemptWindow; }
1121 public override string PasswordStrengthRegularExpression
1123 get { return passwordStrengthRegularExpression; }
1127 private enum DeleteUserTableMask
1129 MembershipUsers = 1,
1132 WebPartStateUser = 8
1135 private sealed class PasswordInfo
1137 private string _password;
1138 private MembershipPasswordFormat _passwordFormat;
1139 private string _passwordSalt;
1140 private int _failedPasswordAttemptCount;
1141 private int _failedPasswordAnswerAttemptCount;
1142 private bool _isApproved;
1143 private DateTime _lastLoginDate;
1144 private DateTime _lastActivityDate;
1146 internal PasswordInfo (
1148 MembershipPasswordFormat passwordFormat,
1149 string passwordSalt,
1150 int failedPasswordAttemptCount,
1151 int failedPasswordAnswerAttemptCount,
1153 DateTime lastLoginDate,
1154 DateTime lastActivityDate)
1156 _password = password;
1157 _passwordFormat = passwordFormat;
1158 _passwordSalt = passwordSalt;
1159 _failedPasswordAttemptCount = failedPasswordAttemptCount;
1160 _failedPasswordAnswerAttemptCount = failedPasswordAnswerAttemptCount;
1161 _isApproved = isApproved;
1162 _lastLoginDate = lastLoginDate;
1163 _lastActivityDate = lastActivityDate;
1166 public string Password
1168 get { return _password; }
1169 set { _password = value; }
1171 public MembershipPasswordFormat PasswordFormat
1173 get { return _passwordFormat; }
1174 set { _passwordFormat = value; }
1176 public string PasswordSalt
1178 get { return _passwordSalt; }
1179 set { _passwordSalt = value; }
1181 public int FailedPasswordAttemptCount
1183 get { return _failedPasswordAttemptCount; }
1184 set { _failedPasswordAttemptCount = value; }
1186 public int FailedPasswordAnswerAttemptCount
1188 get { return _failedPasswordAnswerAttemptCount; }
1189 set { _failedPasswordAnswerAttemptCount = value; }
1191 public bool IsApproved
1193 get { return _isApproved; }
1194 set { _isApproved = value; }
1196 public DateTime LastLoginDate
1198 get { return _lastLoginDate; }
1199 set { _lastLoginDate = value; }
1201 public DateTime LastActivityDate
1203 get { return _lastActivityDate; }
1204 set { _lastActivityDate = value; }