Ok I am writeing a test for a membership password change. The code below is what I have so far. I need some assistance to check the password format. Min char length is 7 and max length is 8.
Also the I can test if the password format is clear, but how do I test an encrypted format?
Can anyone assist?
[TestMethod]
public void TestChangePassword()
{
try
{
AsaMembershipProvider prov = this.GetMembershipProvider();
MembershipCreateStatus status;
//creates user
MembershipUser user = prov.CreateUser("testUserX", "12345", "test.UserX#abc.com", "", "", true, null, out status);
//gets user
user = prov.GetUser("testUserX", false);
Assert.AreEqual(user.UserName, "testUserX");
//Authenticates username and password
var isAuthenticated = prov.ValidateUser(user.UserName, "12345");
Assert.IsTrue(isAuthenticated);
//changes password
prov.ChangePassword("testUserX", "12345", "ABCDE");
//Validates password has been changed
prov.ValidateUser(user.UserName, "ABCDE");
Assert.IsTrue(isAuthenticated);
// Change password back
prov.ChangePassword("testUserX", "ABCDE", "12345");
//Validates password has been changed back
prov.ValidateUser(user.UserName, "12345");
//Deletes User
prov.DeleteUser("testUserX", true);
//tries to get user again
user = prov.GetUser("testUserX", false);
//test that no user is returned
Assert.AreEqual(null, user);
}
catch (Exception ex)
{
LogMessage(ex);
Assert.Fail(ex.Message);
}
}
One option would be to write an extension method targeting AsaMembershipProvider which would validate the password and then call ChangePassword from within it to do the actual change
The downside of the approach is that you will have more code to maintain
public static class CryptoExtensions {
public static void ChangePasswordEx(this AsaMembershipProvider mp, string username, string oldPassword, string newPassword){
// validate format of the password
if (true /*validation code*/ )
{
throw new Exception("Invalid password format");
}
// rest of the code to encrypt and store the password
mp.ChangePassword(username, oldPassword, newPassword);
}
}
Your test code should now call prov.ChangePassword with prov.ChangePasswordEx
Related
I'm hoping someone can explain this to me because I am at my wits end trying to resolve an issue I am having.
The error I am receiving is, "The object already exists." whenever I am trying to run our ResetPassword function. The weird thing is I ONLY receive this error if the user account has had their password reset before. If I either create a new account with a new password, or search for a user in the database that has not had the ResetPassword function called on their account, then it will let me call this function once. Note: On this one time it lets me run the function the password does get reset. Any account that has already run ResetPassword will prompt the object already exists error.
public static void ResetPassword(DirectoryEntry User, string password)
{
User.Invoke("SetPassword", new object[] { password });
User.Properties["LockOutTime"].Value = 0x0000; //unlock account
int val = (int)User.Properties["userAccountControl"].Value;
User.Properties["userAccountControl"].Value = val & ~0x2; //Enable account
User.CommitChanges();
Logs.CreateLogEntry("Reset password", User);
User.Close();
}
As you can see, we are passing in a DirectoryEntry user, along with a generated new password. We are using an anonymous login on the backend of our IIS website to have admin credentials high enough to use SetPassword.
You need to provide credentials of a user in AD that has admin privileges to reset passwords.
public static void ResetPassword(string username, string password)
{
string adminUser = "YourAdminUserIdInAD";
string adminPass = "YourAdminUserPasswordInAD";
string ldapString = "LDAP://YourLDAPString";
DirectoryEntry dEntry = new DirectoryEntry(ldapString , adminUser, adminPass, AuthenticationTypes.Secure);
DirectorySearcher deSearch = new DirectorySearcher(dEntry) {
SearchRoot = dEntry,
Filter = "(&(objectCategory=user)(cn=" + username + "))"
};
var directoryEntry = deSearch.FindOne();
directoryEntry.Invoke("SetPassword", new object[] { password });
directoryEntry.Properties["LockOutTime"].Value = 0x0000; //unlock account
int val = (int)directoryEntry.Properties["userAccountControl"].Value;
directoryEntry.Properties["userAccountControl"].Value = val & ~0x2;
directoryEntry.CommitChanges();
Logs.CreateLogEntry("Reset password", User);
directoryEntry.Close();
}
I am writing a N-Tier application using ASP.Net Core.
In my service layer in logic tier I have an API which update the user's password. Here is the code from the service layer.
public IActionResult UpdatePassword([Required][EmailAddress] string email, [Required][DataType(DataType.Password)] string oldPassword, [Required][DataType(DataType.Password)] string newPassword)
{
try
{
_userLogic.UpdatePasswordLogic(oldPassword, email, newPassword);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
return Ok($"Password updated successfully. The new password is: { newPassword }");
}
basically it just gets the email, the current password, and the new given password by user and pass it to the "UpdatePasswordLogic" method in business layer. The "UpdatePasswordLogic" works as following:
public void UpdatePasswordLogic(string oldPassword, string email, string newPassword)
{
// Take the user's password from the database
string savedPassword = _context.Users
.Where(x => x.Email == email)
.Select(x => x.Password);
if (!string.IsNullOrEmpty(savedPassword))
{
// Check if the oldPassword is equal to the password saved in the databank
if (ComparePasswords(savedPassword, oldPassword))
{
// TODO: Set the new password
}
else
// Throw exception when password is not correct
throw new Exception("password does not match the one saved in the database");
}
else
// If user does not exist, throw an exception
throw new Exception("User does not exist in databank");
}
The "ComparePasswords" method check if the given password by user match the one that saved in the databank. If the password is not correct then it suppose to throw an exception. When I enter a wrong password the UpdatePasswordLogic does throw an exception but my API in service layer does not catch this exception and it returns the OKObjectResult. Does anyone know why it does not catch the exception?
I am writing an ASP.NET program where I need to store the users password in the database. But I get a password mismatched when I Compare the password from the database with the user input password. Even if the users password is correct.
Password Hashing:
string PasswordSalt = Crypto.HashPassword(DateTime.Now.ToString());
string hashPassword = Crypto.HashPassword(formcollection["PassWord"]); //Hash User PassWord
user.PassWord = Crypto.HashPassword(PasswordSalt + hashPassword);//Add Salt to Password For Futher Security
user.PassWordSalt = PasswordSalt;
Password Verification:
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string checkpassword = ThisUser.PassWord;
//User Inputed password.
string password = user.PassWord;
if (password != null)
{
//Need to fix.
string encrypt_password = Crypto.HashPassword(password);
string salted_password = Crypto.HashPassword(ThisUser.PassWordSalt + encrypt_password);
//bool does_password_match = Crypto.VerifyHashedPassword(checkpassword, password);
if (checkpassword == salted_password)
{
//Check if the inputed password matches the password from the Database.
//Remember to give session based on the user_id.
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else
{
ModelState.AddModelError("PassWord", "Wrong Password, Please Enter Correct Password");
return View(user);
}
I've never used it, but based on the documentation...
Crypto.HashPassword adds the salt for you and returns a base-64 encoded string with all the details in it to verify the password. So, you do NOT need to add a salt yourself.
All you need to do is store the hash result (base64EncodedHash below) in the DB, and then use it with VerifyHashedPassword to authenticate later. E.g. make a unit test like so:
var base64EncodedHash = Crypto.HashPassword("password");
Assert.IsTrue( Crypto.VerifyHashedPassword( base64EncodedHash, "password" ) );
Assert.IsFalse( Crypto.VerifyHashedPassword( base64EncodedHash, "otherPass") );
https://msdn.microsoft.com/en-us/library/system.web.helpers.crypto.verifyhashedpassword(v=vs.111).aspx
To translate this to your code:
user.PassWord = Crypto.HashPassword(formcollection["PassWord"]);
Then to verify (comments added for quirks I see):
//Why are you storing "email" in Session before user is validated??? Seems off.
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string userInputPassword = user.PassWord; //this should be coming from POST
if( ThisUser != null && Crypto.VerifyHashedPassword(ThisUser.PassWord, userInputPassword) ) {
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else {
ModelState.AddModelError("PassWord","Your username or password are incorrect");
return View(user);
}
Ideally, as I somewhat indicated by my change of your error text...you also want to give the user the same error message whether the username/email or password are wrong. Your code, as is, probably returns a different error if the email doesn't return an account, but you don't want to give that much info to brute-force attackers.
You also need to put in some brute-force checking so that if they attempt too many times with failures, block that IP address for X amount of time., etc.
And, as someone said...when it comes to security...until you're the
expert...it's best to use pre-existing code/frameworks to mitigate you
risks.
I'm working on an intranet, I've just added a feature on the user's profile to change his password.
As you can see with the following controller :
[HttpPost]
public ActionResult ChangePassword(Employee objToEdit, FormCollection form, LocalPasswordModel model) // Find how to obtain "OldPassword" from AccountModel
{
objToEdit.Login = User.Identity.Name;
string name = objToEdit.FirstName;
string pwd = form["NewPassword"];
string confirm = form["ConfirmPassword"];
if (_service.Edit_password(objToEdit, pwd, confirm)) // Checks if NewPassword and ConfirmPassword are the same, and does some syntax checking
{
bool changePasswordSucceeded;
try
{
changePasswordSucceeded = WebSecurity.ResetPassword(WebSecurity.GeneratePasswordResetToken(objToEdit.Login), pwd); // Seems to work
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("Index", new { Message = CRAWebSiteMVC.Controllers.AccountController.ManageMessageId.ChangePasswordSuccess });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
return new RedirectResult(Url.Action("Index"));
}
return View();
}
So far, the user just needs to input a New password and a confirmation password. I wish to add a "Enter your current Password" feature but I can't find a way to retrieve the user's current password !
The user profile DB does not contain a Password column anymore and I use Form authentication if that's of any help.
EDIT: Thank you for your help, to solve my problem I simply replaced the ResetPassword line by the following :
changePasswordSucceeded = WebSecurity.ChangePassword(objToEdit.Login, current, pwd);
If it fails, it directly displays the error message that the current password is wrong.
You can't !
That's actually a security feature. You should never store a password in plain text.
The good thing is, you don't need to do the comparison yourself:
Instead, use something like ValidateUser to let the Membership Provider validate the provided password. Behind the scenes, this method will hash the password and compare it with the hashed version contained in the database.
EDIT:
Also, note that since you are using the WebSecurity class, there is a method, ChangePassword that accepts the current password. It seems that method will check the current password matches the specified currentPassword parameter. Maybe you should use this one instead of ResetPassword
Microsoft is coming up with a new Membership system called ASP.NET Identity (also the default in ASP.NET MVC 5). I found the sample project, but this is not implemented a password reset.
On password reset topic just found this Article: Implementing User Confirmation and Password Reset with One ASP.NET Identity – Pain or Pleasure, not help for me, because do not use the built-in password recovery.
As I was looking at the options, as I think we need to generate a reset token, which I will send to the user. The user can set then the new password using the token, overwriting the old one.
I found the IdentityManager.Passwords.GenerateResetPasswordToken / IdentityManager.Passwords.GenerateResetPasswordTokenAsync(string tokenId, string userName, validUntilUtc), but I could not figure out what it might mean the tokenId parameter.
How do I implement the Password Reset in ASP.NET with MVC 5.0?
I get it: The tokenid is a freely chosen identity, which identifies a password option. For example,
1. looks like the password recovery process, step 1
(it is based on: https://stackoverflow.com/a/698879/208922)
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPassword(
ResetPasswordViewModel rpvm)
{
string message = null;
//the token is valid for one day
var until = DateTime.Now.AddDays(1);
//We find the user, as the token can not generate the e-mail address,
//but the name should be.
var db = new Context();
var user = db.Users.SingleOrDefault(x=>x.Email == rpvm.Email);
var token = new StringBuilder();
//Prepare a 10-character random text
using (RNGCryptoServiceProvider
rngCsp = new RNGCryptoServiceProvider())
{
var data = new byte[4];
for (int i = 0; i < 10; i++)
{
//filled with an array of random numbers
rngCsp.GetBytes(data);
//this is converted into a character from A to Z
var randomchar = Convert.ToChar(
//produce a random number
//between 0 and 25
BitConverter.ToUInt32(data, 0) % 26
//Convert.ToInt32('A')==65
+ 65
);
token.Append(randomchar);
}
}
//This will be the password change identifier
//that the user will be sent out
var tokenid = token.ToString();
if (null!=user)
{
//Generating a token
var result = await IdentityManager
.Passwords
.GenerateResetPasswordTokenAsync(
tokenid,
user.UserName,
until
);
if (result.Success)
{
//send the email
...
}
}
message =
"We have sent a password reset request if the email is verified.";
return RedirectToAction(
MVC.Account.ResetPasswordWithToken(
token: string.Empty,
message: message
)
);
}
2 And then when the user enters the token and the new password:
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPasswordWithToken(
ResetPasswordWithTokenViewModel
rpwtvm
)
{
if (ModelState.IsValid)
{
string message = null;
//reset the password
var result = await IdentityManager.Passwords.ResetPasswordAsync(
rpwtvm.Token,
rpwtvm.Password
);
if (result.Success)
{
message = "the password has been reset.";
return RedirectToAction(
MVC.Account.ResetPasswordCompleted(message: message)
);
}
else
{
AddErrors(result);
}
}
return View(MVC.Account.ResetPasswordWithToken(rpwtvm));
}
Skeleton proposal to sample project on github, if anyone needs it may be tested.The E-mail sending not yet written, possibly with the addition soon.
Seems like a lot of trouble...
What advantage does the above give over:
the user clicking a 'Recover Account' link
this sends an 64 byte encoded string of a datetime ticks value (call it psuedo-hash) in an email
click the link back in the email to a controller/action route that
matches email and it's source server to psuedo-hash, decrypts the psuedo-hash, validates the time since sent and
offers a View for the user to set a new password
with a valid password, the code removes the old user password and assigns the new.
Once complete, successful or not, delete the psuedo-hash.
With this flow, at no time do you EVER send a password out of your domain.
Please, anyone, prove to me how this is any less secure.