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;
62 bool schemaIsOk = false;
64 DbConnection CreateConnection ()
66 if (!schemaIsOk && !(schemaIsOk = AspNetDBSchemaChecker.CheckMembershipSchemaVersion (factory, connectionString.ConnectionString, "membership", "1")))
67 throw new ProviderException ("Incorrect ASP.NET DB Schema Version.");
69 DbConnection connection;
71 if (connectionString == null)
72 throw new ProviderException ("Connection string for the SQL Membership Provider has not been provided.");
75 connection = factory.CreateConnection ();
76 connection.ConnectionString = connectionString.ConnectionString;
78 } catch (Exception ex) {
79 throw new ProviderException ("Unable to open SQL connection for the SQL Membership Provider.",
86 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
88 return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
91 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
93 DbParameter dbp = command.CreateParameter ();
94 dbp.ParameterName = parameterName;
95 dbp.Value = parameterValue;
96 dbp.Direction = direction;
97 command.Parameters.Add (dbp);
101 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, DbType type, object parameterValue)
103 DbParameter dbp = command.CreateParameter ();
104 dbp.ParameterName = parameterName;
105 dbp.Value = parameterValue;
106 dbp.Direction = direction;
108 command.Parameters.Add (dbp);
112 static int GetReturnValue (DbParameter returnValue)
114 object value = returnValue.Value;
115 return value is int ? (int) value : -1;
118 void CheckParam (string pName, string p, int length)
121 throw new ArgumentNullException (pName);
122 if (p.Length == 0 || p.Length > length || p.IndexOf (",") != -1)
123 throw new ArgumentException (String.Format ("invalid format for {0}", pName));
126 public override bool ChangePassword (string username, string oldPwd, string newPwd)
128 if (username != null) username = username.Trim ();
129 if (oldPwd != null) oldPwd = oldPwd.Trim ();
130 if (newPwd != null) newPwd = newPwd.Trim ();
132 CheckParam ("username", username, 256);
133 CheckParam ("oldPwd", oldPwd, 128);
134 CheckParam ("newPwd", newPwd, 128);
136 if (!CheckPassword (newPwd))
137 throw new ArgumentException (string.Format (
138 "New Password invalid. New Password length minimum: {0}. Non-alphanumeric characters required: {1}.",
139 MinRequiredPasswordLength,
140 MinRequiredNonAlphanumericCharacters));
142 using (DbConnection connection = CreateConnection ()) {
143 PasswordInfo pi = ValidateUsingPassword (username, oldPwd);
146 EmitValidatingPassword (username, newPwd, false);
147 string db_password = EncodePassword (newPwd, pi.PasswordFormat, pi.PasswordSalt);
149 DbCommand command = factory.CreateCommand ();
150 command.Connection = connection;
151 command.CommandText = @"aspnet_Membership_SetPassword";
152 command.CommandType = CommandType.StoredProcedure;
154 AddParameter (command, "@ApplicationName", ApplicationName);
155 AddParameter (command, "@UserName", username);
156 AddParameter (command, "@NewPassword", db_password);
157 AddParameter (command, "@PasswordFormat", (int) pi.PasswordFormat);
158 AddParameter (command, "@PasswordSalt", pi.PasswordSalt);
159 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
160 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
162 command.ExecuteNonQuery ();
164 if (GetReturnValue (returnValue) != 0)
173 public override bool ChangePasswordQuestionAndAnswer (string username, string password, string newPwdQuestion, string newPwdAnswer)
175 if (username != null) username = username.Trim ();
176 if (newPwdQuestion != null) newPwdQuestion = newPwdQuestion.Trim ();
177 if (newPwdAnswer != null) newPwdAnswer = newPwdAnswer.Trim ();
179 CheckParam ("username", username, 256);
180 if (RequiresQuestionAndAnswer)
181 CheckParam ("newPwdQuestion", newPwdQuestion, 128);
182 if (RequiresQuestionAndAnswer)
183 CheckParam ("newPwdAnswer", newPwdAnswer, 128);
185 using (DbConnection connection = CreateConnection ()) {
186 PasswordInfo pi = ValidateUsingPassword (username, password);
189 string db_passwordAnswer = EncodePassword (newPwdAnswer, pi.PasswordFormat, pi.PasswordSalt);
191 DbCommand command = factory.CreateCommand ();
192 command.Connection = connection;
193 command.CommandType = CommandType.StoredProcedure;
194 command.CommandText = @"aspnet_Membership_ChangePasswordQuestionAndAnswer";
196 AddParameter (command, "@ApplicationName", ApplicationName);
197 AddParameter (command, "@UserName", username);
198 AddParameter (command, "@NewPasswordQuestion", newPwdQuestion);
199 AddParameter (command, "@NewPasswordAnswer", db_passwordAnswer);
200 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
202 command.ExecuteNonQuery ();
204 if (GetReturnValue (returnValue) != 0)
213 public override MembershipUser CreateUser (string username,
219 object providerUserKey,
220 out MembershipCreateStatus status)
222 if (username != null) username = username.Trim ();
223 if (password != null) password = password.Trim ();
224 if (email != null) email = email.Trim ();
225 if (pwdQuestion != null) pwdQuestion = pwdQuestion.Trim ();
226 if (pwdAnswer != null) pwdAnswer = pwdAnswer.Trim ();
228 /* some initial validation */
229 if (username == null || username.Length == 0 || username.Length > 256 || username.IndexOf (",") != -1) {
230 status = MembershipCreateStatus.InvalidUserName;
233 if (password == null || password.Length == 0 || password.Length > 128) {
234 status = MembershipCreateStatus.InvalidPassword;
238 if (!CheckPassword (password)) {
239 status = MembershipCreateStatus.InvalidPassword;
242 EmitValidatingPassword (username, password, true);
244 if (RequiresUniqueEmail && (email == null || email.Length == 0)) {
245 status = MembershipCreateStatus.InvalidEmail;
248 if (RequiresQuestionAndAnswer &&
249 (pwdQuestion == null ||
250 pwdQuestion.Length == 0 || pwdQuestion.Length > 256)) {
251 status = MembershipCreateStatus.InvalidQuestion;
254 if (RequiresQuestionAndAnswer &&
255 (pwdAnswer == null ||
256 pwdAnswer.Length == 0 || pwdAnswer.Length > 128)) {
257 status = MembershipCreateStatus.InvalidAnswer;
260 if (providerUserKey != null && !(providerUserKey is Guid)) {
261 status = MembershipCreateStatus.InvalidProviderUserKey;
265 if (providerUserKey == null)
266 providerUserKey = Guid.NewGuid();
268 /* encode our password/answer using the
269 * "passwordFormat" configuration option */
270 string passwordSalt = "";
272 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
273 byte [] salt = new byte [SALT_BYTES];
275 passwordSalt = Convert.ToBase64String (salt);
277 password = EncodePassword (password, PasswordFormat, passwordSalt);
278 if (RequiresQuestionAndAnswer)
279 pwdAnswer = EncodePassword (pwdAnswer, PasswordFormat, passwordSalt);
281 /* make sure the hashed/encrypted password and
282 * answer are still under 128 characters. */
283 if (password.Length > 128) {
284 status = MembershipCreateStatus.InvalidPassword;
288 if (RequiresQuestionAndAnswer) {
289 if (pwdAnswer.Length > 128) {
290 status = MembershipCreateStatus.InvalidAnswer;
294 status = MembershipCreateStatus.Success;
296 using (DbConnection connection = CreateConnection ()) {
299 DbCommand command = factory.CreateCommand ();
300 command.Connection = connection;
301 command.CommandText = @"aspnet_Membership_CreateUser";
302 command.CommandType = CommandType.StoredProcedure;
304 DateTime Now = DateTime.UtcNow;
306 AddParameter (command, "@ApplicationName", ApplicationName);
307 AddParameter (command, "@UserName", username);
308 AddParameter (command, "@Password", password);
309 AddParameter (command, "@PasswordSalt", passwordSalt);
310 AddParameter (command, "@Email", email);
311 AddParameter (command, "@PasswordQuestion", pwdQuestion);
312 AddParameter (command, "@PasswordAnswer", pwdAnswer);
313 AddParameter (command, "@IsApproved", isApproved);
314 AddParameter (command, "@CurrentTimeUtc", Now);
315 AddParameter (command, "@CreateDate", Now);
316 AddParameter (command, "@UniqueEmail", RequiresUniqueEmail);
317 AddParameter (command, "@PasswordFormat", (int) PasswordFormat);
318 AddParameter (command, "@UserId", ParameterDirection.InputOutput, providerUserKey);
319 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
321 command.ExecuteNonQuery ();
323 int st = GetReturnValue (returnValue);
326 return GetUser (username, false);
328 status = MembershipCreateStatus.DuplicateUserName;
330 status = MembershipCreateStatus.DuplicateEmail;
332 status = MembershipCreateStatus.DuplicateProviderUserKey;
334 status = MembershipCreateStatus.ProviderError;
339 status = MembershipCreateStatus.ProviderError;
345 private bool CheckPassword (string password)
347 if (password.Length < MinRequiredPasswordLength)
350 if (MinRequiredNonAlphanumericCharacters > 0) {
351 int nonAlphanumeric = 0;
352 for (int i = 0; i < password.Length; i++) {
353 if (!Char.IsLetterOrDigit (password [i]))
356 return nonAlphanumeric >= MinRequiredNonAlphanumericCharacters;
361 public override bool DeleteUser (string username, bool deleteAllRelatedData)
363 CheckParam ("username", username, 256);
365 DeleteUserTableMask deleteBitmask = DeleteUserTableMask.MembershipUsers;
367 if (deleteAllRelatedData)
369 DeleteUserTableMask.Profiles |
370 DeleteUserTableMask.UsersInRoles |
371 DeleteUserTableMask.WebPartStateUser;
373 using (DbConnection connection = CreateConnection ()) {
374 DbCommand command = factory.CreateCommand ();
375 command.Connection = connection;
376 command.CommandText = @"aspnet_Users_DeleteUser";
377 command.CommandType = CommandType.StoredProcedure;
379 AddParameter (command, "@ApplicationName", ApplicationName);
380 AddParameter (command, "@UserName", username);
381 AddParameter (command, "@TablesToDeleteFrom", (int) deleteBitmask);
382 AddParameter (command, "@NumTablesDeletedFrom", ParameterDirection.Output, 0);
383 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
385 command.ExecuteNonQuery ();
387 if (((int) command.Parameters ["@NumTablesDeletedFrom"].Value) == 0)
390 if (GetReturnValue (returnValue) == 0)
397 public virtual string GeneratePassword ()
399 return Membership.GeneratePassword (MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters);
402 public override MembershipUserCollection FindUsersByEmail (string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
404 CheckParam ("emailToMatch", emailToMatch, 256);
407 throw new ArgumentException ("pageIndex must be >= 0");
409 throw new ArgumentException ("pageSize must be >= 0");
410 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
411 throw new ArgumentException ("pageIndex and pageSize are too large");
413 using (DbConnection connection = CreateConnection ()) {
415 DbCommand command = factory.CreateCommand ();
416 command.Connection = connection;
417 command.CommandText = @"aspnet_Membership_FindUsersByEmail";
418 command.CommandType = CommandType.StoredProcedure;
420 AddParameter (command, "@PageIndex", pageIndex);
421 AddParameter (command, "@PageSize", pageSize);
422 AddParameter (command, "@EmailToMatch", emailToMatch);
423 AddParameter (command, "@ApplicationName", ApplicationName);
425 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
427 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
433 public override MembershipUserCollection FindUsersByName (string nameToMatch, int pageIndex, int pageSize, out int totalRecords)
435 CheckParam ("nameToMatch", nameToMatch, 256);
438 throw new ArgumentException ("pageIndex must be >= 0");
440 throw new ArgumentException ("pageSize must be >= 0");
441 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
442 throw new ArgumentException ("pageIndex and pageSize are too large");
444 using (DbConnection connection = CreateConnection ()) {
446 DbCommand command = factory.CreateCommand ();
447 command.Connection = connection;
448 command.CommandText = @"aspnet_Membership_FindUsersByName";
449 command.CommandType = CommandType.StoredProcedure;
451 AddParameter (command, "@PageIndex", pageIndex);
452 AddParameter (command, "@PageSize", pageSize);
453 AddParameter (command, "@UserNameToMatch", nameToMatch);
454 AddParameter (command, "@ApplicationName", ApplicationName);
456 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
458 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
464 public override MembershipUserCollection GetAllUsers (int pageIndex, int pageSize, out int totalRecords)
467 throw new ArgumentException ("pageIndex must be >= 0");
469 throw new ArgumentException ("pageSize must be >= 0");
470 if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
471 throw new ArgumentException ("pageIndex and pageSize are too large");
473 using (DbConnection connection = CreateConnection ()) {
474 DbCommand command = factory.CreateCommand ();
475 command.Connection = connection;
476 command.CommandText = @"aspnet_Membership_GetAllUsers";
477 command.CommandType = CommandType.StoredProcedure;
479 AddParameter (command, "@ApplicationName", ApplicationName);
480 AddParameter (command, "@PageIndex", pageIndex);
481 AddParameter (command, "@PageSize", pageSize);
483 AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null);
485 MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords);
491 MembershipUserCollection BuildMembershipUserCollection (DbCommand command, int pageIndex, int pageSize, out int totalRecords)
493 DbDataReader reader = null;
495 MembershipUserCollection users = new MembershipUserCollection ();
496 reader = command.ExecuteReader ();
497 while (reader.Read ())
498 users.Add (GetUserFromReader (reader, null, null));
500 totalRecords = Convert.ToInt32 (command.Parameters ["@ReturnValue"].Value);
502 } catch (Exception) {
504 return null; /* should we let the exception through? */
513 public override int GetNumberOfUsersOnline ()
515 using (DbConnection connection = CreateConnection ()) {
516 DateTime now = DateTime.UtcNow;
518 DbCommand command = factory.CreateCommand ();
519 command.Connection = connection;
520 command.CommandText = @"aspnet_Membership_GetNumberOfUsersOnline";
521 command.CommandType = CommandType.StoredProcedure;
523 AddParameter (command, "@CurrentTimeUtc", now.ToString ());
524 AddParameter (command, "@ApplicationName", ApplicationName);
525 AddParameter (command, "@MinutesSinceLastInActive", userIsOnlineTimeWindow.Minutes);
526 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
528 command.ExecuteScalar ();
529 return GetReturnValue (returnValue);
533 public override string GetPassword (string username, string answer)
535 if (!EnablePasswordRetrieval)
536 throw new NotSupportedException ("this provider has not been configured to allow the retrieval of passwords");
538 CheckParam ("username", username, 256);
539 if (RequiresQuestionAndAnswer)
540 CheckParam ("answer", answer, 128);
542 PasswordInfo pi = GetPasswordInfo (username);
544 throw new ProviderException ("An error occurred while retrieving the password from the database");
546 string user_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
547 string password = null;
549 using (DbConnection connection = CreateConnection ()) {
550 DbCommand command = factory.CreateCommand ();
551 command.Connection = connection;
552 command.CommandText = @"aspnet_Membership_GetPassword";
553 command.CommandType = CommandType.StoredProcedure;
555 AddParameter (command, "@ApplicationName", ApplicationName);
556 AddParameter (command, "@UserName", username);
557 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
558 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
559 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
560 AddParameter (command, "@PasswordAnswer", user_answer);
561 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
563 DbDataReader reader = command.ExecuteReader ();
565 int returnValue = GetReturnValue (retValue);
566 if (returnValue == 3)
567 throw new MembershipPasswordException ("Password Answer is invalid");
568 if (returnValue == 99)
569 throw new MembershipPasswordException ("The user account is currently locked out");
571 if (reader.Read ()) {
572 password = reader.GetString (0);
576 if (pi.PasswordFormat == MembershipPasswordFormat.Clear)
578 else if (pi.PasswordFormat == MembershipPasswordFormat.Encrypted)
579 return DecodePassword (password, pi.PasswordFormat);
585 MembershipUser GetUserFromReader (DbDataReader reader, string username, object userId)
588 if (username == null)
592 username = reader.GetString (8);
594 return new MembershipUser (this.Name, /* XXX is this right? */
595 (username == null ? reader.GetString (0) : username), /* name */
596 (userId == null ? reader.GetGuid (8 + i) : userId), /* providerUserKey */
597 reader.IsDBNull (0 + i) ? null : reader.GetString (0 + i), /* email */
598 reader.IsDBNull (1 + i) ? null : reader.GetString (1 + i), /* passwordQuestion */
599 reader.IsDBNull (2 + i) ? null : reader.GetString (2 + i), /* comment */
600 reader.GetBoolean (3 + i), /* isApproved */
601 reader.GetBoolean (9 + i), /* isLockedOut */
602 reader.GetDateTime (4 + i).ToLocalTime (), /* creationDate */
603 reader.GetDateTime (5 + i).ToLocalTime (), /* lastLoginDate */
604 reader.GetDateTime (6 + i).ToLocalTime (), /* lastActivityDate */
605 reader.GetDateTime (7 + i).ToLocalTime (), /* lastPasswordChangedDate */
606 reader.GetDateTime (10 + i).ToLocalTime () /* lastLockoutDate */);
609 MembershipUser BuildMembershipUser (DbCommand query, string username, object userId)
612 using (DbConnection connection = CreateConnection ()) {
613 query.Connection = connection;
614 using (DbDataReader reader = query.ExecuteReader ()) {
618 return GetUserFromReader (reader, username, userId);
621 } catch (Exception) {
622 return null; /* should we let the exception through? */
625 query.Connection = null;
629 public override MembershipUser GetUser (string username, bool userIsOnline)
631 if (username == null)
632 throw new ArgumentNullException ("username");
634 if (username.Length == 0)
637 CheckParam ("username", username, 256);
639 DbCommand command = factory.CreateCommand ();
641 command.CommandText = @"aspnet_Membership_GetUserByName";
642 command.CommandType = CommandType.StoredProcedure;
644 AddParameter (command, "@UserName", username);
645 AddParameter (command, "@ApplicationName", ApplicationName);
646 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
647 AddParameter (command, "@UpdateLastActivity", userIsOnline);
649 MembershipUser u = BuildMembershipUser (command, username, null);
654 public override MembershipUser GetUser (object providerUserKey, bool userIsOnline)
656 DbCommand command = factory.CreateCommand ();
657 command.CommandText = @"aspnet_Membership_GetUserByUserId";
658 command.CommandType = CommandType.StoredProcedure;
660 AddParameter (command, "@UserId", providerUserKey);
661 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
662 AddParameter (command, "@UpdateLastActivity", userIsOnline);
664 MembershipUser u = BuildMembershipUser (command, string.Empty, providerUserKey);
668 public override string GetUserNameByEmail (string email)
670 CheckParam ("email", email, 256);
672 using (DbConnection connection = CreateConnection ()) {
674 DbCommand command = factory.CreateCommand ();
675 command.Connection = connection;
676 command.CommandText = @"aspnet_Membership_GetUserByEmail";
677 command.CommandType = CommandType.StoredProcedure;
679 AddParameter (command, "@ApplicationName", ApplicationName);
680 AddParameter (command, "@Email", email);
682 DbDataReader reader = command.ExecuteReader ();
685 rv = reader.GetString (0);
691 bool GetBoolConfigValue (NameValueCollection config, string name, bool def)
694 string val = config [name];
696 try { rv = Boolean.Parse (val); }
697 catch (Exception e) {
698 throw new ProviderException (String.Format ("{0} must be true or false", name), e);
704 int GetIntConfigValue (NameValueCollection config, string name, int def)
707 string val = config [name];
709 try { rv = Int32.Parse (val); }
710 catch (Exception e) {
711 throw new ProviderException (String.Format ("{0} must be an integer", name), e);
717 int GetEnumConfigValue (NameValueCollection config, string name, Type enumType, int def)
720 string val = config [name];
722 try { rv = (int) Enum.Parse (enumType, val); }
723 catch (Exception e) {
724 throw new ProviderException (String.Format ("{0} must be one of the following values: {1}", name, String.Join (",", Enum.GetNames (enumType))), e);
730 string GetStringConfigValue (NameValueCollection config, string name, string def)
733 string val = config [name];
739 void EmitValidatingPassword (string username, string password, bool isNewUser)
741 ValidatePasswordEventArgs args = new ValidatePasswordEventArgs (username, password, isNewUser);
742 OnValidatingPassword (args);
744 /* if we're canceled.. */
746 if (args.FailureInformation == null)
747 throw new ProviderException ("Password validation canceled");
749 throw args.FailureInformation;
753 public override void Initialize (string name, NameValueCollection config)
756 throw new ArgumentNullException ("config");
758 base.Initialize (name, config);
760 applicationName = GetStringConfigValue (config, "applicationName", "/");
761 enablePasswordReset = GetBoolConfigValue (config, "enablePasswordReset", true);
762 enablePasswordRetrieval = GetBoolConfigValue (config, "enablePasswordRetrieval", false);
763 requiresQuestionAndAnswer = GetBoolConfigValue (config, "requiresQuestionAndAnswer", true);
764 requiresUniqueEmail = GetBoolConfigValue (config, "requiresUniqueEmail", false);
765 passwordFormat = (MembershipPasswordFormat) GetEnumConfigValue (config, "passwordFormat", typeof (MembershipPasswordFormat),
766 (int) MembershipPasswordFormat.Hashed);
767 maxInvalidPasswordAttempts = GetIntConfigValue (config, "maxInvalidPasswordAttempts", 5);
768 minRequiredPasswordLength = GetIntConfigValue (config, "minRequiredPasswordLength", 7);
769 minRequiredNonAlphanumericCharacters = GetIntConfigValue (config, "minRequiredNonalphanumericCharacters", 1);
770 passwordAttemptWindow = GetIntConfigValue (config, "passwordAttemptWindow", 10);
771 passwordStrengthRegularExpression = GetStringConfigValue (config, "passwordStrengthRegularExpression", "");
773 MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
775 userIsOnlineTimeWindow = section.UserIsOnlineTimeWindow;
777 /* we can't support password retrieval with hashed passwords */
778 if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval)
779 throw new ProviderException ("password retrieval cannot be used with hashed passwords");
781 string connectionStringName = config ["connectionStringName"];
783 if (applicationName.Length > 256)
784 throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
785 if (connectionStringName == null || connectionStringName.Length == 0)
786 throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
788 connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];
789 factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ?
790 System.Data.SqlClient.SqlClientFactory.Instance :
791 ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName);
794 public override string ResetPassword (string username, string answer)
796 if (!EnablePasswordReset)
797 throw new NotSupportedException ("this provider has not been configured to allow the resetting of passwords");
799 CheckParam ("username", username, 256);
801 if (RequiresQuestionAndAnswer)
802 CheckParam ("answer", answer, 128);
804 using (DbConnection connection = CreateConnection ()) {
806 PasswordInfo pi = GetPasswordInfo (username);
808 throw new ProviderException (username + "is not found in the membership database");
810 string newPassword = GeneratePassword ();
811 EmitValidatingPassword (username, newPassword, false);
813 string db_password = EncodePassword (newPassword, pi.PasswordFormat, pi.PasswordSalt);
814 string db_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
816 DbCommand command = factory.CreateCommand ();
817 command.Connection = connection;
818 command.CommandText = @"aspnet_Membership_ResetPassword";
819 command.CommandType = CommandType.StoredProcedure;
821 AddParameter (command, "@ApplicationName", ApplicationName);
822 AddParameter (command, "@UserName", username);
823 AddParameter (command, "@NewPassword", db_password);
824 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
825 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
826 AddParameter (command, "@PasswordSalt", pi.PasswordSalt);
827 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
828 AddParameter (command, "@PasswordFormat", (int) pi.PasswordFormat);
829 AddParameter (command, "@PasswordAnswer", db_answer);
830 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
832 command.ExecuteNonQuery ();
834 int returnValue = GetReturnValue (retValue);
836 if (returnValue == 0)
838 else if (returnValue == 3)
839 throw new MembershipPasswordException ("Password Answer is invalid");
840 else if (returnValue == 99)
841 throw new MembershipPasswordException ("The user account is currently locked out");
843 throw new ProviderException ("Failed to reset password");
847 public override void UpdateUser (MembershipUser user)
850 throw new ArgumentNullException ("user");
852 if (user.UserName == null)
853 throw new ArgumentNullException ("user.UserName");
855 if (RequiresUniqueEmail && user.Email == null)
856 throw new ArgumentNullException ("user.Email");
858 CheckParam ("user.UserName", user.UserName, 256);
860 if (user.Email.Length > 256 || (RequiresUniqueEmail && user.Email.Length == 0))
861 throw new ArgumentException ("invalid format for user.Email");
863 using (DbConnection connection = CreateConnection ()) {
866 DbCommand command = factory.CreateCommand ();
867 command.Connection = connection;
868 command.CommandText = @"aspnet_Membership_UpdateUser";
869 command.CommandType = CommandType.StoredProcedure;
871 AddParameter (command, "@ApplicationName", ApplicationName);
872 AddParameter (command, "@UserName", user.UserName);
873 AddParameter (command, "@Email", user.Email == null ? (object) DBNull.Value : (object) user.Email);
874 AddParameter (command, "@Comment", user.Comment == null ? (object) DBNull.Value : (object) user.Comment);
875 AddParameter (command, "@IsApproved", user.IsApproved);
876 AddParameter (command, "@LastLoginDate", DateTime.UtcNow);
877 AddParameter (command, "@LastActivityDate", DateTime.UtcNow);
878 AddParameter (command, "@UniqueEmail", RequiresUniqueEmail);
879 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
880 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
882 command.ExecuteNonQuery ();
884 returnValue = GetReturnValue (retValue);
886 if (returnValue == 1)
887 throw new ProviderException ("The UserName property of user was not found in the database.");
888 if (returnValue == 7)
889 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.");
890 if (returnValue != 0)
891 throw new ProviderException ("Failed to update user");
895 public override bool ValidateUser (string username, string password)
897 if (username.Length == 0)
900 CheckParam ("username", username, 256);
901 EmitValidatingPassword (username, password, false);
903 PasswordInfo pi = ValidateUsingPassword (username, password);
905 pi.LastLoginDate = DateTime.UtcNow;
906 UpdateUserInfo (username, pi, true, true);
913 public override bool UnlockUser (string username)
915 CheckParam ("username", username, 256);
917 using (DbConnection connection = CreateConnection ()) {
919 DbCommand command = factory.CreateCommand ();
920 command.Connection = connection;
921 command.CommandText = @"aspnet_Membership_UnlockUser"; ;
922 command.CommandType = CommandType.StoredProcedure;
924 AddParameter (command, "@ApplicationName", ApplicationName);
925 AddParameter (command, "@UserName", username);
926 DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
928 command.ExecuteNonQuery ();
929 if (GetReturnValue (returnValue) != 0)
932 catch (Exception e) {
933 throw new ProviderException ("Failed to unlock user", e);
939 void UpdateUserInfo (string username, PasswordInfo pi, bool isPasswordCorrect, bool updateLoginActivity)
941 CheckParam ("username", username, 256);
943 using (DbConnection connection = CreateConnection ()) {
945 DbCommand command = factory.CreateCommand ();
946 command.Connection = connection;
947 command.CommandText = @"aspnet_Membership_UpdateUserInfo"; ;
948 command.CommandType = CommandType.StoredProcedure;
950 AddParameter (command, "@ApplicationName", ApplicationName);
951 AddParameter (command, "@UserName", username);
952 AddParameter (command, "@IsPasswordCorrect", isPasswordCorrect);
953 AddParameter (command, "@UpdateLastLoginActivityDate", updateLoginActivity);
954 AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts);
955 AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow);
956 AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow);
957 AddParameter (command, "@LastLoginDate", pi.LastLoginDate);
958 AddParameter (command, "@LastActivityDate", pi.LastActivityDate);
959 DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
961 command.ExecuteNonQuery ();
963 int returnValue = GetReturnValue (retValue);
964 if (returnValue != 0)
967 catch (Exception e) {
968 throw new ProviderException ("Failed to update Membership table", e);
974 PasswordInfo ValidateUsingPassword (string username, string password)
976 MembershipUser user = GetUser (username, true);
980 if (!user.IsApproved || user.IsLockedOut)
983 PasswordInfo pi = GetPasswordInfo (username);
988 /* do the actual validation */
989 string user_password = EncodePassword (password, pi.PasswordFormat, pi.PasswordSalt);
991 if (user_password != pi.Password) {
992 UpdateUserInfo (username, pi, false, false);
999 private PasswordInfo GetPasswordInfo (string username)
1001 using (DbConnection connection = CreateConnection ()) {
1002 DbCommand command = factory.CreateCommand ();
1003 command.Connection = connection;
1004 command.CommandType = CommandType.StoredProcedure;
1005 command.CommandText = @"aspnet_Membership_GetPasswordWithFormat";
1007 AddParameter (command, "@ApplicationName", ApplicationName);
1008 AddParameter (command, "@UserName", username);
1009 AddParameter (command, "@UpdateLastLoginActivityDate", false);
1010 AddParameter (command, "@CurrentTimeUtc", DateTime.Now);
1012 AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null);
1014 DbDataReader reader = command.ExecuteReader ();
1015 if (!reader.Read ())
1018 PasswordInfo pi = new PasswordInfo (
1019 reader.GetString (0),
1020 (MembershipPasswordFormat) reader.GetInt32 (1),
1021 reader.GetString (2),
1022 reader.GetInt32 (3),
1023 reader.GetInt32 (4),
1024 reader.GetBoolean (5),
1025 reader.GetDateTime (6),
1026 reader.GetDateTime (7));
1032 private string EncodePassword (string password, MembershipPasswordFormat passwordFormat, string salt)
1034 byte [] password_bytes;
1037 switch (passwordFormat) {
1038 case MembershipPasswordFormat.Clear:
1040 case MembershipPasswordFormat.Hashed:
1041 password_bytes = Encoding.Unicode.GetBytes (password);
1042 salt_bytes = Convert.FromBase64String (salt);
1044 byte [] hashBytes = new byte [salt_bytes.Length + password_bytes.Length];
1046 Buffer.BlockCopy (salt_bytes, 0, hashBytes, 0, salt_bytes.Length);
1047 Buffer.BlockCopy (password_bytes, 0, hashBytes, salt_bytes.Length, password_bytes.Length);
1049 MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
1050 string alg_type = section.HashAlgorithmType;
1051 if (alg_type == "") {
1052 MachineKeySection keysection = (MachineKeySection) WebConfigurationManager.GetSection ("system.web/machineKey");
1053 alg_type = keysection.Validation.ToString ();
1055 using (HashAlgorithm hash = HashAlgorithm.Create (alg_type)) {
1056 hash.TransformFinalBlock (hashBytes, 0, hashBytes.Length);
1057 return Convert.ToBase64String (hash.Hash);
1059 case MembershipPasswordFormat.Encrypted:
1060 password_bytes = Encoding.Unicode.GetBytes (password);
1061 salt_bytes = Convert.FromBase64String (salt);
1063 byte [] buf = new byte [password_bytes.Length + salt_bytes.Length];
1065 Array.Copy (salt_bytes, 0, buf, 0, salt_bytes.Length);
1066 Array.Copy (password_bytes, 0, buf, salt_bytes.Length, password_bytes.Length);
1068 return Convert.ToBase64String (EncryptPassword (buf));
1075 private string DecodePassword (string password, MembershipPasswordFormat passwordFormat)
1077 switch (passwordFormat) {
1078 case MembershipPasswordFormat.Clear:
1080 case MembershipPasswordFormat.Hashed:
1081 throw new ProviderException ("Hashed passwords cannot be decoded.");
1082 case MembershipPasswordFormat.Encrypted:
1083 return Encoding.Unicode.GetString (DecryptPassword (Convert.FromBase64String (password)));
1090 public override string ApplicationName
1092 get { return applicationName; }
1093 set { applicationName = value; }
1096 public override bool EnablePasswordReset
1098 get { return enablePasswordReset; }
1101 public override bool EnablePasswordRetrieval
1103 get { return enablePasswordRetrieval; }
1106 public override MembershipPasswordFormat PasswordFormat
1108 get { return passwordFormat; }
1111 public override bool RequiresQuestionAndAnswer
1113 get { return requiresQuestionAndAnswer; }
1116 public override bool RequiresUniqueEmail
1118 get { return requiresUniqueEmail; }
1121 public override int MaxInvalidPasswordAttempts
1123 get { return maxInvalidPasswordAttempts; }
1126 public override int MinRequiredNonAlphanumericCharacters
1128 get { return minRequiredNonAlphanumericCharacters; }
1131 public override int MinRequiredPasswordLength
1133 get { return minRequiredPasswordLength; }
1136 public override int PasswordAttemptWindow
1138 get { return passwordAttemptWindow; }
1141 public override string PasswordStrengthRegularExpression
1143 get { return passwordStrengthRegularExpression; }
1147 private enum DeleteUserTableMask
1149 MembershipUsers = 1,
1152 WebPartStateUser = 8
1155 private sealed class PasswordInfo
1157 private string _password;
1158 private MembershipPasswordFormat _passwordFormat;
1159 private string _passwordSalt;
1160 private int _failedPasswordAttemptCount;
1161 private int _failedPasswordAnswerAttemptCount;
1162 private bool _isApproved;
1163 private DateTime _lastLoginDate;
1164 private DateTime _lastActivityDate;
1166 internal PasswordInfo (
1168 MembershipPasswordFormat passwordFormat,
1169 string passwordSalt,
1170 int failedPasswordAttemptCount,
1171 int failedPasswordAnswerAttemptCount,
1173 DateTime lastLoginDate,
1174 DateTime lastActivityDate)
1176 _password = password;
1177 _passwordFormat = passwordFormat;
1178 _passwordSalt = passwordSalt;
1179 _failedPasswordAttemptCount = failedPasswordAttemptCount;
1180 _failedPasswordAnswerAttemptCount = failedPasswordAnswerAttemptCount;
1181 _isApproved = isApproved;
1182 _lastLoginDate = lastLoginDate;
1183 _lastActivityDate = lastActivityDate;
1186 public string Password
1188 get { return _password; }
1189 set { _password = value; }
1191 public MembershipPasswordFormat PasswordFormat
1193 get { return _passwordFormat; }
1194 set { _passwordFormat = value; }
1196 public string PasswordSalt
1198 get { return _passwordSalt; }
1199 set { _passwordSalt = value; }
1201 public int FailedPasswordAttemptCount
1203 get { return _failedPasswordAttemptCount; }
1204 set { _failedPasswordAttemptCount = value; }
1206 public int FailedPasswordAnswerAttemptCount
1208 get { return _failedPasswordAnswerAttemptCount; }
1209 set { _failedPasswordAnswerAttemptCount = value; }
1211 public bool IsApproved
1213 get { return _isApproved; }
1214 set { _isApproved = value; }
1216 public DateTime LastLoginDate
1218 get { return _lastLoginDate; }
1219 set { _lastLoginDate = value; }
1221 public DateTime LastActivityDate
1223 get { return _lastActivityDate; }
1224 set { _lastActivityDate = value; }