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.
32 using System.Collections;
33 using System.Collections.Specialized;
34 using System.Configuration;
35 using System.Configuration.Provider;
37 using System.Data.Common;
39 using System.Web.Configuration;
40 using System.Security.Cryptography;
42 namespace System.Web.Security {
43 public class SqlMembershipProvider : MembershipProvider
45 bool enablePasswordReset;
46 bool enablePasswordRetrieval;
47 int maxInvalidPasswordAttempts;
48 MembershipPasswordFormat passwordFormat;
49 bool requiresQuestionAndAnswer;
50 bool requiresUniqueEmail;
51 int minRequiredNonAlphanumericCharacters;
52 int minRequiredPasswordLength;
53 int passwordAttemptWindow;
54 string passwordStrengthRegularExpression;
55 TimeSpan userIsOnlineTimeWindow;
57 ConnectionStringSettings connectionString;
58 DbProviderFactory factory;
60 string applicationName;
61 bool schemaIsOk = false;
63 DbConnection CreateConnection ()
65 if (!schemaIsOk && !(schemaIsOk = AspNetDBSchemaChecker.CheckMembershipSchemaVersion (factory, connectionString.ConnectionString, "membership", "1")))
66 throw new ProviderException ("Incorrect ASP.NET DB Schema Version.");
68 DbConnection connection;
70 if (connectionString == null)
71 throw new ProviderException ("Connection string for the SQL Membership Provider has not been provided.");
74 connection = factory.CreateConnection ();
75 connection.ConnectionString = connectionString.ConnectionString;
77 } catch (Exception ex) {
78 throw new ProviderException ("Unable to open SQL connection for the SQL Membership Provider.",
85 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
87 return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
90 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
92 DbParameter dbp = command.CreateParameter ();
93 dbp.ParameterName = parameterName;
94 dbp.Value = parameterValue;
95 dbp.Direction = direction;
96 command.Parameters.Add (dbp);
100 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, DbType type, object parameterValue)
102 DbParameter dbp = command.CreateParameter ();
103 dbp.ParameterName = parameterName;
104 dbp.Value = parameterValue;
105 dbp.Direction = direction;
107 command.Parameters.Add (dbp);
111 static int GetReturnValue (DbParameter returnValue)
113 object value = returnValue.Value;
114 return value is int ? (int) value : -1;
117 void CheckParam (string pName, string p, int length)
120 throw new ArgumentNullException (pName);
121 if (p.Length == 0 || p.Length > length || p.IndexOf (',') != -1)
122 throw new ArgumentException (String.Format ("invalid format for {0}", pName));
125 public override bool ChangePassword (string username, string oldPwd, string newPwd)
127 if (username != null) username = username.Trim ();
128 if (oldPwd != null) oldPwd = oldPwd.Trim ();
129 if (newPwd != null) newPwd = newPwd.Trim ();
131 CheckParam ("username", username, 256);
132 CheckParam ("oldPwd", oldPwd, 128);
133 CheckParam ("newPwd", newPwd, 128);
135 if (!CheckPassword (newPwd))
136 throw new ArgumentException (string.Format (
137 "New Password invalid. New Password length minimum: {0}. Non-alphanumeric characters required: {1}.",
138 MinRequiredPasswordLength,
139 MinRequiredNonAlphanumericCharacters));
141 using (DbConnection connection = CreateConnection ()) {
142 PasswordInfo pi = ValidateUsingPassword (username, oldPwd);
145 EmitValidatingPassword (username, newPwd, false);
146 string db_password = EncodePassword (newPwd, pi.PasswordFormat, pi.PasswordSalt);
148 DbCommand command = factory.CreateCommand ();
149 command.Connection = connection;
150 command.CommandText = @"aspnet_Membership_SetPassword";
151 command.CommandType = CommandType.StoredProcedure;
153 AddParameter (command, "@ApplicationName", ApplicationName);
154 AddParameter (command, "@UserName", username);
155 AddParameter (command, "@NewPassword", db_password);
156 AddParameter (command, "@PasswordFormat", (int) pi.PasswordFormat);
157 AddParameter (command, "@PasswordSalt", pi.PasswordSalt);
158 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
159 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
161 command.ExecuteNonQuery ();
163 if (GetReturnValue (returnValue) != 0)
172 public override bool ChangePasswordQuestionAndAnswer (string username, string password, string newPwdQuestion, string newPwdAnswer)
174 if (username != null) username = username.Trim ();
175 if (newPwdQuestion != null) newPwdQuestion = newPwdQuestion.Trim ();
176 if (newPwdAnswer != null) newPwdAnswer = newPwdAnswer.Trim ();
178 CheckParam ("username", username, 256);
179 if (RequiresQuestionAndAnswer)
180 CheckParam ("newPwdQuestion", newPwdQuestion, 128);
181 if (RequiresQuestionAndAnswer)
182 CheckParam ("newPwdAnswer", newPwdAnswer, 128);
184 using (DbConnection connection = CreateConnection ()) {
185 PasswordInfo pi = ValidateUsingPassword (username, password);
188 string db_passwordAnswer = EncodePassword (newPwdAnswer, pi.PasswordFormat, pi.PasswordSalt);
190 DbCommand command = factory.CreateCommand ();
191 command.Connection = connection;
192 command.CommandType = CommandType.StoredProcedure;
193 command.CommandText = @"aspnet_Membership_ChangePasswordQuestionAndAnswer";
195 AddParameter (command, "@ApplicationName", ApplicationName);
196 AddParameter (command, "@UserName", username);
197 AddParameter (command, "@NewPasswordQuestion", newPwdQuestion);
198 AddParameter (command, "@NewPasswordAnswer", db_passwordAnswer);
199 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
201 command.ExecuteNonQuery ();
203 if (GetReturnValue (returnValue) != 0)
212 public override MembershipUser CreateUser (string username,
218 object providerUserKey,
219 out MembershipCreateStatus status)
221 if (username != null) username = username.Trim ();
222 if (password != null) password = password.Trim ();
223 if (email != null) email = email.Trim ();
224 if (pwdQuestion != null) pwdQuestion = pwdQuestion.Trim ();
225 if (pwdAnswer != null) pwdAnswer = pwdAnswer.Trim ();
227 /* some initial validation */
228 if (username == null || username.Length == 0 || username.Length > 256 || username.IndexOf (',') != -1) {
229 status = MembershipCreateStatus.InvalidUserName;
232 if (password == null || password.Length == 0 || password.Length > 128) {
233 status = MembershipCreateStatus.InvalidPassword;
237 if (!CheckPassword (password)) {
238 status = MembershipCreateStatus.InvalidPassword;
241 EmitValidatingPassword (username, password, true);
243 if (RequiresUniqueEmail && (email == null || email.Length == 0)) {
244 status = MembershipCreateStatus.InvalidEmail;
247 if (RequiresQuestionAndAnswer &&
248 (pwdQuestion == null ||
249 pwdQuestion.Length == 0 || pwdQuestion.Length > 256)) {
250 status = MembershipCreateStatus.InvalidQuestion;
253 if (RequiresQuestionAndAnswer &&
254 (pwdAnswer == null ||
255 pwdAnswer.Length == 0 || pwdAnswer.Length > 128)) {
256 status = MembershipCreateStatus.InvalidAnswer;
259 if (providerUserKey != null && !(providerUserKey is Guid)) {
260 status = MembershipCreateStatus.InvalidProviderUserKey;
264 if (providerUserKey == null)
265 providerUserKey = Guid.NewGuid();
267 /* encode our password/answer using the
268 * "passwordFormat" configuration option */
269 string passwordSalt = "";
271 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
272 byte [] salt = new byte [MembershipHelper.SALT_BYTES];
274 passwordSalt = Convert.ToBase64String (salt);
276 password = EncodePassword (password, PasswordFormat, passwordSalt);
277 if (RequiresQuestionAndAnswer)
278 pwdAnswer = EncodePassword (pwdAnswer, PasswordFormat, passwordSalt);
280 /* make sure the hashed/encrypted password and
281 * answer are still under 128 characters. */
282 if (password.Length > 128) {
283 status = MembershipCreateStatus.InvalidPassword;
287 if (RequiresQuestionAndAnswer) {
288 if (pwdAnswer.Length > 128) {
289 status = MembershipCreateStatus.InvalidAnswer;
293 status = MembershipCreateStatus.Success;
295 using (DbConnection connection = CreateConnection ()) {
298 DbCommand command = factory.CreateCommand ();
299 command.Connection = connection;
300 command.CommandText = @"aspnet_Membership_CreateUser";
301 command.CommandType = CommandType.StoredProcedure;
303 DateTime Now = DateTime.UtcNow;
305 AddParameter (command, "@ApplicationName", ApplicationName);
306 AddParameter (command, "@UserName", username);
307 AddParameter (command, "@Password", password);
308 AddParameter (command, "@PasswordSalt", passwordSalt);
309 AddParameter (command, "@Email", email);
310 AddParameter (command, "@PasswordQuestion", pwdQuestion);
311 AddParameter (command, "@PasswordAnswer", pwdAnswer);
312 AddParameter (command, "@IsApproved", isApproved);
313 AddParameter (command, "@CurrentTimeUtc", Now);
314 AddParameter (command, "@CreateDate", Now);
315 AddParameter (command, "@UniqueEmail", RequiresUniqueEmail);
316 AddParameter (command, "@PasswordFormat", (int) PasswordFormat);
317 AddParameter (command, "@UserId", ParameterDirection.InputOutput, providerUserKey);
318 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
320 command.ExecuteNonQuery ();
322 int st = GetReturnValue (returnValue);
325 return GetUser (username, false);
327 status = MembershipCreateStatus.DuplicateUserName;
329 status = MembershipCreateStatus.DuplicateEmail;
331 status = MembershipCreateStatus.DuplicateProviderUserKey;
333 status = MembershipCreateStatus.ProviderError;
338 status = MembershipCreateStatus.ProviderError;
344 bool CheckPassword (string password)
346 if (password.Length < MinRequiredPasswordLength)
349 if (MinRequiredNonAlphanumericCharacters > 0) {
350 int nonAlphanumeric = 0;
351 for (int i = 0; i < password.Length; i++) {
352 if (!Char.IsLetterOrDigit (password [i]))
355 return nonAlphanumeric >= MinRequiredNonAlphanumericCharacters;
360 public override bool DeleteUser (string username, bool deleteAllRelatedData)
362 CheckParam ("username", username, 256);
364 DeleteUserTableMask deleteBitmask = DeleteUserTableMask.MembershipUsers;
366 if (deleteAllRelatedData)
368 DeleteUserTableMask.Profiles |
369 DeleteUserTableMask.UsersInRoles |
370 DeleteUserTableMask.WebPartStateUser;
372 using (DbConnection connection = CreateConnection ()) {
373 DbCommand command = factory.CreateCommand ();
374 command.Connection = connection;
375 command.CommandText = @"aspnet_Users_DeleteUser";
376 command.CommandType = CommandType.StoredProcedure;
378 AddParameter (command, "@ApplicationName", ApplicationName);
379 AddParameter (command, "@UserName", username);
380 AddParameter (command, "@TablesToDeleteFrom", (int) deleteBitmask);
381 AddParameter (command, "@NumTablesDeletedFrom", ParameterDirection.Output, 0);
382 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
384 command.ExecuteNonQuery ();
386 if (((int) command.Parameters ["@NumTablesDeletedFrom"].Value) == 0)
389 if (GetReturnValue (returnValue) == 0)
396 public virtual string GeneratePassword ()
398 return Membership.GeneratePassword (MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters);
401 public override MembershipUserCollection FindUsersByEmail (string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
403 CheckParam ("emailToMatch", emailToMatch, 256);
406 throw new ArgumentException ("pageIndex must be >= 0");
408 throw new ArgumentException ("pageSize must be >= 0");
409 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
410 throw new ArgumentException ("pageIndex and pageSize are too large");
412 using (DbConnection connection = CreateConnection ()) {
414 DbCommand command = factory.CreateCommand ();
415 command.Connection = connection;
416 command.CommandText = @"aspnet_Membership_FindUsersByEmail";
417 command.CommandType = CommandType.StoredProcedure;
419 AddParameter (command, "@PageIndex", pageIndex);
420 AddParameter (command, "@PageSize", pageSize);
421 AddParameter (command, "@EmailToMatch", emailToMatch);
422 AddParameter (command, "@ApplicationName", ApplicationName);
424 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
426 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
432 public override MembershipUserCollection FindUsersByName (string nameToMatch, int pageIndex, int pageSize, out int totalRecords)
434 CheckParam ("nameToMatch", nameToMatch, 256);
437 throw new ArgumentException ("pageIndex must be >= 0");
439 throw new ArgumentException ("pageSize must be >= 0");
440 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
441 throw new ArgumentException ("pageIndex and pageSize are too large");
443 using (DbConnection connection = CreateConnection ()) {
445 DbCommand command = factory.CreateCommand ();
446 command.Connection = connection;
447 command.CommandText = @"aspnet_Membership_FindUsersByName";
448 command.CommandType = CommandType.StoredProcedure;
450 AddParameter (command, "@PageIndex", pageIndex);
451 AddParameter (command, "@PageSize", pageSize);
452 AddParameter (command, "@UserNameToMatch", nameToMatch);
453 AddParameter (command, "@ApplicationName", ApplicationName);
455 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
457 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
463 public override MembershipUserCollection GetAllUsers (int pageIndex, int pageSize, out int totalRecords)
466 throw new ArgumentException ("pageIndex must be >= 0");
468 throw new ArgumentException ("pageSize must be >= 0");
469 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
470 throw new ArgumentException ("pageIndex and pageSize are too large");
472 using (DbConnection connection = CreateConnection ()) {
473 DbCommand command = factory.CreateCommand ();
474 command.Connection = connection;
475 command.CommandText = @"aspnet_Membership_GetAllUsers";
476 command.CommandType = CommandType.StoredProcedure;
478 AddParameter (command, "@ApplicationName", ApplicationName);
479 AddParameter (command, "@PageIndex", pageIndex);
480 AddParameter (command, "@PageSize", pageSize);
482 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
484 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
490 MembershipUserCollection BuildMembershipUserCollection (DbCommand command, int pageIndex, int pageSize, out int totalRecords)
492 DbDataReader reader = null;
494 MembershipUserCollection users = new MembershipUserCollection ();
495 reader = command.ExecuteReader ();
496 while (reader.Read ())
497 users.Add (GetUserFromReader (reader, null, null));
499 totalRecords = Convert.ToInt32 (command.Parameters ["@ReturnValue"].Value);
501 } catch (Exception) {
503 return null; /* should we let the exception through? */
512 public override int GetNumberOfUsersOnline ()
514 using (DbConnection connection = CreateConnection ()) {
515 DateTime now = DateTime.UtcNow;
517 DbCommand command = factory.CreateCommand ();
518 command.Connection = connection;
519 command.CommandText = @"aspnet_Membership_GetNumberOfUsersOnline";
520 command.CommandType = CommandType.StoredProcedure;
522 AddParameter (command, "@CurrentTimeUtc", now.ToString ());
523 AddParameter (command, "@ApplicationName", ApplicationName);
524 AddParameter (command, "@MinutesSinceLastInActive", userIsOnlineTimeWindow.Minutes);
525 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
527 command.ExecuteScalar ();
528 return GetReturnValue (returnValue);
532 public override string GetPassword (string username, string answer)
534 if (!EnablePasswordRetrieval)
535 throw new NotSupportedException ("this provider has not been configured to allow the retrieval of passwords");
537 CheckParam ("username", username, 256);
538 if (RequiresQuestionAndAnswer)
539 CheckParam ("answer", answer, 128);
541 PasswordInfo pi = GetPasswordInfo (username);
543 throw new ProviderException ("An error occurred while retrieving the password from the database");
545 string user_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
546 string password = null;
548 using (DbConnection connection = CreateConnection ()) {
549 DbCommand command = factory.CreateCommand ();
550 command.Connection = connection;
551 command.CommandText = @"aspnet_Membership_GetPassword";
552 command.CommandType = CommandType.StoredProcedure;
554 AddParameter (command, "@ApplicationName", ApplicationName);
555 AddParameter (command, "@UserName", username);
556 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
557 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
558 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
559 AddParameter (command, "@PasswordAnswer", user_answer);
560 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
562 DbDataReader reader = command.ExecuteReader ();
564 int returnValue = GetReturnValue (retValue);
565 if (returnValue == 3)
566 throw new MembershipPasswordException ("Password Answer is invalid");
567 if (returnValue == 99)
568 throw new MembershipPasswordException ("The user account is currently locked out");
570 if (reader.Read ()) {
571 password = reader.GetString (0);
575 if (pi.PasswordFormat == MembershipPasswordFormat.Clear)
577 else if (pi.PasswordFormat == MembershipPasswordFormat.Encrypted)
578 return DecodePassword (password, pi.PasswordFormat);
584 MembershipUser GetUserFromReader (DbDataReader reader, string username, object userId)
587 if (username == null)
591 username = reader.GetString (8);
593 return new MembershipUser (this.Name, /* XXX is this right? */
594 (username == null ? reader.GetString (0) : username), /* name */
595 (userId == null ? reader.GetGuid (8 + i) : userId), /* providerUserKey */
596 reader.IsDBNull (0 + i) ? null : reader.GetString (0 + i), /* email */
597 reader.IsDBNull (1 + i) ? null : reader.GetString (1 + i), /* passwordQuestion */
598 reader.IsDBNull (2 + i) ? null : reader.GetString (2 + i), /* comment */
599 reader.GetBoolean (3 + i), /* isApproved */
600 reader.GetBoolean (9 + i), /* isLockedOut */
601 reader.GetDateTime (4 + i).ToLocalTime (), /* creationDate */
602 reader.GetDateTime (5 + i).ToLocalTime (), /* lastLoginDate */
603 reader.GetDateTime (6 + i).ToLocalTime (), /* lastActivityDate */
604 reader.GetDateTime (7 + i).ToLocalTime (), /* lastPasswordChangedDate */
605 reader.GetDateTime (10 + i).ToLocalTime () /* lastLockoutDate */);
608 MembershipUser BuildMembershipUser (DbCommand query, string username, object userId)
611 using (DbConnection connection = CreateConnection ()) {
612 query.Connection = connection;
613 using (DbDataReader reader = query.ExecuteReader ()) {
617 return GetUserFromReader (reader, username, userId);
620 } catch (Exception) {
621 return null; /* should we let the exception through? */
624 query.Connection = null;
628 public override MembershipUser GetUser (string username, bool userIsOnline)
630 if (username == null)
631 throw new ArgumentNullException ("username");
633 if (username.Length == 0)
636 CheckParam ("username", username, 256);
638 DbCommand command = factory.CreateCommand ();
640 command.CommandText = @"aspnet_Membership_GetUserByName";
641 command.CommandType = CommandType.StoredProcedure;
643 AddParameter (command, "@UserName", username);
644 AddParameter (command, "@ApplicationName", ApplicationName);
645 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
646 AddParameter (command, "@UpdateLastActivity", userIsOnline);
648 MembershipUser u = BuildMembershipUser (command, username, null);
653 public override MembershipUser GetUser (object providerUserKey, bool userIsOnline)
655 DbCommand command = factory.CreateCommand ();
656 command.CommandText = @"aspnet_Membership_GetUserByUserId";
657 command.CommandType = CommandType.StoredProcedure;
659 AddParameter (command, "@UserId", providerUserKey);
660 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
661 AddParameter (command, "@UpdateLastActivity", userIsOnline);
663 MembershipUser u = BuildMembershipUser (command, string.Empty, providerUserKey);
667 public override string GetUserNameByEmail (string email)
669 CheckParam ("email", email, 256);
671 using (DbConnection connection = CreateConnection ()) {
673 DbCommand command = factory.CreateCommand ();
674 command.Connection = connection;
675 command.CommandText = @"aspnet_Membership_GetUserByEmail";
676 command.CommandType = CommandType.StoredProcedure;
678 AddParameter (command, "@ApplicationName", ApplicationName);
679 AddParameter (command, "@Email", email);
681 DbDataReader reader = command.ExecuteReader ();
684 rv = reader.GetString (0);
690 bool GetBoolConfigValue (NameValueCollection config, string name, bool def)
693 string val = config [name];
695 try { rv = Boolean.Parse (val); }
696 catch (Exception e) {
697 throw new ProviderException (String.Format ("{0} must be true or false", name), e);
703 int GetIntConfigValue (NameValueCollection config, string name, int def)
706 string val = config [name];
708 try { rv = Int32.Parse (val); }
709 catch (Exception e) {
710 throw new ProviderException (String.Format ("{0} must be an integer", name), e);
716 int GetEnumConfigValue (NameValueCollection config, string name, Type enumType, int def)
719 string val = config [name];
721 try { rv = (int) Enum.Parse (enumType, val); }
722 catch (Exception e) {
723 throw new ProviderException (String.Format ("{0} must be one of the following values: {1}", name, String.Join (",", Enum.GetNames (enumType))), e);
729 string GetStringConfigValue (NameValueCollection config, string name, string def)
732 string val = config [name];
738 void EmitValidatingPassword (string username, string password, bool isNewUser)
740 ValidatePasswordEventArgs args = new ValidatePasswordEventArgs (username, password, isNewUser);
741 OnValidatingPassword (args);
743 /* if we're canceled.. */
745 if (args.FailureInformation == null)
746 throw new ProviderException ("Password validation canceled");
748 throw args.FailureInformation;
752 public override void Initialize (string name, NameValueCollection config)
755 throw new ArgumentNullException ("config");
757 base.Initialize (name, config);
759 applicationName = GetStringConfigValue (config, "applicationName", "/");
760 enablePasswordReset = GetBoolConfigValue (config, "enablePasswordReset", true);
761 enablePasswordRetrieval = GetBoolConfigValue (config, "enablePasswordRetrieval", false);
762 requiresQuestionAndAnswer = GetBoolConfigValue (config, "requiresQuestionAndAnswer", true);
763 requiresUniqueEmail = GetBoolConfigValue (config, "requiresUniqueEmail", false);
764 passwordFormat = (MembershipPasswordFormat) GetEnumConfigValue (config, "passwordFormat", typeof (MembershipPasswordFormat),
765 (int) MembershipPasswordFormat.Hashed);
766 maxInvalidPasswordAttempts = GetIntConfigValue (config, "maxInvalidPasswordAttempts", 5);
767 minRequiredPasswordLength = GetIntConfigValue (config, "minRequiredPasswordLength", 7);
768 minRequiredNonAlphanumericCharacters = GetIntConfigValue (config, "minRequiredNonalphanumericCharacters", 1);
769 passwordAttemptWindow = GetIntConfigValue (config, "passwordAttemptWindow", 10);
770 passwordStrengthRegularExpression = GetStringConfigValue (config, "passwordStrengthRegularExpression", "");
772 MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
774 userIsOnlineTimeWindow = section.UserIsOnlineTimeWindow;
776 /* we can't support password retrieval with hashed passwords */
777 if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval)
778 throw new ProviderException ("password retrieval cannot be used with hashed passwords");
780 string connectionStringName = config ["connectionStringName"];
782 if (applicationName.Length > 256)
783 throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
784 if (connectionStringName == null || connectionStringName.Length == 0)
785 throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
787 connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];
788 factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ?
789 System.Data.SqlClient.SqlClientFactory.Instance :
790 ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName);
793 public override string ResetPassword (string username, string answer)
795 if (!EnablePasswordReset)
796 throw new NotSupportedException ("this provider has not been configured to allow the resetting of passwords");
798 CheckParam ("username", username, 256);
800 if (RequiresQuestionAndAnswer)
801 CheckParam ("answer", answer, 128);
803 using (DbConnection connection = CreateConnection ()) {
805 PasswordInfo pi = GetPasswordInfo (username);
807 throw new ProviderException (username + "is not found in the membership database");
809 string newPassword = GeneratePassword ();
810 EmitValidatingPassword (username, newPassword, false);
812 string db_password = EncodePassword (newPassword, pi.PasswordFormat, pi.PasswordSalt);
813 string db_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
815 DbCommand command = factory.CreateCommand ();
816 command.Connection = connection;
817 command.CommandText = @"aspnet_Membership_ResetPassword";
818 command.CommandType = CommandType.StoredProcedure;
820 AddParameter (command, "@ApplicationName", ApplicationName);
821 AddParameter (command, "@UserName", username);
822 AddParameter (command, "@NewPassword", db_password);
823 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
824 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
825 AddParameter (command, "@PasswordSalt", pi.PasswordSalt);
826 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
827 AddParameter (command, "@PasswordFormat", (int) pi.PasswordFormat);
828 AddParameter (command, "@PasswordAnswer", db_answer);
829 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
831 command.ExecuteNonQuery ();
833 int returnValue = GetReturnValue (retValue);
835 if (returnValue == 0)
837 else if (returnValue == 3)
838 throw new MembershipPasswordException ("Password Answer is invalid");
839 else if (returnValue == 99)
840 throw new MembershipPasswordException ("The user account is currently locked out");
842 throw new ProviderException ("Failed to reset password");
846 public override void UpdateUser (MembershipUser user)
849 throw new ArgumentNullException ("user");
851 if (user.UserName == null)
852 throw new ArgumentNullException ("user.UserName");
854 if (RequiresUniqueEmail && user.Email == null)
855 throw new ArgumentNullException ("user.Email");
857 CheckParam ("user.UserName", user.UserName, 256);
859 if (user.Email.Length > 256 || (RequiresUniqueEmail && user.Email.Length == 0))
860 throw new ArgumentException ("invalid format for user.Email");
862 using (DbConnection connection = CreateConnection ()) {
865 DbCommand command = factory.CreateCommand ();
866 command.Connection = connection;
867 command.CommandText = @"aspnet_Membership_UpdateUser";
868 command.CommandType = CommandType.StoredProcedure;
870 AddParameter (command, "@ApplicationName", ApplicationName);
871 AddParameter (command, "@UserName", user.UserName);
872 AddParameter (command, "@Email", user.Email == null ? (object) DBNull.Value : (object) user.Email);
873 AddParameter (command, "@Comment", user.Comment == null ? (object) DBNull.Value : (object) user.Comment);
874 AddParameter (command, "@IsApproved", user.IsApproved);
875 AddParameter (command, "@LastLoginDate", DateTime.UtcNow);
876 AddParameter (command, "@LastActivityDate", DateTime.UtcNow);
877 AddParameter (command, "@UniqueEmail", RequiresUniqueEmail);
878 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
879 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
881 command.ExecuteNonQuery ();
883 returnValue = GetReturnValue (retValue);
885 if (returnValue == 1)
886 throw new ProviderException ("The UserName property of user was not found in the database.");
887 if (returnValue == 7)
888 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.");
889 if (returnValue != 0)
890 throw new ProviderException ("Failed to update user");
894 public override bool ValidateUser (string username, string password)
896 if (username.Length == 0)
899 CheckParam ("username", username, 256);
900 EmitValidatingPassword (username, password, false);
902 PasswordInfo pi = ValidateUsingPassword (username, password);
904 pi.LastLoginDate = DateTime.UtcNow;
905 UpdateUserInfo (username, pi, true, true);
912 public override bool UnlockUser (string username)
914 CheckParam ("username", username, 256);
916 using (DbConnection connection = CreateConnection ()) {
918 DbCommand command = factory.CreateCommand ();
919 command.Connection = connection;
920 command.CommandText = @"aspnet_Membership_UnlockUser"; ;
921 command.CommandType = CommandType.StoredProcedure;
923 AddParameter (command, "@ApplicationName", ApplicationName);
924 AddParameter (command, "@UserName", username);
925 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
927 command.ExecuteNonQuery ();
928 if (GetReturnValue (returnValue) != 0)
931 catch (Exception e) {
932 throw new ProviderException ("Failed to unlock user", e);
938 void UpdateUserInfo (string username, PasswordInfo pi, bool isPasswordCorrect, bool updateLoginActivity)
940 CheckParam ("username", username, 256);
942 using (DbConnection connection = CreateConnection ()) {
944 DbCommand command = factory.CreateCommand ();
945 command.Connection = connection;
946 command.CommandText = @"aspnet_Membership_UpdateUserInfo"; ;
947 command.CommandType = CommandType.StoredProcedure;
949 AddParameter (command, "@ApplicationName", ApplicationName);
950 AddParameter (command, "@UserName", username);
951 AddParameter (command, "@IsPasswordCorrect", isPasswordCorrect);
952 AddParameter (command, "@UpdateLastLoginActivityDate", updateLoginActivity);
953 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
954 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
955 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
956 AddParameter (command, "@LastLoginDate", pi.LastLoginDate);
957 AddParameter (command, "@LastActivityDate", pi.LastActivityDate);
958 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
960 command.ExecuteNonQuery ();
962 int returnValue = GetReturnValue (retValue);
963 if (returnValue != 0)
966 catch (Exception e) {
967 throw new ProviderException ("Failed to update Membership table", e);
973 PasswordInfo ValidateUsingPassword (string username, string password)
975 MembershipUser user = GetUser (username, true);
979 if (!user.IsApproved || user.IsLockedOut)
982 PasswordInfo pi = GetPasswordInfo (username);
987 /* do the actual validation */
988 string user_password = EncodePassword (password, pi.PasswordFormat, pi.PasswordSalt);
990 if (user_password != pi.Password) {
991 UpdateUserInfo (username, pi, false, false);
998 PasswordInfo GetPasswordInfo (string username)
1000 using (DbConnection connection = CreateConnection ()) {
1001 DbCommand command = factory.CreateCommand ();
1002 command.Connection = connection;
1003 command.CommandType = CommandType.StoredProcedure;
1004 command.CommandText = @"aspnet_Membership_GetPasswordWithFormat";
1006 AddParameter (command, "@ApplicationName", ApplicationName);
1007 AddParameter (command, "@UserName", username);
1008 AddParameter (command, "@UpdateLastLoginActivityDate", false);
1009 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
1011 AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
1013 DbDataReader reader = command.ExecuteReader ();
1014 if (!reader.Read ())
1017 PasswordInfo pi = new PasswordInfo (
1018 reader.GetString (0),
1019 (MembershipPasswordFormat) reader.GetInt32 (1),
1020 reader.GetString (2),
1021 reader.GetInt32 (3),
1022 reader.GetInt32 (4),
1023 reader.GetBoolean (5),
1024 reader.GetDateTime (6),
1025 reader.GetDateTime (7));
1031 string EncodePassword (string password, MembershipPasswordFormat passwordFormat, string salt)
1033 byte [] password_bytes;
1036 switch (passwordFormat) {
1037 case MembershipPasswordFormat.Clear:
1039 case MembershipPasswordFormat.Hashed:
1040 password_bytes = Encoding.Unicode.GetBytes (password);
1041 salt_bytes = Convert.FromBase64String (salt);
1043 byte [] hashBytes = new byte [salt_bytes.Length + password_bytes.Length];
1045 Buffer.BlockCopy (salt_bytes, 0, hashBytes, 0, salt_bytes.Length);
1046 Buffer.BlockCopy (password_bytes, 0, hashBytes, salt_bytes.Length, password_bytes.Length);
1048 MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
1049 string alg_type = section.HashAlgorithmType;
1050 if (alg_type.Length == 0) {
1051 alg_type = MachineKeySection.Config.Validation.ToString ();
1052 // support new (4.0) custom algorithms
1053 if (alg_type.StartsWith ("alg:"))
1054 alg_type = alg_type.Substring (4);
1056 using (HashAlgorithm hash = HashAlgorithm.Create (alg_type)) {
1057 // for compatibility (with 2.0) we'll allow MD5 and SHA1 not to map to HMACMD5 and HMACSHA1
1058 // but that won't work with new (4.0) algorithms, like HMACSHA256|384|512 or custom, won't work without using the key
1059 KeyedHashAlgorithm kha = (hash as KeyedHashAlgorithm);
1061 kha.Key = MachineKeySection.Config.GetValidationKey ();
1062 hash.TransformFinalBlock (hashBytes, 0, hashBytes.Length);
1063 return Convert.ToBase64String (hash.Hash);
1065 case MembershipPasswordFormat.Encrypted:
1066 password_bytes = Encoding.Unicode.GetBytes (password);
1067 salt_bytes = Convert.FromBase64String (salt);
1069 byte [] buf = new byte [password_bytes.Length + salt_bytes.Length];
1071 Array.Copy (salt_bytes, 0, buf, 0, salt_bytes.Length);
1072 Array.Copy (password_bytes, 0, buf, salt_bytes.Length, password_bytes.Length);
1074 return Convert.ToBase64String (EncryptPassword (buf));
1081 string DecodePassword (string password, MembershipPasswordFormat passwordFormat)
1083 switch (passwordFormat) {
1084 case MembershipPasswordFormat.Clear:
1086 case MembershipPasswordFormat.Hashed:
1087 throw new ProviderException ("Hashed passwords cannot be decoded.");
1088 case MembershipPasswordFormat.Encrypted:
1089 return Encoding.Unicode.GetString (DecryptPassword (Convert.FromBase64String (password)));
1096 public override string ApplicationName
1098 get { return applicationName; }
1099 set { applicationName = value; }
1102 public override bool EnablePasswordReset
1104 get { return enablePasswordReset; }
1107 public override bool EnablePasswordRetrieval
1109 get { return enablePasswordRetrieval; }
1112 public override MembershipPasswordFormat PasswordFormat
1114 get { return passwordFormat; }
1117 public override bool RequiresQuestionAndAnswer
1119 get { return requiresQuestionAndAnswer; }
1122 public override bool RequiresUniqueEmail
1124 get { return requiresUniqueEmail; }
1127 public override int MaxInvalidPasswordAttempts
1129 get { return maxInvalidPasswordAttempts; }
1132 public override int MinRequiredNonAlphanumericCharacters
1134 get { return minRequiredNonAlphanumericCharacters; }
1137 public override int MinRequiredPasswordLength
1139 get { return minRequiredPasswordLength; }
1142 public override int PasswordAttemptWindow
1144 get { return passwordAttemptWindow; }
1147 public override string PasswordStrengthRegularExpression
1149 get { return passwordStrengthRegularExpression; }
1153 enum DeleteUserTableMask
1155 MembershipUsers = 1,
1158 WebPartStateUser = 8
1161 sealed class PasswordInfo
1164 MembershipPasswordFormat _passwordFormat;
1165 string _passwordSalt;
1166 int _failedPasswordAttemptCount;
1167 int _failedPasswordAnswerAttemptCount;
1169 DateTime _lastLoginDate;
1170 DateTime _lastActivityDate;
1172 internal PasswordInfo (
1174 MembershipPasswordFormat passwordFormat,
1175 string passwordSalt,
1176 int failedPasswordAttemptCount,
1177 int failedPasswordAnswerAttemptCount,
1179 DateTime lastLoginDate,
1180 DateTime lastActivityDate)
1182 _password = password;
1183 _passwordFormat = passwordFormat;
1184 _passwordSalt = passwordSalt;
1185 _failedPasswordAttemptCount = failedPasswordAttemptCount;
1186 _failedPasswordAnswerAttemptCount = failedPasswordAnswerAttemptCount;
1187 _isApproved = isApproved;
1188 _lastLoginDate = lastLoginDate;
1189 _lastActivityDate = lastActivityDate;
1192 public string Password
1194 get { return _password; }
1195 set { _password = value; }
1197 public MembershipPasswordFormat PasswordFormat
1199 get { return _passwordFormat; }
1200 set { _passwordFormat = value; }
1202 public string PasswordSalt
1204 get { return _passwordSalt; }
1205 set { _passwordSalt = value; }
1207 public int FailedPasswordAttemptCount
1209 get { return _failedPasswordAttemptCount; }
1210 set { _failedPasswordAttemptCount = value; }
1212 public int FailedPasswordAnswerAttemptCount
1214 get { return _failedPasswordAnswerAttemptCount; }
1215 set { _failedPasswordAnswerAttemptCount = value; }
1217 public bool IsApproved
1219 get { return _isApproved; }
1220 set { _isApproved = value; }
1222 public DateTime LastLoginDate
1224 get { return _lastLoginDate; }
1225 set { _lastLoginDate = value; }
1227 public DateTime LastActivityDate
1229 get { return _lastActivityDate; }
1230 set { _lastActivityDate = value; }