//***************************************************************************** // This code file is part of the Universal Provider Framework for SharePoint. // This file was written by Adam Buenz [WSS MVP] of ARB Security Solutions, LLC // http://www.sharepointsecurity.com // // This file and its parts is free for re-distribution, for use in both free // and commercial applications, however this header must remain intact for legal // use. The Universal Provider Framework is distributed in the hope that it will // be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. //***************************************************************************** //************************************** // Current Version: 1.0.0.0 (Beta) //************************************** // namespace references using System; using System.Collections.Specialized; using System.Configuration; using System.Configuration.Provider; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web.Hosting; using System.Web.Security; namespace Universal.SharePointProvider.Framework { /// /// Expose and inherit out of the membership services /// public abstract class SharePointMembershipProvider : MembershipProvider { /// /// Declare constructor for the SharePointMembershipProvider class /// /// /// /// protected SharePointMembershipProvider(string defaultName, string defaultDescription, DbProviderFactory provider) { this.eventLog = "Application"; this.exceptionMessage = "An exception has occurred with the SharePoint membership provider. Please contact your SharePoint administrator."; this.defaultName = defaultName; this.defaultDescription = defaultDescription; this.provider = provider; } /// /// Declare neccesary variables /// private bool _EnablePasswordReset; private bool _EnablePasswordRetrieval; private int _MaxInvalidPasswordAttempts; private int _MinRequiredNonalphanumericCharacters; private int _MinRequiredPasswordLength; private int _PasswordAttemptWindow; private bool _RequiresQuestionAndAnswer; private bool _RequiresUniqueEmail; private string connectionString; private string defaultDescription; private string defaultName; private string eventLog; private string exceptionMessage; private string pApplicationName; private MembershipPasswordFormat passwordFormat; private ConnectionStringSettings pConnectionStringSettings; private readonly DbProviderFactory provider; private bool pWriteExceptionsToEventLog; private SharePointUsersProvider usersProvider; /// /// Declare relevant properties /// public override string ApplicationName { get { return this.pApplicationName; } set { this.pApplicationName = value; } } public override bool EnablePasswordReset { get { return this._EnablePasswordReset; } } public override bool EnablePasswordRetrieval { get { return this._EnablePasswordRetrieval; } } public override int MaxInvalidPasswordAttempts { get { return this._MaxInvalidPasswordAttempts; } } public override int MinRequiredNonAlphanumericCharacters { get { return this._MinRequiredNonalphanumericCharacters; } } public override int MinRequiredPasswordLength { get { return this._MinRequiredPasswordLength; } } public override int PasswordAttemptWindow { get { return this._PasswordAttemptWindow; } } public override MembershipPasswordFormat PasswordFormat { get { return this.passwordFormat; } } public override string PasswordStrengthRegularExpression { get { return ""; } } public override bool RequiresQuestionAndAnswer { get { return this._RequiresQuestionAndAnswer; } } public override bool RequiresUniqueEmail { get { return this._RequiresUniqueEmail; } } public bool WriteExceptionsToEventLog { get { return this.pWriteExceptionsToEventLog; } set { this.pWriteExceptionsToEventLog = value; } } protected virtual void ApplyParameterInfo(DbParameter parameter, DbType dbType, object value) { // Set the DBtype parameter parameter.DbType = dbType; // Set the value of the parameter parameter.Value = value; } /// /// Update the users password in the custom pluggable data store /// /// /// /// /// public override bool ChangePassword(string username, string oldPassword, string newPassword) { string message = null; if ((string.IsNullOrEmpty(username) || string.IsNullOrEmpty(oldPassword)) || (string.IsNullOrEmpty(newPassword) || !this.IsStrongPassword(newPassword, out message))) { return false; } bool flagValid = this.CheckUsersPassword(username, oldPassword); if (flagValid) { this.UpdateMembershipParameter(username, new object[] { "lastpasswordchangeddate", DbType.DateTime, DateTime.Now, "password", DbType.String, this.EncodePassword(newPassword) }); } return flagValid; } /// /// Update the PasswordQuestionandAnswer in the custom data store /// /// /// /// /// /// public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { if ((string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) || (string.IsNullOrEmpty(newPasswordQuestion) || string.IsNullOrEmpty(newPasswordAnswer))) { return false; } bool flagValid = this.CheckUsersPassword(username, password); if (flagValid) { this.UpdateMembershipParameter(username, new object[] { "passwordquestion", DbType.String, newPasswordQuestion, "passwordanswer", DbType.String, this.EncodePassword(newPasswordAnswer) }); } return flagValid; } /// /// Check the user answer from the custom membership data store /// /// /// /// /// private string CheckUsersAnswer(string username, string answer, bool retrivePassword) { if (string.IsNullOrEmpty(username)) { throw new ArgumentNullException("username"); } UserData userInformation = this.GetUserMembershipByUserName(username); // Check whether the username is available within the custom datastore if (userInformation == null) { throw new MembershipPasswordException("The provided SharePoint user name could not found."); } // Check whether the user is currently locked out if (userInformation.membershipUser.IsLockedOut) { throw new MembershipPasswordException("The provided SharePoint user is currently locked out."); } string confirmationAnswer = userInformation.answer; if (this.PasswordFormat == MembershipPasswordFormat.Encrypted) { confirmationAnswer = this.DecodePassword(userInformation.answer); } else if (this.PasswordFormat == MembershipPasswordFormat.Hashed) { answer = this.EncodePassword(answer); } if (this.RequiresQuestionAndAnswer && (confirmationAnswer.ToLower() != answer.ToLower())) { if (!this.GetFailedAttempts(username, false)) { this.UpdateMembershipParameter(username, new object[] { "islockedout", DbType.Boolean, true, "lastlockoutdate", DbType.DateTime, DateTime.Now }); } throw new MembershipPasswordException("Incorrect password answer provided."); } if (retrivePassword) { return this.DecodePassword(userInformation.password); } return null; } /// /// Check the users password from the custom membership data store /// /// /// /// private bool CheckUsersPassword(string username, string password) { // determine whether the username or password is empty if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { return false; } UserData userInformation = this.GetUserMembershipByUserName(username); bool flagValid = ((userInformation != null) && userInformation.membershipUser.IsApproved) && !userInformation.membershipUser.IsLockedOut; if (flagValid) { string passwordString = userInformation.password; if (this.PasswordFormat == MembershipPasswordFormat.Encrypted) { passwordString = this.DecodePassword(userInformation.password); } else if (this.PasswordFormat == MembershipPasswordFormat.Hashed) { password = this.EncodePassword(password); } flagValid = passwordString == password; if (!flagValid && !this.GetFailedAttempts(username, true)) { this.UpdateMembershipParameter(username, new object[] { "islockedout", DbType.Boolean, true, "lastlockoutdate", DbType.DateTime, DateTime.Now }); } } return flagValid; } /// /// Specify command creation /// /// /// /// private DbCommand CreateCommand(string commandText, DbConnection connection) { DbCommand membershipDbCommand = this.provider.CreateCommand(); membershipDbCommand.Connection = connection; membershipDbCommand.CommandText = commandText; return membershipDbCommand; } /// /// Specify Parameter Creation /// /// /// /// /// private DbParameter CreateParameter(string name, DbType dbType, object value) { DbParameter membershipDbParameter = this.provider.CreateParameter(); membershipDbParameter.ParameterName = name; this.ApplyParameterInfo(membershipDbParameter, dbType, value); return membershipDbParameter; } /// /// Specify Parameter Creation /// /// /// /// /// /// private DbParameter CreateParameter(string name, DbType dbType, int size, object value) { DbParameter membershipDbParameter = this.provider.CreateParameter(); membershipDbParameter.ParameterName = name; membershipDbParameter.Size = size; this.ApplyParameterInfo(membershipDbParameter, dbType, value); return membershipDbParameter; } /// /// Create a New User In The Custom Membership Data Store /// /// /// /// /// /// /// /// /// /// public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object userId, out MembershipCreateStatus status) { DbConnection createUserDbConnection = null; IDbCommand createUserDbCommand = null; MembershipCreateStatus userCreationStatus = MembershipCreateStatus.Success; MembershipUser sharepointUser = null; try { string message; if (this.RequiresQuestionAndAnswer && string.IsNullOrEmpty(passwordQuestion)) { userCreationStatus = MembershipCreateStatus.InvalidQuestion; throw new ProviderException("The question provided is not a valid question."); } if (this.RequiresQuestionAndAnswer && string.IsNullOrEmpty(passwordAnswer)) { userCreationStatus = MembershipCreateStatus.InvalidAnswer; throw new ProviderException("The answer provided is not a valid answer."); } if (string.IsNullOrEmpty(username)) { userCreationStatus = MembershipCreateStatus.InvalidUserName; throw new ProviderException("The user name provided is not a valid SharePoint username."); } if (!this.IsStrongPassword(password, out message)) { userCreationStatus = MembershipCreateStatus.InvalidPassword; throw new ProviderException(message); } password = this.EncodePassword(password); passwordAnswer = this.EncodePassword(passwordAnswer); DateTime currentTime = DateTime.Now; createUserDbConnection = this.provider.CreateConnection(); createUserDbConnection.ConnectionString = this.connectionString; createUserDbConnection.Open(); DbTransaction membershipDbTransaction = createUserDbConnection.BeginTransaction(IsolationLevel.ReadCommitted); try { if ((userId != null) && !(userId is Guid)) { throw new ArgumentException("userId"); } if (userId == null) { userId = Guid.NewGuid(); } this.usersProvider.CreateUser(createUserDbConnection, false, username, userId, true, out status); if (status != MembershipCreateStatus.Success) { userCreationStatus = status; return sharepointUser; } createUserDbCommand = this.CreateCommand("INSERT INTO aspnet_membership (email, passwordquestion, passwordanswer, isapproved, islockedout, creationdate, lastpasswordchangeddate, lastlockoutdate, userid, password) VALUES (:email, :passwordquestion, :passwordanswer, :isapproved, :islockedout, :creationdate, :lastpasswordchangeddate, :lastlockoutdate, :userid, :password)", createUserDbConnection); createUserDbCommand.Parameters.Add(this.CreateParameter("email", DbType.String, 0xff, email)); createUserDbCommand.Parameters.Add(this.CreateParameter("passwordquestion", DbType.String, 0xff, passwordQuestion)); createUserDbCommand.Parameters.Add(this.CreateParameter("passwordanswer", DbType.String, 0xff, passwordAnswer)); createUserDbCommand.Parameters.Add(this.CreateParameter("isapproved", DbType.Boolean, isApproved)); createUserDbCommand.Parameters.Add(this.CreateParameter("islockedout", DbType.Boolean, false)); createUserDbCommand.Parameters.Add(this.CreateParameter("creationdate", DbType.DateTime, currentTime)); createUserDbCommand.Parameters.Add(this.CreateParameter("lastpasswordchangeddate", DbType.DateTime, currentTime)); createUserDbCommand.Parameters.Add(this.CreateParameter("lastlockoutdate", DbType.DateTime, currentTime)); createUserDbCommand.Parameters.Add(this.CreateParameter("userid", DbType.Guid, 0xff, userId)); createUserDbCommand.Parameters.Add(this.CreateParameter("password", DbType.String, 0xff, password)); createUserDbCommand.ExecuteNonQuery(); sharepointUser = new MembershipUser(this.Name, username, userId, email, passwordQuestion, null, isApproved, false, currentTime, currentTime, currentTime, currentTime, currentTime); membershipDbTransaction.Commit(); } catch (Exception exception) { membershipDbTransaction.Rollback(); throw exception; } } catch (ProviderException exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "CreateUser"); } if (userCreationStatus == MembershipCreateStatus.Success) { userCreationStatus = MembershipCreateStatus.ProviderError; } throw exception; } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "CreateUser"); } throw new ProviderException(this.exceptionMessage, exception); } finally { if (createUserDbConnection != null) { createUserDbConnection.Close(); } } status = userCreationStatus; return sharepointUser; } /// /// Decore the userpassword from the custom membership store for consumption /// /// /// internal string DecodePassword(string pass) { string decPass; if (pass == null) { return null; } switch (this.passwordFormat) { case MembershipPasswordFormat.Clear: return pass; case MembershipPasswordFormat.Hashed: throw new ProviderException("The SharePoint membership provider could not decode the hashed password."); } try { byte[] convertBuffer = Convert.FromBase64String(pass); byte[] decryptBuffer = this.DecryptPassword(convertBuffer); if (decryptBuffer == null) { return null; } decPass = Encoding.Unicode.GetString(decryptBuffer, 0x10, decryptBuffer.Length - 0x10); } catch (Exception) { throw new ProviderException("The SharePoint membership provider could not decode the hashed password."); } return decPass; } /// /// Delete a user from the custom membership data store /// /// /// /// public override bool DeleteUser(string username, bool deleteAllRelatedData) { DbConnection deleteUserDbConnection = this.provider.CreateConnection(); deleteUserDbConnection.ConnectionString = this.connectionString; // Confirm the deleation of the user bool flagConfirm = false; try { deleteUserDbConnection.Open(); DbTransaction deleteUserDbTransaction = deleteUserDbConnection.BeginTransaction(IsolationLevel.ReadCommitted); try { flagConfirm = this.usersProvider.DeleteUser(deleteUserDbConnection, username, SharePointUsersProvider.ProviderType.All); deleteUserDbTransaction.Commit(); return flagConfirm; } catch (Exception exception) { deleteUserDbTransaction.Rollback(); throw exception; } } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "DeleteUser"); } throw new ProviderException(this.exceptionMessage, exception); } finally { deleteUserDbConnection.Close(); } return flagConfirm; } /// /// Encode the user password using a salt to hinder dictionary attacks against the custom membership data store /// /// /// internal string EncodePassword(string pass) { string encPass; try { byte[] secondaryBuffer; if (pass == null) { return null; } string randomValues = this.GenerateSalt(); if (this.passwordFormat == MembershipPasswordFormat.Clear) { return pass; } byte[] primaryBuffer = Encoding.Unicode.GetBytes(pass); byte[] encryptBuffer = null; if (this.passwordFormat == MembershipPasswordFormat.Encrypted) { byte[] randomBuffer = Convert.FromBase64String(randomValues); secondaryBuffer = new byte[randomBuffer.Length + primaryBuffer.Length]; Buffer.BlockCopy(randomBuffer, 0, secondaryBuffer, 0, randomBuffer.Length); Buffer.BlockCopy(primaryBuffer, 0, secondaryBuffer, randomBuffer.Length, primaryBuffer.Length); encryptBuffer = this.EncryptPassword(secondaryBuffer); } else { secondaryBuffer = new byte[primaryBuffer.Length]; Buffer.BlockCopy(primaryBuffer, 0, secondaryBuffer, 0, primaryBuffer.Length); HashAlgorithm encodeAlgorithm = HashAlgorithm.Create(Membership.HashAlgorithmType); if (encodeAlgorithm == null) { throw new ProviderException("Undefined Hash Algorithm Provided"); } encryptBuffer = encodeAlgorithm.ComputeHash(secondaryBuffer); } encPass = Convert.ToBase64String(encryptBuffer); } catch (Exception exception) { if (exception is ProviderException) { throw exception; } throw new ProviderException("The SharePoint membership provider could not encode the password."); } return encPass; } /// /// Find all the users by matching their email addresses /// /// /// /// /// /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { if (string.IsNullOrEmpty(emailToMatch)) { throw new ArgumentNullException("emailToMatch"); } MembershipUserCollection userCollection = this.QueryAllUsers("email", emailToMatch.ToLower(), pageIndex, pageSize, out totalRecords, false); totalRecords = userCollection.Count; return userCollection; } /// /// Find users by their specified names /// /// /// /// /// /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { if (string.IsNullOrEmpty(usernameToMatch)) { throw new ArgumentNullException("usernameToMatch"); } MembershipUserCollection userCollection = this.QueryAllUsers("username", usernameToMatch.ToLower(), pageIndex, pageSize, out totalRecords, false); totalRecords = userCollection.Count; return userCollection; } /// /// Generate the salt for encrypting the password with random characters to hinder dictionary attacks /// /// internal string GenerateSalt() { byte[] saltBuffer = new byte[0x10]; new RNGCryptoServiceProvider().GetBytes(saltBuffer); return Convert.ToBase64String(saltBuffer); } /// /// Get all the users /// /// /// /// /// public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { return this.QueryAllUsers(null, null, pageIndex, pageSize, out totalRecords, true); } /// /// Get all the failed attempts against the custom membership datastore /// /// /// /// private bool GetFailedAttempts(string userName, bool normalLogin) { string dbAttempt; DbConnection failedAttemptConnection = this.provider.CreateConnection(); failedAttemptConnection.ConnectionString = this.connectionString; if (normalLogin) { dbAttempt = "SELECT m.failedpasswordattemptcount, m.failedpasswordattemptstart FROM aspnet_membership m, aspnet_users u WHERE u.userid = m.userid AND LOWER(u.applicationname) = :applicationname AND LOWER(u.UserName) = :username"; } else { dbAttempt = "SELECT m.failedpasswordanswercount, m.failedpasswordanswerstart FROM aspnet_membership m, aspnet_users u WHERE u.userid = m.userid AND LOWER(u.applicationname) = :applicationname AND LOWER(u.UserName) = :username"; } IDbCommand failedAttemptCommand = this.CreateCommand(dbAttempt, failedAttemptConnection); failedAttemptCommand.Parameters.Add(this.CreateParameter("applicationname", DbType.String, 0xff, this.ApplicationName.ToLower())); failedAttemptCommand.Parameters.Add(this.CreateParameter("username", DbType.String, 0xff, userName.ToLower())); int metric = 0; DateTime currentTime = new DateTime(); try { failedAttemptConnection.Open(); IDataReader failedAttemptReader = failedAttemptCommand.ExecuteReader(); if (failedAttemptReader.Read()) { metric = failedAttemptReader.GetInt32(0); currentTime = failedAttemptReader.GetDateTime(1); } failedAttemptReader.Close(); } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "GetFailedAttempts"); } throw new ProviderException(this.exceptionMessage, exception); } finally { failedAttemptConnection.Close(); } bool flagLogin = true; if (currentTime <= (DateTime.Now - new TimeSpan(this.PasswordAttemptWindow * 0x23c34600))) { metric = 0; } else if (metric >= (this.MaxInvalidPasswordAttempts - 1)) { flagLogin = false; } if (normalLogin) { this.UpdateMembershipParameter(userName, new object[] { "failedpasswordattemptcount", DbType.Int32, metric + 1, "failedpasswordattemptstart", DbType.DateTime, DateTime.Now }); return flagLogin; } this.UpdateMembershipParameter(userName, new object[] { "failedpasswordanswercount", DbType.Int32, metric + 1, "failedpasswordanswerstart", DbType.DateTime, DateTime.Now }); return flagLogin; } /// /// Get memberships based on the passed username from the custom membership datastore /// /// /// /// /// /// /// private MembershipUser GetMembershipByUserName(string parameterName, DbType dbType, object value, bool useLike, bool caseSensitive) { DbConnection getMemByUserNameConnection = this.provider.CreateConnection(); getMemByUserNameConnection.ConnectionString = this.connectionString; string userInfo = "SELECT username, u.userid, email, passwordquestion, comments, isapproved, islockedout, creationdate, lastlogindate, lastactivitydate, lastpasswordchangeddate, lastlockoutdate FROM aspnet_membership m, aspnet_users u WHERE u.userid=m.userid AND LOWER(u.applicationname) = :applicationname AND "; if (parameterName == "userid") { userInfo = userInfo + "u."; } if (caseSensitive) { if (useLike) { userInfo = userInfo + parameterName + " LIKE :" + parameterName; } else { userInfo = userInfo + parameterName + " = :" + parameterName; } } else if (useLike) { userInfo = userInfo + "LOWER(" + parameterName + ") LIKE :" + parameterName; } else { userInfo = userInfo + "LOWER(" + parameterName + ") = :" + parameterName; } IDbCommand getMemByUserNameCommand = this.CreateCommand(userInfo, getMemByUserNameConnection); getMemByUserNameCommand.Parameters.Add(this.CreateParameter("applicationname", DbType.String, 0xff, this.ApplicationName.ToLower())); getMemByUserNameCommand.Parameters.Add(this.CreateParameter(parameterName, dbType, value)); MembershipUser sharepointUser = null; try { getMemByUserNameConnection.Open(); IDataReader getMemByUserNameReader = getMemByUserNameCommand.ExecuteReader(); if (getMemByUserNameReader.Read()) { string userName = getMemByUserNameReader.GetString(0); Guid nameGUID = (Guid) this.ReadField(getMemByUserNameReader, 1, DbType.Guid); string email = getMemByUserNameReader.GetString(2); string passQues = getMemByUserNameReader.GetString(3); string comments = getMemByUserNameReader.GetString(4); bool flagApprove = getMemByUserNameReader.GetBoolean(5); bool flagLook = getMemByUserNameReader.GetBoolean(6); DateTime creationDate = getMemByUserNameReader.GetDateTime(7); DateTime lastLoginDate = getMemByUserNameReader.GetDateTime(8); DateTime lastActiveDate = getMemByUserNameReader.GetDateTime(9); DateTime lastPassChange = getMemByUserNameReader.GetDateTime(10); DateTime lastLook = getMemByUserNameReader.GetDateTime(11); sharepointUser = new MembershipUser(this.Name, userName, nameGUID, email, passQues, comments, flagApprove, flagLook, creationDate, lastLoginDate, lastActiveDate, lastPassChange, lastLook); } getMemByUserNameReader.Close(); } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "GetUserMembershipByUserName"); } throw new ProviderException(this.exceptionMessage, exception); } finally { getMemByUserNameConnection.Close(); } return sharepointUser; } /// /// Get the number of users that are online from the membership data store /// /// public override int GetNumberOfUsersOnline() { DbConnection numUserOnlineConnection = this.provider.CreateConnection(); numUserOnlineConnection.ConnectionString = this.connectionString; IDbCommand numUserOnlineCommand = this.CreateCommand("SELECT COUNT(*) FROM aspnet_users WHERE LOWER(applicationname) = :applicationname AND lastactivitydate >= :lastactivitydate1 AND lastactivitydate <= :lastactivitydate2", numUserOnlineConnection); numUserOnlineCommand.Parameters.Add(this.CreateParameter("applicationname", DbType.String, 0xff, this.ApplicationName.ToLower())); DateTime curentTime = DateTime.Now; numUserOnlineCommand.Parameters.Add(this.CreateParameter("lastactivitydate1", DbType.DateTime, curentTime - new TimeSpan(Membership.UserIsOnlineTimeWindow * 0x23c34600))); numUserOnlineCommand.Parameters.Add(this.CreateParameter("lastactivitydate2", DbType.DateTime, curentTime)); int metric = 0; try { numUserOnlineConnection.Open(); IDataReader numUserOnlineReader = numUserOnlineCommand.ExecuteReader(); if (numUserOnlineReader.Read()) { metric = numUserOnlineReader.GetInt32(0); } numUserOnlineReader.Close(); } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "GetNumberOfUsersOnline"); } throw new ProviderException(this.exceptionMessage, exception); } finally { numUserOnlineConnection.Close(); } return metric; } /// /// Get the password of a user based on the passed username from the custom membership data store /// /// /// /// public override string GetPassword(string username, string answer) { if (!this.EnablePasswordRetrieval) { throw new NotSupportedException(); } if (this.PasswordFormat == MembershipPasswordFormat.Hashed) { throw new ProviderException("The SharePoint membership provider could not retrieve hashed passwords."); } this.GetUserMembershipByUserName(username); return this.CheckUsersAnswer(username, answer, true); } /// /// Get a specific user by passing in the userID /// /// /// /// public override MembershipUser GetUser(object userId, bool userIsOnline) { if (userId == null) { throw new ArgumentNullException("providerUserKey"); } if (!(userId is Guid)) { throw new ArgumentException("invalid providerUserKey"); } MembershipUser sharepointUser = this.GetMembershipByUserName("userid", DbType.Guid, userId, false, true); if ((sharepointUser != null) && userIsOnline) { this.usersProvider.UpdateUserParameter(this.connectionString, sharepointUser.UserName, new object[] { "lastactivitydate", DbType.DateTime, DateTime.Now }); } return sharepointUser; } /// /// Get a user from the custom membership data store /// /// /// /// public override MembershipUser GetUser(string username, bool userIsOnline) { if (string.IsNullOrEmpty(username)) { throw new ArgumentNullException("username"); } MembershipUser sharepointUser = this.GetMembershipByUserName("username", DbType.String, username.ToLower(), false, false); if ((sharepointUser != null) && userIsOnline) { this.usersProvider.UpdateUserParameter(this.connectionString, username, new object[] { "lastactivitydate", DbType.DateTime, DateTime.Now }); } return sharepointUser; } /// /// Get the user membership by passing in the specified username /// /// /// private UserData GetUserMembershipByUserName(string username) { DbConnection getUseByMemConnection = this.provider.CreateConnection(); getUseByMemConnection.ConnectionString = this.connectionString; IDbCommand getUseByMemCommand = this.CreateCommand("SELECT password, u.userid, email, passwordquestion, passwordanswer, comments, isapproved, islockedout, creationdate, lastlogindate, lastactivitydate, lastpasswordchangeddate, lastlockoutdate FROM aspnet_membership m, aspnet_users u WHERE u.userid=m.userid AND LOWER(u.applicationname) = :applicationname AND LOWER(u.username) = :username", getUseByMemConnection); getUseByMemCommand.Parameters.Add(this.CreateParameter("applicationname", DbType.String, 0xff, this.ApplicationName.ToLower())); getUseByMemCommand.Parameters.Add(this.CreateParameter("username", DbType.String, 0xff, username.ToLower())); UserData userInformation = null; try { getUseByMemConnection.Open(); IDataReader getUseByMemReader = getUseByMemCommand.ExecuteReader(); if (getUseByMemReader.Read()) { string pass = getUseByMemReader.GetString(0); Guid userID = (Guid) this.ReadField(getUseByMemReader, 1, DbType.Guid); string email = getUseByMemReader.GetString(2); string passQuestion = getUseByMemReader.GetString(3); string passAnswer = getUseByMemReader.GetString(4); string comments = getUseByMemReader.GetString(5); bool approve = getUseByMemReader.GetBoolean(6); bool lockedOut = getUseByMemReader.GetBoolean(7); DateTime creationDate = getUseByMemReader.GetDateTime(8); DateTime lastLoginDate = getUseByMemReader.GetDateTime(9); DateTime lastActivityDate = getUseByMemReader.GetDateTime(10); DateTime lastPasswordChangeDate = getUseByMemReader.GetDateTime(11); DateTime lastLockoutDate = getUseByMemReader.GetDateTime(12); MembershipUser sharepointUser = new MembershipUser(this.Name, username, userID, email, passQuestion, comments, approve, lockedOut, creationDate, lastLoginDate, lastActivityDate, lastPasswordChangeDate, lastLockoutDate); userInformation = new UserData(sharepointUser, pass, passAnswer); } getUseByMemReader.Close(); } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "GetUserMembershipByUserName"); } throw new ProviderException(this.exceptionMessage, exception); } finally { getUseByMemConnection.Close(); } return userInformation; } /// /// Get the specified username by passing in a specific email /// /// /// public override string GetUserNameByEmail(string email) { if (string.IsNullOrEmpty(email)) { throw new ArgumentNullException("email"); } MembershipUser sharepointUser = this.GetMembershipByUserName("email", DbType.String, email.ToLower(), false, false); if (sharepointUser == null) { return ""; } return sharepointUser.UserName; } /// /// Call the custom DbUsersProvider class /// /// internal abstract SharePointUsersProvider GetUsersProvider(); /// /// Intialization based on configuration setttings /// /// /// public override void Initialize(string name, NameValueCollection config) { if (config == null) { throw new ArgumentNullException("config"); } if (string.IsNullOrEmpty(name)) { name = this.defaultName; } if (string.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", this.defaultDescription); } base.Initialize(name, config); string pass = config["passwordFormat"]; if (string.IsNullOrEmpty(pass)) { pass = "Hashed"; } if (pass == "Encrypted") { this.passwordFormat = MembershipPasswordFormat.Encrypted; } else if (pass == "Hashed") { this.passwordFormat = MembershipPasswordFormat.Hashed; } else { if (pass != "Clear") { throw new ProviderException("SharePoint membership provider bad password format has been specified"); } this.passwordFormat = MembershipPasswordFormat.Clear; } config.Remove("passwordFormat"); this._EnablePasswordRetrieval = GeneralUtilities.GetBooleanValue(config, "enablePasswordRetrieval", false); config.Remove("enablePasswordRetrieval"); if ((this.PasswordFormat == MembershipPasswordFormat.Hashed) && this._EnablePasswordRetrieval) { throw new ProviderException("Cannot retrieve Hashed passwords."); } this._EnablePasswordReset = GeneralUtilities.GetBooleanValue(config, "enablePasswordReset", true); config.Remove("enablePasswordReset"); this._RequiresQuestionAndAnswer = GeneralUtilities.GetBooleanValue(config, "requiresQuestionAndAnswer", true); config.Remove("requiresQuestionAndAnswer"); this._RequiresUniqueEmail = GeneralUtilities.GetBooleanValue(config, "requiresUniqueEmail", true); config.Remove("requiresUniqueEmail"); this._MaxInvalidPasswordAttempts = GeneralUtilities.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 100); config.Remove("maxInvalidPasswordAttempts"); this._PasswordAttemptWindow = GeneralUtilities.GetIntValue(config, "passwordAttemptWindow", 10, false, 0); config.Remove("passwordAttemptWindow"); this._MinRequiredPasswordLength = GeneralUtilities.GetIntValue(config, "minRequiredPasswordLength", 7, false, 0x80); config.Remove("minRequiredPasswordLength"); this._MinRequiredNonalphanumericCharacters = GeneralUtilities.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); config.Remove("minRequiredNonalphanumericCharacters"); this.pApplicationName = config["ApplicationName"]; if (string.IsNullOrEmpty(this.pApplicationName)) { this.pApplicationName = HostingEnvironment.ApplicationVirtualPath; } config.Remove("ApplicationName"); this.pConnectionStringSettings = ConfigurationManager.ConnectionStrings[config["connectionStringName"]]; config.Remove("connectionStringName"); if (this.pConnectionStringSettings == null) { this.connectionString = ""; } else { this.connectionString = this.pConnectionStringSettings.ConnectionString.Trim(); } if (config.Count > 0) { string unrecongizedAtt = config.GetKey(0); if (!string.IsNullOrEmpty(unrecongizedAtt)) { throw new ProviderException("Arbitrary unrecognizable attribute related to the SharePoint membership provider: " + unrecongizedAtt); } } this.usersProvider = this.GetUsersProvider(); } /// /// Determine the strength of a password and whether it satisified stated conditions /// /// /// /// private bool IsStrongPassword(string newPassword, out string errorMessage) { errorMessage = null; if ((newPassword == null) || (newPassword.Length < this.MinRequiredPasswordLength)) { errorMessage = "The provided SharePoint member password is shorter than" + this.MinRequiredPasswordLength.ToString(CultureInfo.InvariantCulture); return false; } int metric = 0; for (int i = 0; i < newPassword.Length; i++) { if (!char.IsLetterOrDigit(newPassword, i)) { metric++; } } if (metric < this.MinRequiredNonAlphanumericCharacters) { errorMessage = "The provided SharePoint member password requires non-alphanumeric characters greater than " + this.MinRequiredNonAlphanumericCharacters.ToString(CultureInfo.InvariantCulture); return false; } if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(newPassword, this.PasswordStrengthRegularExpression)) { errorMessage = "The provided SharePoint member password does not appropriatly match regular expression"; return false; } if (newPassword.Length > 0xff) { errorMessage = "The provided SharePoint member password is too long"; return false; } return true; } private MembershipUserCollection QueryAllUsers(string stringParameterName, string likeExpression, int pageIndex, int pageSize, out int totalRecords, bool caseSensitive) { MembershipUserCollection userCollection = new MembershipUserCollection(); DbConnection queryUsersConnection = this.provider.CreateConnection(); queryUsersConnection.ConnectionString = this.connectionString; string queryText = "SELECT username, password, u.userid, email, passwordquestion, passwordanswer, comments, isapproved, islockedout, creationdate, lastlogindate, lastactivitydate, lastpasswordchangeddate, lastlockoutdate FROM aspnet_membership m, aspnet_users u WHERE u.userid = m.userid AND LOWER(u.applicationname) = :applicationname"; if (stringParameterName != null) { if (caseSensitive) { queryText = queryText + " AND " + stringParameterName + " LIKE :" + stringParameterName; } else { queryText = queryText + " AND LOWER(" + stringParameterName + ") LIKE :" + stringParameterName; } } IDbCommand queryUsersCommand = this.CreateCommand(queryText, queryUsersConnection); queryUsersCommand.Parameters.Add(this.CreateParameter("applicationname", DbType.String, 0xff, this.ApplicationName.ToLower())); if (stringParameterName != null) { queryUsersCommand.Parameters.Add(this.CreateParameter(stringParameterName, DbType.String, likeExpression)); } try { queryUsersConnection.Open(); IDataReader queryUsersReader = queryUsersCommand.ExecuteReader(); while (queryUsersReader.Read()) { string userName = queryUsersReader.GetString(0); queryUsersReader.GetString(1); Guid userID = (Guid) this.ReadField(queryUsersReader, 2, DbType.Guid); string email = queryUsersReader.GetString(3); string passQuestion = queryUsersReader.GetString(4); queryUsersReader.GetString(5); string comments = queryUsersReader.GetString(6); bool approval = queryUsersReader.GetBoolean(7); bool lockedOut = queryUsersReader.GetBoolean(8); DateTime creationDate = queryUsersReader.GetDateTime(9); DateTime lastLoginDate = queryUsersReader.GetDateTime(10); DateTime lastActivityDate = queryUsersReader.GetDateTime(11); DateTime lastPassChangeDate = queryUsersReader.GetDateTime(12); DateTime lastLockOutDate = queryUsersReader.GetDateTime(13); MembershipUser sharepointUser = new MembershipUser(this.Name, userName, userID, email, passQuestion, comments, approval, lockedOut, creationDate, lastLoginDate, lastActivityDate, lastPassChangeDate, lastLockOutDate); userCollection.Add(sharepointUser); } queryUsersReader.Close(); } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "GetAllUsers"); } throw new ProviderException(this.exceptionMessage, exception); } finally { queryUsersConnection.Close(); } totalRecords = userCollection.Count; return userCollection; } protected virtual object ReadField(IDataReader reader, int i, DbType dbType) { return reader.GetValue(i); } public override string ResetPassword(string username, string answer) { if (!this.EnablePasswordReset) { throw new NotSupportedException(); } this.CheckUsersAnswer(username, answer, false); string newPassword = Membership.GeneratePassword(this.MinRequiredPasswordLength, this.MinRequiredNonAlphanumericCharacters); this.UpdateMembershipParameter(username, new object[] { "lastpasswordchangeddate", DbType.DateTime, DateTime.Now, "password", DbType.String, this.EncodePassword(newPassword) }); return newPassword; } /// /// Unlock the user based on the passed in username /// /// /// public override bool UnlockUser(string userName) { this.UpdateMembershipParameter(userName, new object[] { "islockedout", DbType.Boolean, false, "failedpasswordanswercount", DbType.Int32, 0, "failedpasswordattemptcount", DbType.Int32, 0 }); return true; } /// /// Update various portions of the membership parameters /// /// /// private void UpdateMembershipParameter(string userName, params object[] parameters) { DbConnection updateMemDbConnection = this.provider.CreateConnection(); updateMemDbConnection.ConnectionString = this.connectionString; try { updateMemDbConnection.Open(); DbTransaction updateMemDbTransaction = updateMemDbConnection.BeginTransaction(IsolationLevel.ReadCommitted); try { bool dummyVar; object userObject = null; userObject = this.usersProvider.GetMembershipByUserName(updateMemDbConnection, "username", DbType.String, userName.ToLower(), out dummyVar, false); if (userObject != null) { string textQuery = "UPDATE aspnet_membership SET "; for (int i = 0; i < parameters.Length; i += 3) { textQuery = textQuery + ((string) parameters[i]) + " = :" + ((string) parameters[i]); if ((i + 3) < parameters.Length) { textQuery = textQuery + ", "; } else { textQuery = textQuery + " "; } } textQuery = textQuery + "WHERE userid = :userid"; IDbCommand updateMemDbCommand = this.CreateCommand(textQuery, updateMemDbConnection); for (int i = 0; i < parameters.Length; i += 3) { updateMemDbCommand.Parameters.Add(this.CreateParameter((string) parameters[i], (DbType) parameters[i + 1], parameters[i + 2])); } updateMemDbCommand.Parameters.Add(this.CreateParameter("userid", DbType.Guid, userObject)); updateMemDbCommand.ExecuteNonQuery(); updateMemDbTransaction.Commit(); } } catch (Exception exception) { updateMemDbTransaction.Rollback(); throw exception; } } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "UpdateMembershipParameter"); } throw new ProviderException(this.exceptionMessage, exception); } finally { updateMemDbConnection.Close(); } } /// /// Update the user /// /// public override void UpdateUser(MembershipUser user) { DbConnection updateUserDbConnection = this.provider.CreateConnection(); updateUserDbConnection.ConnectionString = this.connectionString; try { updateUserDbConnection.Open(); DbTransaction updateUserDbTransaction = updateUserDbConnection.BeginTransaction(IsolationLevel.ReadCommitted); try { Guid userID = this.usersProvider.UpdateUser(updateUserDbConnection, user); IDbCommand updateUserDbCommand = this.CreateCommand("UPDATE aspnet_membership SET email = :email, comments = :comments, isapproved = :isapproved WHERE userid = :userid", updateUserDbConnection); updateUserDbCommand.Parameters.Add(this.CreateParameter("email", DbType.String, 0xff, user.Email)); updateUserDbCommand.Parameters.Add(this.CreateParameter("comments", DbType.String, 0xff, user.Comment)); updateUserDbCommand.Parameters.Add(this.CreateParameter("isapproved", DbType.Boolean, user.IsApproved)); updateUserDbCommand.Parameters.Add(this.CreateParameter("userid", DbType.Guid, 0xff, userID)); updateUserDbCommand.ExecuteNonQuery(); updateUserDbTransaction.Commit(); } catch (Exception exception) { updateUserDbTransaction.Rollback(); throw exception; } } catch (Exception exception) { if (this.WriteExceptionsToEventLog) { this.WriteToEventLog(exception, "UpdateUser"); } throw new ProviderException(this.exceptionMessage, exception); } finally { updateUserDbConnection.Close(); } } /// /// Validate the user /// /// /// /// public override bool ValidateUser(string username, string password) { bool flagValid = this.CheckUsersPassword(username, password); if (flagValid) { this.UpdateMembershipParameter(username, new object[] { "lastlogindate", DbType.DateTime, DateTime.Now, "failedpasswordattemptcount", DbType.Int32, 0 }); } return flagValid; } /// /// Write an errors to the event log /// /// /// private void WriteToEventLog(Exception e, string action) { EventLog log = new EventLog(); log.Source = this.defaultName; log.Log = this.eventLog; string message = "An exception occurred communicating with the SharePoint membership store.\n\n"; message = message + "Action: " + action + "\n\n"; message = message + "Exception: " + e.ToString(); log.WriteEntry(message); } } }