3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
11 // The above copyright notice and this permission notice shall be
12 // included in all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // Copyright © 2006, 2007 Nauck IT KG http://www.nauck-it.de
25 // Daniel Nauck <d.nauck(at)nauck-it.de>
27 // Adapted to Sqlite by Marek Habersack <mhabersack@novell.com>
33 using System.Data.Common;
34 using System.Collections.Generic;
35 using System.Collections.Specialized;
37 using System.Security.Cryptography;
38 using System.Web.Hosting;
39 using System.Web.Configuration;
40 using System.Web.Security;
41 using System.Configuration;
42 using System.Configuration.Provider;
43 using System.Diagnostics;
45 using Mono.Data.Sqlite;
47 namespace System.Web.Security
49 internal class SqliteMembershipProvider : MembershipProvider
51 private const string m_TableName = "Users";
52 private string m_ConnectionString = string.Empty;
53 private const int m_NewPasswordLength = 8;
54 private bool machineKeyIsAutoGenerated;
56 // Used when determining encryption key values.
57 private MachineKeySection m_MachineKey = null;
59 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
61 return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
64 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
66 DbParameter dbp = command.CreateParameter ();
67 dbp.ParameterName = parameterName;
68 dbp.Value = parameterValue;
69 dbp.Direction = direction;
70 command.Parameters.Add (dbp);
74 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, DbType type, object parameterValue)
76 DbParameter dbp = command.CreateParameter ();
77 dbp.ParameterName = parameterName;
78 dbp.Value = parameterValue;
79 dbp.Direction = direction;
81 command.Parameters.Add (dbp);
86 /// System.Configuration.Provider.ProviderBase.Initialize Method.
88 public override void Initialize(string name, NameValueCollection config)
90 // Initialize values from web.config.
92 throw new ArgumentNullException("Config", Properties.Resources.ErrArgumentNull);
94 if (string.IsNullOrEmpty(name))
95 name = Properties.Resources.MembershipProviderDefaultName;
97 if (string.IsNullOrEmpty(config["description"]))
99 config.Remove("description");
100 config.Add("description", Properties.Resources.MembershipProviderDefaultDescription);
103 // Initialize the abstract base class.
104 base.Initialize(name, config);
106 m_ApplicationName = GetConfigValue(config["applicationName"], HostingEnvironment.ApplicationVirtualPath);
107 m_MaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
108 m_PasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
109 m_MinRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonAlphanumericCharacters"], "1"));
110 m_MinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
111 m_PasswordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], ""));
112 m_EnablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
113 m_EnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
114 m_RequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
115 m_RequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));
117 // Get password encryption type.
118 string pwFormat = GetConfigValue(config["passwordFormat"], "Hashed");
122 m_PasswordFormat = MembershipPasswordFormat.Hashed;
125 m_PasswordFormat = MembershipPasswordFormat.Encrypted;
128 m_PasswordFormat = MembershipPasswordFormat.Clear;
131 throw new ProviderException(Properties.Resources.ErrPwFormatNotSupported);
134 // Get connection string.
135 string connStrName = config["connectionStringName"];
137 if (string.IsNullOrEmpty(connStrName))
139 throw new ArgumentOutOfRangeException("ConnectionStringName", Properties.Resources.ErrArgumentNullOrEmpty);
143 ConnectionStringSettings ConnectionStringSettings = ConfigurationManager.ConnectionStrings[connStrName];
145 if (ConnectionStringSettings == null || string.IsNullOrEmpty(ConnectionStringSettings.ConnectionString.Trim()))
147 throw new ProviderException(Properties.Resources.ErrConnectionStringNullOrEmpty);
150 m_ConnectionString = ConnectionStringSettings.ConnectionString;
153 // Get encryption and decryption key information from the configuration.
154 System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath);
155 m_MachineKey = (MachineKeySection)cfg.GetSection("system.web/machineKey");
157 if (!m_PasswordFormat.Equals(MembershipPasswordFormat.Clear))
159 if (m_MachineKey == null)
160 throw new ArgumentNullException("system.web/machineKey", Properties.Resources.ErrArgumentNull);
162 if (m_MachineKey.ValidationKey.Contains("AutoGenerate")) {
163 machineKeyIsAutoGenerated = true;
164 if (m_PasswordFormat.Equals (MembershipPasswordFormat.Encrypted))
165 throw new ProviderException(Properties.Resources.ErrAutoGeneratedKeyNotSupported);
171 /// System.Web.Security.MembershipProvider properties.
173 #region System.Web.Security.MembershipProvider properties
174 private string m_ApplicationName = string.Empty;
175 private bool m_EnablePasswordReset = false;
176 private bool m_EnablePasswordRetrieval = false;
177 private bool m_RequiresQuestionAndAnswer = false;
178 private bool m_RequiresUniqueEmail = false;
179 private int m_MaxInvalidPasswordAttempts = 0;
180 private int m_PasswordAttemptWindow = 0;
181 private MembershipPasswordFormat m_PasswordFormat = MembershipPasswordFormat.Clear;
182 private int m_MinRequiredNonAlphanumericCharacters = 0;
183 private int m_MinRequiredPasswordLength = 0;
184 private string m_PasswordStrengthRegularExpression = string.Empty;
186 public override string ApplicationName
188 get { return m_ApplicationName; }
189 set { m_ApplicationName = value; }
192 public override bool EnablePasswordReset
194 get { return m_EnablePasswordReset; }
197 public override bool EnablePasswordRetrieval
199 get { return m_EnablePasswordRetrieval; }
202 public override bool RequiresQuestionAndAnswer
204 get { return m_RequiresQuestionAndAnswer; }
207 public override bool RequiresUniqueEmail
209 get { return m_RequiresUniqueEmail; }
212 public override int MaxInvalidPasswordAttempts
214 get { return m_MaxInvalidPasswordAttempts; }
217 public override int PasswordAttemptWindow
219 get { return m_PasswordAttemptWindow; }
222 public override MembershipPasswordFormat PasswordFormat
224 get { return m_PasswordFormat; }
227 public override int MinRequiredNonAlphanumericCharacters
229 get { return m_MinRequiredNonAlphanumericCharacters; }
232 public override int MinRequiredPasswordLength
234 get { return m_MinRequiredPasswordLength; }
237 public override string PasswordStrengthRegularExpression
239 get { return m_PasswordStrengthRegularExpression; }
245 /// System.Web.Security.MembershipProvider methods.
247 #region System.Web.Security.MembershipProvider methods
250 /// MembershipProvider.ChangePassword
252 public override bool ChangePassword(string username, string oldPassword, string newPassword)
254 if (!ValidateUser(username, oldPassword))
257 ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);
259 OnValidatingPassword(args);
263 if (args.FailureInformation != null)
264 throw args.FailureInformation;
266 throw new MembershipPasswordException(Properties.Resources.ErrPasswordChangeCanceled);
269 int rowsAffected = 0;
271 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
273 using (SqliteCommand dbCommand = dbConn.CreateCommand())
275 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"Password\" = @Password, \"LastPasswordChangedDate\" = @LastPasswordChangedDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
277 AddParameter (dbCommand,"@Password", EncodePassword(newPassword));
278 AddParameter (dbCommand,"@LastPasswordChangedDate", DateTime.Now);
279 AddParameter (dbCommand,"@Username", username);
280 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
287 rowsAffected = dbCommand.ExecuteNonQuery();
289 catch (SqliteException e)
291 Trace.WriteLine(e.ToString());
292 throw new ProviderException(Properties.Resources.ErrOperationAborted);
302 if (rowsAffected > 0)
309 /// MembershipProvider.ChangePasswordQuestionAndAnswer
311 public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
313 if (!ValidateUser(username, password))
316 int rowsAffected = 0;
318 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
320 using (SqliteCommand dbCommand = dbConn.CreateCommand())
322 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"PasswordQuestion\" = @PasswordQuestion, \"PasswordAnswer\" = @PasswordAnswer WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
324 AddParameter (dbCommand,"@PasswordQuestion", newPasswordQuestion);
325 AddParameter (dbCommand,"@PasswordAnswer", EncodePassword(newPasswordAnswer));
326 AddParameter (dbCommand,"@Username", username);
327 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
334 rowsAffected = dbCommand.ExecuteNonQuery();
336 catch (SqliteException e)
338 Trace.WriteLine(e.ToString());
339 throw new ProviderException(Properties.Resources.ErrOperationAborted);
349 if (rowsAffected > 0)
356 /// MembershipProvider.CreateUser
358 public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved,
359 object providerUserKey, out MembershipCreateStatus status)
361 ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
363 OnValidatingPassword(args);
367 status = MembershipCreateStatus.InvalidPassword;
371 if (RequiresUniqueEmail && string.IsNullOrEmpty(email))
373 status = MembershipCreateStatus.InvalidEmail;
377 if (RequiresUniqueEmail && !string.IsNullOrEmpty(GetUserNameByEmail(email)))
379 status = MembershipCreateStatus.DuplicateEmail;
383 if (GetUser(username, false) == null)
385 DateTime createDate = DateTime.Now;
387 if (providerUserKey == null)
389 providerUserKey = Guid.NewGuid();
393 if (!(providerUserKey is Guid))
395 status = MembershipCreateStatus.InvalidProviderUserKey;
400 // Create user in database
401 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
403 using (SqliteCommand dbCommand = dbConn.CreateCommand())
405 dbCommand.CommandText = string.Format("INSERT INTO \"{0}\" (\"pId\", \"Username\", \"Password\", \"Email\", \"PasswordQuestion\", \"PasswordAnswer\", \"IsApproved\", \"CreationDate\", \"LastPasswordChangedDate\", \"LastActivityDate\", \"ApplicationName\", \"IsLockedOut\", \"LastLockedOutDate\", \"FailedPasswordAttemptCount\", \"FailedPasswordAttemptWindowStart\", \"FailedPasswordAnswerAttemptCount\", \"FailedPasswordAnswerAttemptWindowStart\") Values (@pId, @Username, @Password, @Email, @PasswordQuestion, @PasswordAnswer, @IsApproved, @CreationDate, @LastPasswordChangedDate, @LastActivityDate, @ApplicationName, @IsLockedOut, @LastLockedOutDate, @FailedPasswordAttemptCount, @FailedPasswordAttemptWindowStart, @FailedPasswordAnswerAttemptCount, @FailedPasswordAnswerAttemptWindowStart)", m_TableName);
407 AddParameter (dbCommand,"@pId", providerUserKey);
408 AddParameter (dbCommand,"@Username", username);
409 AddParameter (dbCommand,"@Password", EncodePassword(password));
410 AddParameter (dbCommand,"@Email", email);
411 AddParameter (dbCommand,"@PasswordQuestion", passwordQuestion);
412 AddParameter (dbCommand,"@PasswordAnswer", EncodePassword(passwordAnswer));
413 AddParameter (dbCommand,"@IsApproved", isApproved);
414 AddParameter (dbCommand,"@CreationDate", createDate);
415 AddParameter (dbCommand,"@LastPasswordChangedDate", createDate);
416 AddParameter (dbCommand,"@LastActivityDate", createDate);
417 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
418 AddParameter (dbCommand,"@IsLockedOut", false);
419 AddParameter (dbCommand,"@LastLockedOutDate", createDate);
420 AddParameter (dbCommand,"@FailedPasswordAttemptCount", 0);
421 AddParameter (dbCommand,"@FailedPasswordAttemptWindowStart", createDate);
422 AddParameter (dbCommand,"@FailedPasswordAnswerAttemptCount", 0);
423 AddParameter (dbCommand,"@FailedPasswordAnswerAttemptWindowStart", createDate);
430 if (dbCommand.ExecuteNonQuery() > 0)
432 status = MembershipCreateStatus.Success;
436 status = MembershipCreateStatus.UserRejected;
439 catch (SqliteException e)
441 status = MembershipCreateStatus.ProviderError;
442 Trace.WriteLine(e.ToString());
443 throw new ProviderException(Properties.Resources.ErrOperationAborted);
451 return GetUser(username, false);
457 status = MembershipCreateStatus.DuplicateUserName;
463 /// MembershipProvider.DeleteUser
465 public override bool DeleteUser(string username, bool deleteAllRelatedData)
467 int rowsAffected = 0;
469 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
471 using (SqliteCommand dbCommand = dbConn.CreateCommand())
473 dbCommand.CommandText = string.Format("DELETE FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
475 AddParameter (dbCommand,"@Username", username);
476 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
483 rowsAffected = dbCommand.ExecuteNonQuery();
485 if (deleteAllRelatedData)
487 // Process commands to delete all data for the user in the database.
490 catch (SqliteException e)
492 Trace.WriteLine(e.ToString());
493 throw new ProviderException(Properties.Resources.ErrOperationAborted);
503 if (rowsAffected > 0)
510 /// MembershipProvider.FindUsersByEmail
512 public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
515 MembershipUserCollection users = new MembershipUserCollection();
517 // replace permitted wildcard characters
518 emailToMatch = emailToMatch.Replace('*','%');
519 emailToMatch = emailToMatch.Replace('?', '_');
521 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
524 using (SqliteCommand dbCommand = dbConn.CreateCommand())
526 dbCommand.CommandText = string.Format("SELECT Count(*) FROM \"{0}\" WHERE \"Email\" LIKE @Email AND \"ApplicationName\" = @ApplicationName", m_TableName);
528 AddParameter (dbCommand,"@Email", emailToMatch);
529 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
536 Int32.TryParse(dbCommand.ExecuteScalar().ToString(), out totalRecords);
538 if (totalRecords <= 0) { return users; }
540 catch (SqliteException e)
542 Trace.WriteLine(e.ToString());
543 throw new ProviderException(Properties.Resources.ErrOperationAborted);
552 // Fetch user from database
553 using (SqliteCommand dbCommand = dbConn.CreateCommand())
555 dbCommand.CommandText = string.Format("SELECT \"pId\", \"Username\", \"Email\", \"PasswordQuestion\", \"Comment\", \"IsApproved\", \"IsLockedOut\", \"CreationDate\", \"LastLoginDate\", \"LastActivityDate\", \"LastPasswordChangedDate\", \"LastLockedOutDate\" FROM \"{0}\" WHERE \"Email\" LIKE @Email AND \"ApplicationName\" = @ApplicationName ORDER BY \"Username\" ASC LIMIT @MaxCount OFFSET @StartIndex", m_TableName);
557 AddParameter (dbCommand,"@Email", emailToMatch);
558 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
559 AddParameter (dbCommand,"@MaxCount", pageSize);
560 AddParameter (dbCommand,"@StartIndex", pageSize * pageIndex);
567 using (SqliteDataReader reader = dbCommand.ExecuteReader())
569 while (reader.Read())
571 MembershipUser u = GetUserFromReader(reader);
576 catch (SqliteException e)
578 Trace.WriteLine(e.ToString());
579 throw new ProviderException(Properties.Resources.ErrOperationAborted);
593 /// MembershipProvider.FindUsersByName
595 public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
598 MembershipUserCollection users = new MembershipUserCollection();
600 // replace permitted wildcard characters
601 usernameToMatch = usernameToMatch.Replace('*', '%');
602 usernameToMatch = usernameToMatch.Replace('?', '_');
604 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
607 using (SqliteCommand dbCommand = dbConn.CreateCommand())
609 dbCommand.CommandText = string.Format("SELECT Count(*) FROM \"{0}\" WHERE \"Username\" LIKE @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
611 AddParameter (dbCommand,"@Username", usernameToMatch);
612 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
619 Int32.TryParse(dbCommand.ExecuteScalar().ToString(), out totalRecords);
621 if (totalRecords <= 0) { return users; }
623 catch (SqliteException e)
625 Trace.WriteLine(e.ToString());
626 throw new ProviderException(Properties.Resources.ErrOperationAborted);
635 // Fetch user from database
636 using (SqliteCommand dbCommand = dbConn.CreateCommand())
638 dbCommand.CommandText = string.Format("SELECT \"pId\", \"Username\", \"Email\", \"PasswordQuestion\", \"Comment\", \"IsApproved\", \"IsLockedOut\", \"CreationDate\", \"LastLoginDate\", \"LastActivityDate\", \"LastPasswordChangedDate\", \"LastLockedOutDate\" FROM \"{0}\" WHERE \"Username\" LIKE @Username AND \"ApplicationName\" = @ApplicationName ORDER BY \"Username\" ASC LIMIT @MaxCount OFFSET @StartIndex", m_TableName);
640 AddParameter (dbCommand,"@Username", usernameToMatch);
641 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
642 AddParameter (dbCommand,"@MaxCount", pageSize);
643 AddParameter (dbCommand,"@StartIndex", pageSize * pageIndex);
650 using (SqliteDataReader reader = dbCommand.ExecuteReader())
652 while (reader.Read())
654 MembershipUser u = GetUserFromReader(reader);
659 catch (SqliteException e)
661 Trace.WriteLine(e.ToString());
662 throw new ProviderException(Properties.Resources.ErrOperationAborted);
676 /// MembershipProvider.GetAllUsers
678 public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
681 MembershipUserCollection users = new MembershipUserCollection();
683 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
686 using (SqliteCommand dbCommand = dbConn.CreateCommand())
688 dbCommand.CommandText = string.Format("SELECT Count(*) FROM \"{0}\" WHERE \"ApplicationName\" = @ApplicationName", m_TableName);
690 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
697 Int32.TryParse(dbCommand.ExecuteScalar().ToString(), out totalRecords);
699 if (totalRecords <= 0) { return users; }
701 catch (SqliteException e)
703 Trace.WriteLine(e.ToString());
713 // Fetch user from database
714 using (SqliteCommand dbCommand = dbConn.CreateCommand())
716 dbCommand.CommandText = string.Format("SELECT \"pId\", \"Username\", \"Email\", \"PasswordQuestion\", \"Comment\", \"IsApproved\", \"IsLockedOut\", \"CreationDate\", \"LastLoginDate\", \"LastActivityDate\", \"LastPasswordChangedDate\", \"LastLockedOutDate\" FROM \"{0}\" WHERE \"ApplicationName\" = @ApplicationName ORDER BY \"Username\" ASC LIMIT @MaxCount OFFSET @StartIndex", m_TableName);
718 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
719 AddParameter (dbCommand,"@MaxCount", pageSize);
720 AddParameter (dbCommand,"@StartIndex", pageSize * pageIndex);
727 using (SqliteDataReader reader = dbCommand.ExecuteReader())
729 while (reader.Read())
731 MembershipUser u = GetUserFromReader(reader);
736 catch (SqliteException e)
738 Trace.WriteLine(e.ToString());
739 throw new ProviderException(Properties.Resources.ErrOperationAborted);
753 /// MembershipProvider.GetNumberOfUsersOnline
755 public override int GetNumberOfUsersOnline()
759 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
761 using (SqliteCommand dbCommand = dbConn.CreateCommand())
763 TimeSpan onlineSpan = new TimeSpan(0, System.Web.Security.Membership.UserIsOnlineTimeWindow, 0);
764 DateTime compareTime = DateTime.Now.Subtract(onlineSpan);
766 dbCommand.CommandText = string.Format("SELECT Count(*) FROM \"{0}\" WHERE \"LastActivityDate\" > @CompareTime AND \"ApplicationName\" = @ApplicationName", m_TableName);
768 AddParameter (dbCommand,"@CompareTime", compareTime);
769 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
776 numOnline = (int)dbCommand.ExecuteScalar();
778 catch (SqliteException e)
780 Trace.WriteLine(e.ToString());
781 throw new ProviderException(Properties.Resources.ErrOperationAborted);
795 /// MembershipProvider.GetPassword
797 public override string GetPassword(string username, string answer)
799 if (!EnablePasswordRetrieval)
801 throw new ProviderException(Properties.Resources.ErrPasswordRetrievalNotEnabled);
804 if (PasswordFormat == MembershipPasswordFormat.Hashed)
806 throw new ProviderException(Properties.Resources.ErrCantRetrieveHashedPw);
809 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
811 using (SqliteCommand dbCommand = dbConn.CreateCommand())
813 dbCommand.CommandText = string.Format("SELECT \"Password\", \"PasswordAnswer\", \"IsLockedOut\" FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
815 AddParameter (dbCommand,"@Username", username);
816 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
823 using (SqliteDataReader reader = dbCommand.ExecuteReader())
829 string password = reader.GetString(0);
830 string passwordAnswer = reader.GetString(1);
831 bool isLockedOut = reader.GetBoolean(2);
836 throw new MembershipPasswordException(Properties.Resources.ErrUserIsLoggedOut);
838 if (m_RequiresQuestionAndAnswer && !CheckPassword(answer, passwordAnswer))
840 UpdateFailureCount(username, FailureType.PasswordAnswer);
842 throw new MembershipPasswordException(Properties.Resources.ErrIncorrectPasswordAnswer);
845 if (m_PasswordFormat == MembershipPasswordFormat.Encrypted)
847 password = UnEncodePassword(password);
854 throw new MembershipPasswordException(Properties.Resources.ErrUserNotFound);
858 catch (SqliteException e)
860 Trace.WriteLine(e.ToString());
861 throw new ProviderException(Properties.Resources.ErrOperationAborted);
873 /// MembershipProvider.GetUser
875 public override MembershipUser GetUser(string username, bool userIsOnline)
877 MembershipUser u = null;
879 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
881 using (SqliteCommand dbCommand = dbConn.CreateCommand())
883 dbCommand.CommandText = string.Format("SELECT \"pId\", \"Username\", \"Email\", \"PasswordQuestion\", \"Comment\", \"IsApproved\", \"IsLockedOut\", \"CreationDate\", \"LastLoginDate\", \"LastActivityDate\", \"LastPasswordChangedDate\", \"LastLockedOutDate\" FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
885 AddParameter (dbCommand,"@Username", username);
886 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
893 using (SqliteDataReader reader = dbCommand.ExecuteReader())
898 u = GetUserFromReader(reader);
902 // Update user online status
903 using (SqliteCommand dbUpdateCommand = dbConn.CreateCommand())
905 dbUpdateCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"LastActivityDate\" = @LastActivityDate WHERE \"pId\" = @pId", m_TableName);
907 AddParameter (dbUpdateCommand, "@LastActivityDate", DateTime.Now);
908 AddParameter (dbUpdateCommand, "@pId", u.ProviderUserKey);
910 dbUpdateCommand.Prepare();
912 dbUpdateCommand.ExecuteNonQuery();
918 catch (SqliteException e)
920 Trace.WriteLine(e.ToString());
921 throw new ProviderException(Properties.Resources.ErrOperationAborted);
935 /// MembershipProvider.GetUser
937 public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
939 MembershipUser u = null;
941 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
943 using (SqliteCommand dbCommand = dbConn.CreateCommand())
945 dbCommand.CommandText = string.Format("SELECT \"pId\", \"Username\", \"Email\", \"PasswordQuestion\", \"Comment\", \"IsApproved\", \"IsLockedOut\", \"CreationDate\", \"LastLoginDate\", \"LastActivityDate\", \"LastPasswordChangedDate\", \"LastLockedOutDate\" FROM \"{0}\" WHERE \"pId\" = @pId", m_TableName);
947 AddParameter (dbCommand,"@pId", providerUserKey);
954 using (SqliteDataReader reader = dbCommand.ExecuteReader())
959 u = GetUserFromReader(reader);
963 // Update user online status
964 using (SqliteCommand dbUpdateCommand = dbConn.CreateCommand())
966 dbUpdateCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"LastActivityDate\" = @LastActivityDate WHERE \"pId\" = @pId", m_TableName);
968 AddParameter (dbUpdateCommand, "@LastActivityDate", DateTime.Now);
969 AddParameter (dbUpdateCommand, "@pId", u.ProviderUserKey);
971 dbUpdateCommand.Prepare();
973 dbUpdateCommand.ExecuteNonQuery();
979 catch (SqliteException e)
981 Trace.WriteLine(e.ToString());
982 throw new ProviderException(Properties.Resources.ErrOperationAborted);
996 /// MembershipProvider.GetUserNameByEmail
998 public override string GetUserNameByEmail(string email)
1000 string username = string.Empty;
1002 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
1004 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1006 dbCommand.CommandText = string.Format("SELECT \"Username\" FROM \"{0}\" WHERE \"Email\" = @Email AND \"ApplicationName\" = @ApplicationName", m_TableName);
1008 AddParameter (dbCommand,"@Email", email);
1009 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1014 dbCommand.Prepare();
1016 username = (dbCommand.ExecuteScalar() as string) ?? string.Empty;
1018 catch (SqliteException e)
1020 Trace.WriteLine(e.ToString());
1021 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1035 /// MembershipProvider.ResetPassword
1037 public override string ResetPassword(string username, string answer)
1039 if (!m_EnablePasswordReset)
1041 throw new NotSupportedException(Properties.Resources.ErrPasswordResetNotEnabled);
1044 if (string.IsNullOrEmpty(answer) && m_RequiresQuestionAndAnswer)
1046 UpdateFailureCount(username, FailureType.PasswordAnswer);
1048 throw new ProviderException(Properties.Resources.ErrPasswordAnswerRequired);
1051 string newPassword = Membership.GeneratePassword(m_NewPasswordLength, m_MinRequiredNonAlphanumericCharacters);
1054 ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);
1056 OnValidatingPassword(args);
1060 if (args.FailureInformation != null)
1061 throw args.FailureInformation;
1063 throw new MembershipPasswordException(Properties.Resources.ErrPasswordResetCanceled);
1066 int rowsAffected = 0;
1068 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
1070 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1072 dbCommand.CommandText = string.Format("SELECT \"PasswordAnswer\", \"IsLockedOut\" FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1074 AddParameter (dbCommand,"@Username", username);
1075 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1079 string passwordAnswer = string.Empty;
1082 dbCommand.Prepare();
1084 using (SqliteDataReader reader = dbCommand.ExecuteReader())
1090 passwordAnswer = reader.GetString(0);
1091 bool isLockedOut = reader.GetBoolean(1);
1096 throw new MembershipPasswordException(Properties.Resources.ErrUserIsLoggedOut);
1098 if (m_RequiresQuestionAndAnswer && !CheckPassword(answer, passwordAnswer))
1100 UpdateFailureCount(username, FailureType.PasswordAnswer);
1102 throw new MembershipPasswordException(Properties.Resources.ErrIncorrectPasswordAnswer);
1107 throw new MembershipPasswordException(Properties.Resources.ErrUserNotFound);
1112 using (SqliteCommand dbUpdateCommand = dbConn.CreateCommand())
1114 dbUpdateCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"Password\" = @Password, \"LastPasswordChangedDate\" = @LastPasswordChangedDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsLockedOut\" = @IsLockedOut", m_TableName);
1116 AddParameter (dbUpdateCommand, "@Password", EncodePassword(newPassword));
1117 AddParameter (dbUpdateCommand, "@LastPasswordChangedDate", DateTime.Now);
1118 AddParameter (dbUpdateCommand, "@Username", username);
1119 AddParameter (dbUpdateCommand, "@ApplicationName", m_ApplicationName);
1120 AddParameter (dbUpdateCommand, "@IsLockedOut", false);
1122 dbUpdateCommand.Prepare();
1124 rowsAffected = dbUpdateCommand.ExecuteNonQuery();
1128 catch (SqliteException e)
1130 Trace.WriteLine(e.ToString());
1131 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1141 if (rowsAffected > 0)
1145 throw new MembershipPasswordException(Properties.Resources.ErrPasswordResetAborted);
1149 /// MembershipProvider.UnlockUser
1151 public override bool UnlockUser(string userName)
1153 int rowsAffected = 0;
1155 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
1157 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1159 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"IsLockedOut\" = @IsLockedOut, \"LastLockedOutDate\" = @LastLockedOutDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1161 AddParameter (dbCommand,"@IsLockedOut", false);
1162 AddParameter (dbCommand,"@LastLockedOutDate", DateTime.Now);
1163 AddParameter (dbCommand,"@Username", userName);
1164 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1169 dbCommand.Prepare();
1171 rowsAffected = dbCommand.ExecuteNonQuery();
1173 catch (SqliteException e)
1175 Trace.WriteLine(e.ToString());
1176 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1186 if (rowsAffected > 0)
1194 /// MembershipProvider.UpdateUser
1196 public override void UpdateUser(MembershipUser user)
1198 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
1200 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1202 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"Email\" = @Email, \"Comment\" = @Comment, \"IsApproved\" = @IsApproved WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1204 AddParameter (dbCommand,"@Email", user.Email);
1205 AddParameter (dbCommand,"@Comment", user.Comment);
1206 AddParameter (dbCommand,"@IsApproved", user.IsApproved);
1207 AddParameter (dbCommand,"@Username", user.UserName);
1208 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1213 dbCommand.Prepare();
1215 dbCommand.ExecuteNonQuery();
1217 catch (SqliteException e)
1219 Trace.WriteLine(e.ToString());
1220 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1232 /// MembershipProvider.ValidateUser
1234 public override bool ValidateUser(string username, string password)
1236 string dbPassword = string.Empty;
1237 bool dbIsApproved = false;
1239 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
1241 // Fetch user data from database
1242 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1244 dbCommand.CommandText = string.Format("SELECT \"Password\", \"IsApproved\" FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsLockedOut\" = @IsLockedOut", m_TableName);
1246 AddParameter (dbCommand,"@Username", username);
1247 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1248 AddParameter (dbCommand,"@IsLockedOut", false);
1253 dbCommand.Prepare();
1255 using (SqliteDataReader reader = dbCommand.ExecuteReader())
1260 dbPassword = reader.GetString(0);
1261 dbIsApproved = reader.GetBoolean(1);
1269 catch (SqliteException e)
1271 Trace.WriteLine(e.ToString());
1272 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1281 if (CheckPassword(password, dbPassword))
1285 // Update last login date
1286 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1288 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"LastLoginDate\" = @LastLoginDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1290 AddParameter (dbCommand,"@LastLoginDate", DateTime.Now);
1291 AddParameter (dbCommand,"@Username", username);
1292 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1297 dbCommand.Prepare();
1299 dbCommand.ExecuteNonQuery();
1303 catch (SqliteException e)
1305 Trace.WriteLine(e.ToString());
1306 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1322 #region private methods
1324 /// A helper function to retrieve config values from the configuration file.
1326 /// <param name="configValue"></param>
1327 /// <param name="defaultValue"></param>
1328 /// <returns></returns>
1329 private string GetConfigValue(string configValue, string defaultValue)
1331 if (string.IsNullOrEmpty(configValue))
1332 return defaultValue;
1338 /// A helper function that takes the current row from the SqliteDataReader
1339 /// and hydrates a MembershipUser from the values. Called by the
1340 /// MembershipUser.GetUser implementation.
1342 /// <param name="reader">SqliteDataReader object</param>
1343 /// <returns>MembershipUser object</returns>
1344 private MembershipUser GetUserFromReader(SqliteDataReader reader)
1346 object providerUserKey = reader.GetValue(0);
1347 string username = reader.GetString(1);
1348 string email = string.Empty;
1349 if (!reader.IsDBNull(2))
1350 email = reader.GetString(2);
1352 string passwordQuestion = string.Empty;
1353 if (!reader.IsDBNull(3))
1354 passwordQuestion = reader.GetString(3);
1356 string comment = string.Empty;
1357 if (!reader.IsDBNull(4))
1358 comment = reader.GetString(4);
1360 bool isApproved = reader.GetBoolean(5);
1361 bool isLockedOut = reader.GetBoolean(6);
1362 DateTime creationDate = reader.GetDateTime(7);
1364 DateTime lastLoginDate = new DateTime();
1365 if (!reader.IsDBNull(8))
1366 lastLoginDate = reader.GetDateTime(8);
1368 DateTime lastActivityDate = reader.GetDateTime(9);
1369 DateTime lastPasswordChangedDate = reader.GetDateTime(10);
1371 DateTime lastLockedOutDate = new DateTime();
1372 if (!reader.IsDBNull(11))
1373 lastLockedOutDate = reader.GetDateTime(11);
1375 MembershipUser u = new MembershipUser(this.Name,
1386 lastPasswordChangedDate,
1393 /// Compares password values based on the MembershipPasswordFormat.
1395 /// <param name="password"></param>
1396 /// <param name="dbpassword"></param>
1397 /// <returns></returns>
1398 private bool CheckPassword(string password, string dbpassword)
1400 string pass1 = password;
1401 string pass2 = dbpassword;
1403 switch (PasswordFormat)
1405 case MembershipPasswordFormat.Encrypted:
1406 pass2 = UnEncodePassword(dbpassword);
1409 case MembershipPasswordFormat.Hashed:
1410 pass1 = EncodePassword(password);
1417 if (pass1.Equals(pass2))
1424 /// Encrypts, Hashes, or leaves the password clear based on the PasswordFormat.
1426 /// <param name="password"></param>
1427 /// <returns></returns>
1428 private string EncodePassword(string password)
1430 if (string.IsNullOrEmpty(password))
1433 string encodedPassword = password;
1435 switch (PasswordFormat)
1437 case MembershipPasswordFormat.Clear:
1440 case MembershipPasswordFormat.Encrypted:
1441 encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
1444 case MembershipPasswordFormat.Hashed:
1445 HMACSHA1 hash = new HMACSHA1();
1446 if (machineKeyIsAutoGenerated)
1447 hash.Key = MachineKeySectionUtils.ValidationKeyBytes ();
1449 hash.Key = HexToByte(m_MachineKey.ValidationKey);
1450 encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
1454 throw new ProviderException(Properties.Resources.ErrPwFormatNotSupported);
1457 return encodedPassword;
1461 /// Decrypts or leaves the password clear based on the PasswordFormat.
1463 /// <param name="encodedPassword"></param>
1464 /// <returns></returns>
1465 private string UnEncodePassword(string encodedPassword)
1467 string password = encodedPassword;
1469 switch (PasswordFormat)
1471 case MembershipPasswordFormat.Clear:
1474 case MembershipPasswordFormat.Encrypted:
1475 password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
1478 case MembershipPasswordFormat.Hashed:
1479 throw new ProviderException(Properties.Resources.ErrCantDecodeHashedPw);
1482 throw new ProviderException(Properties.Resources.ErrPwFormatNotSupported);
1489 /// Converts a hexadecimal string to a byte array. Used to convert encryption
1490 /// key values from the configuration.
1492 /// <param name="hexString"></param>
1493 /// <returns></returns>
1494 private byte[] HexToByte(string hexString)
1496 byte[] returnBytes = new byte[hexString.Length / 2];
1497 for (int i = 0; i < returnBytes.Length; i++)
1498 returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
1504 /// A helper method that performs the checks and updates associated with
1505 /// password failure tracking.
1507 /// <param name="username"></param>
1508 /// <param name="failType"></param>
1509 private void UpdateFailureCount(string username, FailureType failType)
1511 DateTime windowStart = new DateTime();
1512 int failureCount = 0;
1514 using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
1516 // Fetch user data from database
1517 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1519 dbCommand.CommandText = string.Format("SELECT \"FailedPasswordAttemptCount\", \"FailedPasswordAttemptWindowStart\", \"FailedPasswordAnswerAttemptCount\", \"FailedPasswordAnswerAttemptWindowStart\" FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1521 AddParameter (dbCommand,"@Username", username);
1522 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1527 dbCommand.Prepare();
1529 using (SqliteDataReader reader = dbCommand.ExecuteReader())
1535 if (failType.Equals(FailureType.Password))
1537 failureCount = reader.GetInt32(0);
1538 windowStart = reader.GetDateTime(1);
1540 else if (failType.Equals(FailureType.PasswordAnswer))
1542 failureCount = reader.GetInt32(2);
1543 windowStart = reader.GetDateTime(3);
1548 catch (SqliteException e)
1550 Trace.WriteLine(e.ToString());
1551 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1560 // Calculate failture count and update database
1561 using (SqliteCommand dbCommand = dbConn.CreateCommand())
1563 DateTime windowEnd = windowStart.AddMinutes(m_PasswordAttemptWindow);
1567 if (failureCount == 0 || DateTime.Now > windowEnd)
1569 // First password failure or outside of PasswordAttemptWindow.
1570 // Start a new password failure count from 1 and a new window starting now.
1572 if (failType.Equals(FailureType.Password))
1574 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"FailedPasswordAttemptCount\" = @Count, \"FailedPasswordAttemptWindowStart\" = @WindowStart WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1576 else if (failType.Equals(FailureType.PasswordAnswer))
1578 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"FailedPasswordAnswerAttemptCount\" = @Count, \"FailedPasswordAnswerAttemptWindowStart\" = @WindowStart WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1581 AddParameter (dbCommand,"@Count", 1);
1582 AddParameter (dbCommand,"@WindowStart", DateTime.Now);
1583 AddParameter (dbCommand,"@Username", username);
1584 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1586 if (dbCommand.ExecuteNonQuery() < 0)
1587 throw new ProviderException(Properties.Resources.ErrCantUpdateFailtureCountAndWindowStart);
1593 if (failureCount >= m_MaxInvalidPasswordAttempts)
1595 // Password attempts have exceeded the failure threshold. Lock out
1597 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"IsLockedOut\" = @IsLockedOut, \"LastLockedOutDate\" = @LastLockedOutDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1599 AddParameter (dbCommand,"@IsLockedOut", true);
1600 AddParameter (dbCommand,"@LastLockedOutDate", DateTime.Now);
1601 AddParameter (dbCommand,"@Username", username);
1602 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1604 if (dbCommand.ExecuteNonQuery() < 0)
1605 throw new ProviderException(string.Format(Properties.Resources.ErrCantLogoutUser, username));
1609 // Password attempts have not exceeded the failure threshold. Update
1610 // the failure counts. Leave the window the same.
1611 if (failType.Equals(FailureType.Password))
1613 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"FailedPasswordAttemptCount\" = @Count WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1615 else if (failType.Equals(FailureType.PasswordAnswer))
1617 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"FailedPasswordAnswerAttemptCount\" = @Count WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_TableName);
1620 AddParameter (dbCommand,"@Count", failureCount);
1621 AddParameter (dbCommand,"@Username", username);
1622 AddParameter (dbCommand,"@ApplicationName", m_ApplicationName);
1624 if (dbCommand.ExecuteNonQuery() < 0)
1625 throw new ProviderException(Properties.Resources.ErrCantUpdateFailtureCount);
1629 catch (SqliteException e)
1631 Trace.WriteLine(e.ToString());
1632 throw new ProviderException(Properties.Resources.ErrOperationAborted);
1643 private enum FailureType