using System; using System.Collections.Specialized; using System.Configuration; using System.Configuration.Provider; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Web.Configuration; using System.Web.Security; using Persistence; namespace Service.Provider { class HeuristicLabMembershipProvider : MembershipProvider { #region variables private string pApplicationName; private bool pEnablePasswordReset; private bool pEnablePasswordRetrieval; private bool pRequiresQuestionAndAnswer; private bool pRequiresUniqueEmail; private int pMaxInvalidPasswordAttempts = 3; private int pPasswordAttemptWindow; private int pMinRequiredPasswordLength = 5; private MembershipPasswordFormat pPasswordFormat = MembershipPasswordFormat.Clear; #endregion #region properties public override string ApplicationName { get { return pApplicationName; } set { pApplicationName = value; } } public override bool EnablePasswordReset { get { return pEnablePasswordReset; } } public override bool EnablePasswordRetrieval { get { return pEnablePasswordRetrieval; } } public override int MaxInvalidPasswordAttempts { get { return pMaxInvalidPasswordAttempts; } } public override int MinRequiredNonAlphanumericCharacters { get { return 0; } } public override int MinRequiredPasswordLength { get { return pMinRequiredPasswordLength; } } public override int PasswordAttemptWindow { get { return pPasswordAttemptWindow; } } public override MembershipPasswordFormat PasswordFormat { get { return pPasswordFormat; } } public override string PasswordStrengthRegularExpression { get { return string.Empty; } } public override bool RequiresQuestionAndAnswer { get { return false; } } public override bool RequiresUniqueEmail { get { return pRequiresUniqueEmail; } } #endregion /// /// this is the methode to initialize all important and needed /// variables and settings /// The most settings will be read from the web.config /// /// /// /// public override void Initialize(string name, NameValueCollection config) { // // Initialize values from web.config. // if (config == null) throw new ArgumentNullException("config"); if (string.IsNullOrEmpty(name) || name.Length == 0) name = "HeuristicLabMembershipProvider"; if (String.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", "Heuristic Lab Membership provider"); } // Initialize the abstract base class. base.Initialize(name, config); pApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath); pMaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5")); pPasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10")); pMinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7")); pEnablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true")); pEnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true")); pRequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false")); pRequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true")); string tempFormat = config["passwordFormat"]; if (tempFormat == null) { tempFormat = "Clear"; } switch (tempFormat) { case "Hashed": pPasswordFormat = MembershipPasswordFormat.Hashed; break; case "Encrypted": pPasswordFormat = MembershipPasswordFormat.Encrypted; break; case "Clear": pPasswordFormat = MembershipPasswordFormat.Clear; break; default: throw new ProviderException("Password format not supported."); } } /// /// this mehtode change the password of an existent user /// the methode look for db connection and connect to /// such db if it possible /// Also neccessary requirements of password complexity is used /// by this methode /// /// /// /// /// /// return true if password is changed, or false if it is not able /// to set the password /// public override bool ChangePassword(string username, string oldPassword, string newPassword) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { // check database connection if (db == null) { return false; } if (newPassword.Length < MinRequiredPasswordLength) { return false; } try { // try to get user HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username); if (u.Password == EncodePassword(oldPassword) && newPassword.Length > 0) { u.Password = EncodePassword(newPassword); db.SubmitChanges(); return true; } return false; } catch (Exception) { return false; } } } /// /// change the password questionandanswer for specified user /// /// /// /// /// /// /// /// return true if changes are submitted /// false if are no user found /// public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { if (db.HeuristicLabUsers.Count(x => x.UserName == username) > 0 && newPasswordQuestion.Length > 0 && newPasswordAnswer.Length > 0) { HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username); u.PasswordAnswer = newPasswordAnswer; u.PasswordQuestion = newPasswordQuestion; db.SubmitChanges(); return true; } return false; } } /// /// create a new user with username, password /// email, passwordquestion, passwordAnswer, /// Approvedstate, whether is providerUserKey and certain creation MembershipCreateStatus status /// /// /// /// /// /// /// /// /// /// /// return a created user if there is no error occoured /// return null if there is an error occoured /// public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { // check database connection if (db == null) { status = MembershipCreateStatus.ProviderError; return null; } try { // check for duplicate entries if (db.HeuristicLabUsers.Count(x => x.UserName == username) > 0) { status = MembershipCreateStatus.DuplicateUserName; return null; } if (db.HeuristicLabUsers.Count(x => x.Email == email) > 0) { status = MembershipCreateStatus.DuplicateEmail; return null; } if (password.Length < MinRequiredPasswordLength) { status = MembershipCreateStatus.InvalidPassword; return null; } // create new user HeuristicLabUser u = new HeuristicLabUser(username, email, passwordQuestion, ""); password = EncodePassword(password); u.Password = password; u.PasswordAnswer = passwordAnswer; u.PasswordQuestion = passwordQuestion; // save user into database db.HeuristicLabUsers.InsertOnSubmit(u); db.SubmitChanges(); // success status = MembershipCreateStatus.Success; return u.getMembershipUser(this.Name); } catch (Exception) { // error status = MembershipCreateStatus.ProviderError; return null; } } } public override bool DeleteUser(string username, bool deleteAllRelatedData) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { // check database connection if (db == null) { return false; } try { // try to get user HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username); // optionally delete related data if (deleteAllRelatedData) { db.HeuristicLabUserRole.DeleteAllOnSubmit(u.HeuristicLabUserRoles); } // delete user db.HeuristicLabUsers.DeleteOnSubmit(u); db.SubmitChanges(); return true; } catch (Exception) { return false; } } } /// /// Returns a paged collection of users that match a given e-mail address /// TODO: Return a sorted result set /// /// /// /// /// /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { IQueryable users = (from u in db.HeuristicLabUsers where u.Email == emailToMatch select u); return PagedCollection(users, pageIndex, pageSize, out totalRecords); } } /// /// Returns a paged collection of users that match a given username /// TODO: Return a sorted result set /// /// /// /// /// /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { IQueryable users = (from u in db.HeuristicLabUsers where u.UserName == usernameToMatch select u); return PagedCollection(users, pageIndex, pageSize, out totalRecords); } } /// /// Returns a paged collection of all users /// TODO: Return a sorted result set /// /// /// /// /// public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { // orderby u.UserName // skipped ordering for now as the default comparator seems to be wrong IQueryable users = (from u in db.HeuristicLabUsers select u); return PagedCollection(users, pageIndex, pageSize, out totalRecords); } } /// /// Helper method that takes an IQueriable object and returns a paged part of it /// /// /// /// /// /// private MembershipUserCollection PagedCollection(IQueryable querySource, int pageIndex, int pageSize, out int totalRecords) { totalRecords = querySource.Count(); MembershipUserCollection userCollection = new MembershipUserCollection(); int skip = (pageIndex == 0) ? 0 : (pageIndex * pageSize); foreach (HeuristicLabUser u in querySource.Skip(skip).Take(pageSize)) { userCollection.Add(u.getMembershipUser(this.Name)); } return userCollection; } /// /// not jet implemented returns 0 as default /// /// public override int GetNumberOfUsersOnline() { return 0; } public override string GetPassword(string username, string answer) { if (PasswordFormat == MembershipPasswordFormat.Hashed) throw new NotSupportedException("Passwort is hashed"); using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username); if (EnablePasswordRetrieval && RequiresQuestionAndAnswer) { if (u.PasswordAnswer.Equals(answer)) { return u.Password; } else { throw new MembershipPasswordException(); } } else throw new NotSupportedException("PasswortRetrival or RequiresQuestionAndAnswer not set"); } } public override MembershipUser GetUser(string username, bool userIsOnline) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username); if (u != null) return u.getMembershipUser(this.Name); else return null; } } public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { long ID = providerUserKey is long ? (long)providerUserKey : -1; using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.ID == ID); if (u != null) return u.getMembershipUser(this.Name); else return null; } } public override string GetUserNameByEmail(string email) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.Email == email); if (u != null) return u.UserName; else return null; } } public override string ResetPassword(string username, string answer) { throw new NotSupportedException("Restet password not avaliable"); } public bool LockUser(string userName) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { // check database connection if (db == null) { return false; } try { // try to get user HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == userName); // unlock user u.Locked = true; db.SubmitChanges(); return true; } catch (Exception) { return false; } } } public override bool UnlockUser(string userName) { using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { // check database connection if (db == null) { return false; } try { // try to get user HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == userName); // unlock user u.Locked = false; db.SubmitChanges(); return true; } catch (Exception) { return false; } } } public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); } /// /// Validates a user /// /// /// /// public override bool ValidateUser(string username, string password) { bool isValid = false; using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) { if (db == null) { return false; } if (db.HeuristicLabUsers.Count(x => x.UserName == username && x.Locked == false) > 0) { HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username && x.Locked == false); isValid = CheckPassword(password, u.Password) && u.FailedLogins <= MaxInvalidPasswordAttempts; if (!isValid) { u.FailedLogins++; if (u.FailedLogins > MaxInvalidPasswordAttempts) { u.Locked = true; } } else { u.FailedLogins = 0; } db.SubmitChanges(); } } return isValid; } /// /// compaiers to passwords /// /// /// /// private bool CheckPassword(string password, string dbpassword) { string pass1 = password; string pass2 = dbpassword; switch (PasswordFormat) { case MembershipPasswordFormat.Encrypted: pass2 = DecodePassword(dbpassword); break; case MembershipPasswordFormat.Hashed: pass1 = EncodePassword(password); break; default: break; } if (pass1 == pass2) { return true; } return false; } /// /// Encodes a password. /// Public because needed for unit testing. /// /// /// public string EncodePassword(string password) { string encodedPassword = password; switch (PasswordFormat) { case MembershipPasswordFormat.Clear: break; case MembershipPasswordFormat.Encrypted: encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); break; case MembershipPasswordFormat.Hashed: SHA512 sha512 = SHA512.Create(); ASCIIEncoding encoder = new ASCIIEncoding(); byte[] combined = encoder.GetBytes(password); sha512.ComputeHash(combined); encodedPassword = Convert.ToBase64String(sha512.Hash); break; default: throw new ProviderException("Unsupported password format."); } return encodedPassword; } private readonly byte[] _rgbKey = new byte[] { 182, 140, 37, 101, 52, 157, 80, 17, 65, 35, 130, 208, 101, 68, 161, 45, 197, 102, 112, 190, 187, 177, 37, 76, 63, 38, 190, 117, 247, 122, 94, 17 }; private readonly byte[] _rgbIv = new byte[] { 60, 121, 178, 142, 50, 160, 226, 84, 41, 66, 158, 180, 26, 232, 42, 113 }; protected override byte[] EncryptPassword(byte[] password) { SymmetricAlgorithm sa = Aes.Create(); MemoryStream msEncrypt = new MemoryStream(); CryptoStream csEncrypt = new CryptoStream(msEncrypt, sa.CreateEncryptor(_rgbKey, _rgbIv), CryptoStreamMode.Write); csEncrypt.Write(password, 0, password.Length); csEncrypt.Close(); byte[] encryptedTextBytes = msEncrypt.ToArray(); msEncrypt.Close(); return encryptedTextBytes; } protected override byte[] DecryptPassword(byte[] encodedPassword) { SymmetricAlgorithm sa = Aes.Create(); MemoryStream msDecrypt = new MemoryStream(encodedPassword); CryptoStream csDecrypt = new CryptoStream(msDecrypt, sa.CreateDecryptor(_rgbKey, _rgbIv), CryptoStreamMode.Read); byte[] decryptedTextBytes = new Byte[encodedPassword.Length]; csDecrypt.Read(decryptedTextBytes, 0, encodedPassword.Length); csDecrypt.Close(); msDecrypt.Close(); return decryptedTextBytes; } /// /// Decodes a encoded Password /// /// /// private string DecodePassword(string encodedPassword) { string password = encodedPassword; switch (PasswordFormat) { case MembershipPasswordFormat.Clear: break; case MembershipPasswordFormat.Encrypted: password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password))).TrimEnd('\0'); break; case MembershipPasswordFormat.Hashed: throw new ProviderException("Cannot unencode a hashed password."); default: throw new ProviderException("Unsupported password format."); } return password; } /// /// returns the configuration string, if the value is null or empty the default value is returned /// /// /// /// private string GetConfigValue(string configValue, string defaultValue) { if (String.IsNullOrEmpty(configValue)) return defaultValue; return configValue; } } }