so,
i have a "User" model that has a lot of fields but following are the impt ones:
public int Id {get;set;}
public string Username { get; set; }
public string Pwd { get; set; }
and i have a view model that validates the password that i use on a different controller:
public class ConfirmPassword : IValidatableObject
{
[Required]
public string Password { get; set; }
[Required(ErrorMessage="Confirm Password field is required.")]
public string ConfirmPwd { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
string regex1 = #"^.{8,10}$"; // 8 - 10 characters
Match requirement1 = Regex.Match(Password, regex1);
if (Password != ConfirmPwd)
yield return new ValidationResult("Password and Confirm Password is not identical.");
if (!requirement1.Success)
yield return new ValidationResult("Password must be between 8 and 10 characters.");
}
}
is there a way where i can connect the view model to my model entity? or just copy paste the codes my only option? I can't do copy paste since the code needs to have a IValidateObject which will mess up the whole User entity as there's a ton of background auditing happeneing.. I just need to validate the passwords whenever a profile is edited / created.
EDIT:
so sorry if everybody got confused. basically, i have multiple validations that i need on the confirm passwords that can't be handled by dataannotations, ergo the confirmpassword viewmodel. this validation i want to be applied onto the User model but without adding the "ConfirmPassword" field into it. as i dont need another field on the database. My question is how do i force the validations i have from the confirmapassword to trigger whenever the view POSTs and the password field does not meet its requirements?
namespace
{
public class User
{
public int Id {get;set;}
public string Username { get; set; }
public string Pwd { get; set; }
}
}
namespace
{
public class ConfirmPassword : IValidatableObject
{
[Required]
public string Password { get; set; }
[Required(ErrorMessage="Confirm Password field is required.")]
public string ConfirmPwd { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
string regex1 = #"^.{8,10}$"; // 8 - 10 characters
string regex2 = #"(?:.*?[A-Z]){1}"; // 1 uppercase
string regex3 = ""; // 1 lowercase
string regex4 = ""; // 1 numeric
Match requirement1 = Regex.Match(Password, regex1);
Match requirement2 = Regex.Match(Password, regex2);
Match requirement3 = Regex.Match(Password, regex3);
Match requirement4 = Regex.Match(Password, regex4);
if (Password != ConfirmPwd)
yield return new ValidationResult("Password and Confirm Password is not identical.");
if (!requirement1.Success)
yield return new ValidationResult("Password must be between 8 and 10 characters.");
if (!requirement2.Success)
yield return new ValidationResult("Password must contain at least 1 uppercase letter.");
if (!requirement3.Success)
yield return new ValidationResult("Password must contain at least 1 lowercase letter.");
if (!requirement4.Success)
yield return new ValidationResult("Password must contain at least 1 numeric character.");
}
}
}
You can use the decorator patter in your view model to do what you like. You can also use the System.ComponentModel.DataAnnotations namespace attributes to do all your validation for you.
public class ConfirmPassword
{
User model;
[Required]
public string Username
{
get { return this.model.Username; }
set { this.model.Username = value; }
}
[Required]
[DataType(DataType.Password)]
public string Password
{
get { return this.model.Pwd; }
set { this.model.Pwd = value; }
}
[Required(ErrorMessage = "Confirm Password field is required.")]
[Compare("NewPassword",
ErrorMessage = "The new password and confirmation password do not match.")]
[RegularExpression(#"^.{8,10}$")]
[DataType(DataType.Password)]
public string ConfirmPwd { get; set; }
public ConfirmPassword()
{
this.model = new User();
}
public ConfirmPassword(User model)
{
this.model = model;
}
}
Ok, I am still a little iffy on what you mean by connect view model to model entity... I think what you want is to now check if the password from the actual user matches the password passed in to the view model.
I personally would not do this in the view model itself, but rather in the controller and would add a ModelState.AddError if the password was incorrect.
The normal input validation can happen on the server or client side if you are using client side validation, but you can't check the password client side, so it will have to go to the server.
public LoginViewModel()
{
[Required]
public string LoginId {get; set;}
[DataType(DataType.Password)]
public string Password {get; set;}
[DataType(DataType.Password)]
public string ConfirmPassword {get; set;}
// this validation is not db related and can be done client side...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
string regex1 = #"^.{8,10}$"; // 8 - 10 characters
Match requirement1 = Regex.Match(Password, regex1);
if (Password != ConfirmPwd)
yield return new ValidationResult("Password and Confirm Password is not identical.");
if (!requirement1.Success)
yield return new ValidationResult("Password must be between 8 and 10 characters.");
}
}
[HttpGet]
public ActionResult Login()
{
var model = new LoginViewModel();
return View("Login",model);
}
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
var user = GetUserFromRepositoryByUsername(model.username);
if(user != null)
{
if(user.password == model.Password)
{
RedirectToAction("YouLoggedInYay!");
}
}
// if we made it this far, the user didn't exist or the password was wrong.
// Highlight the username field red and add a validation error message.
ModelState.AddError("Username","Your credentials were invalid punk!");
return View("Login",model);
}
I would include a userId in your ViewModel or add it on the URL so the controller can pick it up.
After validating the password combo you use the Id to retrieve the relevant User and update it with the new password. You can just do this within your new password post action.
You may want to match with the old password btw.
From what I believe, your UI validations and Logic validations should not be mixed up. It is worth doing in over again in the Logic even if it is the same thing. Basically, it is not the responsibility of the Entity object to carry the validations. It might be some service layer class that does it for the Entity. And you can probably throw an exception at that point.So it is handled in completely different ways in the UI and the Logic.
"I just need to validate the passwords whenever a profile is edited / created"
Use data annotations combined with IsValid on the ViewModel to check for failures. As far as mapping a Model to a View Model just use a decorator pattern.
Use System.ComponentModel.DataAnnotations (they even have a regular expression validator you can use)
Once passwords are verified against a policy, convert them to an MD5 hash and store that, not the password value
If all else fails there is nothing wrong with creating a separate UserValidation Class and share logic between the View Model and Model e.g. they both call the same methods to determine validity (reducing code).
Related
Today i'm trying to follow this article of Shai Raiten's Blog and when I finish it the createStatus return invalidAnswer
here is my Register action
[HttpPost]
[AllowAnonymous]
[CaptchaValidation("CaptchaCode", "registerCaptcha", "Wrong captcha!")]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, false, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
MailHelper.SendConfirmationEmail(model.UserName);
return RedirectToAction("Confirmation", "User");
}
else
{
ModelState.AddModelError("", "Failed!");
}
}
return View(model);
}
and here is my RegisterModel.cs
public class RegisterModel
{
[Key]
public long ID { set; get; }
[Required(ErrorMessage = "Do not Skip this")]
public string UserName { set; get; }
[StringLength(500, MinimumLength = 6, ErrorMessage = "Atleast 6 characters in passwords")]
[Required(ErrorMessage = "Do not Skip this")]
public string Password { set; get; }
[Compare("Password", ErrorMessage = "Wrong confirm passwords")]
[Required(ErrorMessage = "Do not skip this")]
public string ConfirmPassword { set; get; }
public string Name { set; get; }
public string Address { set; get; }
[RegularExpression(#"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", ErrorMessage = "This is not an email")]
public string Email { set; get; }
public string Phone { set; get; }
public bool EmailConfirm { set; get; }
}
any suggestion for me , really appreciated all the help you guys make.
The simplest thing you can do is:
First, you should define a property in your user model which will hold email confirmation token. Also, you should define property bool IsEmailConfirmed which defaults to false.
The token should be something like auto-generated random string. E.g. Guid.NewGuid().ToString()
Then, you should define another action, say [HttpGet, AllowAnonymous] ConfirmEmail(string email, string token), which will validate that token against saved in the database and update IsEmailConfirmed accordingly.
And the link you are asking about, should then point to an url which will look like something like that: http://YOUR.SERVER/YourController/ConfirmEmail?email={0}&token={1}, where {0} is user email and {1} is your user email confirmation token. It should return a view that tells whether confirmation was successfull.
However, i do recommend not to reinvent the wheel and to simply use Asp.Net Identity 2.0 framework, which will do all that authn & authz stuff for you.
Please follow the below example from ASP.Net site where its beautifully explained how to send email during registration prcoess.
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset
Additionally I would not recommend MD5 password hashinh since its pretty old, try using SHA 256 hashing for password encryption.
http://forums.asp.net/t/1211478.aspx?How+do+I+use+Sha256+to+Encrypt+a+String+
The Problem
I have a pre-defined [StringLength()] and [RegularExpression()] constraint on my Code-First Model.
The [StringLength()] requirement is 8-16 characters
The [RegularExpression()] constraint for the password is in a different format from the encrypted password.
The password is encrypted before being shoved into the database.
The encrypted password is 70 characters in length, which is higher than the 8-16 character limit defined in the Model.
I am required to use e.Encrypt(), meaning I cannot use the default ASP.NET hashing algorithm.
I've searched high and low, but have yet to find the correct answer.
My Code
I have a function that allows a user to register an account. It looks like this:
[HttpPost]
public ActionResult Register([Bind(Include="Username,Password,EmailAddress")] UserModel user)
{
if (TryUpdateModel(user))
{
// Set password to a different format than Model's designated Regex
user.Password = e.Encrypt(user.Password);
context.Entry(user).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Login", "Account");
}
return View();
}
In my UserModel.cs file, I have the following constraints:
[Required]
[DataType(DataType.Password)]
[StringLength(16, MinimumLength = 8, ErrorMessage = "Must be between 8 and 16 characters.")]
[RegularExpression("^(?=.*[0-9])(?=.*[!##$%^&*])[a-zA-Z0-9!##$%^&*]", ErrorMessage = ErrorMessage_PasswordRegex)]
public string Password { get; set; }
Again, the hashed password is in a completely different format. Therefore, I cannot update it because it throws a DbEntityValidationException. This [RegularExpression()] is for the format of the user's password. I need to be able to bypass or suspend the password's Regex constraint.
What I have done to remedy this
I've removed the [RegularExpression()] requirements, and upped the [StringLength()] to 70, which is the length of my password hash.
However, I don't want to allow users to input 70 characters of text. This seems like a cheap hack, and I feel that there should be a better way to do this. Any ideas?
Here's an example:
We've got different requirements for user input than what our database requires. We might need more user input which we will programmatically act upon.
The EF model
public class UserModel()
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
[Required, StringLength(70)]
public string Password { get; set; }
}
Now here's the class we use to capture user input
public class UserViewModel()
{
[Required]
public string Name { get; set; }
[Required]
[RegularExpression("^(?=.*[0-9])(?=.*[!##$%^&*])[a-zA-Z0-9!##$%^&*]", ErrorMessage = ErrorMessage_PasswordRegex)]
public string Password { get; set; }
[Required]
public string ConfirmPassword { get; set; }
}
Now we transform and map values.
The user won't know what to use for Id and the database doesn't have need for ConfirmPassword. We can also transform what the user originally entered as a password.
[HttpPost]
public ActionResult Register(UserViewModel model)
{
if ((ModelState.IsValid) &&
(model.Password == model.ConfirmPassword))
{
// map to EF model
var user = new UserModel
{
Name = model.Name,
Password = e.encrypt(model.Password) // transform, encrypt, whatever
};
db.Users.Add(user); // use EF model to persist
...
return RedirectToAction("Login", "Account");
}
return(model);
}
The following ActionResult logs in a user and puts it in a cookie. I create a JSON string for the user data; which includes loggingOnUser.MergedRights (a list of type RightModel).
How can I access the merged rights list from a view to see if the user has a specific right?
UserController
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
string username = model.Email;
string password = model.Password;
var loggingOnUser = _userService.Login(username, password);
if (loggingOnUser != null)
{
string userData = OptionBox.Common.JSON.ToJSON(loggingOnUser);
var ticket = new FormsAuthenticationTicket(1, loggingOnUser.Email.ToString(), DateTime.Now, DateTime.Now.AddDays(1), true, userData, FormsAuthentication.FormsCookiePath);
//Encrypt ticket
string encTicket = FormsAuthentication.Encrypt(ticket);
//Create the cookie
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Dashboard");
}
}
}
ModelState.AddModelError("", "The username or password provided is incorrect");
return View(model);
}
RightModel.cs
public class RightModel
{
public string RightName { get; set; }
public string Description { get; set; }
public bool Assigned { get; set; }
}
LoginModel.cs
public class LoginModel
{
public int ID { get; set; }
[Required]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
public string RedirectUrl { get; set; }
public DateTime LastLoggedIn { get; set; }
public virtual List<RightModel> MergedRights { get; set; }
}
Screenshot of userData as JSON
Setting aside the obvious concerns that you shouldn't be creating cookies with sensitive data in plain text such as the username and password, the only way you can access data and perform operations on it in a view is if the data is already stored in a container accessible by the view, such as the ViewBag/ViewData, the Model or the TempData object.
In order to include the desired information within the view, you should read the data from the cookie in the controller, and then include it in either the ViewBag/TempData or the Model if that is applicable to your design, then you would simply access it as ViewBag.MergedRigts / Model.MergedRights, etc.
IMO these types of operations (checking for user permissions and authentication) should be done in a controller action instead of within a view, so that you can route the request to the respective view depending on the model and session data, but as far as simply accessing the data stored in a cookie, that's one way you can do it.
Your problem here is that you're essentially bypassing all the built-in security that .NET provides for you, and that security is well written and extensive. By bypassing, you're are fighting against the current.
What you need to do is implement a custom IPrincipal interface that can provide an IsMemberOf() implementation. This way, you can use all the built-in security seamlessly.
I suggest you begin by reading up on IPrincipal, IIdentity, and custom implementations in MVC.
I have created an ADO.NET model of my database.
Created a new controller with CRUD (entity framework and using the ADO.NET entity model I created).
In my database I have a simple Users table. The Password row in the table will hold the users passwords encrypted with SimpleCrypto (PBKDF2).
In my ADO.NET Users.cs model I have added following validation:
[Required]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Password")]
public string Password { get; set; }
That works with jQuery in the browser with validation.
But in my controller I am encrypting the Password, and then the Password string will be way more than 20 chars in lenght.
var crypto = new SimpleCrypto.PBKDF2();
var encryptedPass = crypto.Compute(user.Password);
user.Password = encryptedPass;
user.PasswordSalt = crypto.Salt;
_db.Users.Add(user);
_db.SaveChanges();
And this gives me and "Validation failed for one or more entities."-error.
I can copy the user over to a "var newUser" and then set all the properties there, but isn't there a easier way to bypass the model validation in this case?
EDIT: If I remove the validation of the Password prop in the model then everything works. So it is the validation that gives me the error because I alter the Password from 6-20 length chars to +100 lengt chars because of the encryption in the controller.
EDIT: Complete controller section inserted to this question.
[HttpPost]
public ActionResult Create(Users user)
{
if (!ModelState.IsValid)
{
return View();
}
if (_db.Users.FirstOrDefault(u => u.Email == user.Email) != null)
{
ModelState.AddModelError("", "User already exists in database!");
return View();
}
var crypto = new SimpleCrypto.PBKDF2();
var encryptedPass = crypto.Compute(user.Password);
user.Password = encryptedPass;
user.PasswordSalt = crypto.Salt;
_db.Users.Add(user);
_db.SaveChanges();
return RedirectToAction("Index", "User");
}
This is where ViewModels enter the game. You should create a model which you pass to the view and map that back to the domain model later.
The ViewModel:
public class RegisterModel
{
[Required]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Password")]
public string Password { get; set; }
}
The domain model (your current User model):
public class User
{
// other properties..
[Required]
public string Password { get; set; }
}
You can use these models in your controller like this:
The GET action:
public ActionResult Register()
{
var registerModel = new RegisterModel();
return View(registerModel)
}
With a view like this:
#model RegisterModel
#Html.LabelFor(model => model.UserName)
#Html.TextBoxFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
#Html.LabelFor(model => model.Password)
#Html.PasswordFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
And the POST action:
[HttpPost]
public ActionResult Register(RegisterModel registerModel)
{
// Map RegisterModel to a User model.
var user = new User
{
UserName = registerModel.UserName,
Password = registerModel.Password // Do the hasing here for example.
};
db.Users.Add(user);
db.SaveChanges();
}
You say your password encryption is occurring in the controller. In this case shouldn't you be encrypting after validation? For example:
public ActionResult SomeControllerAction(UserViewModel user)
{
if (!ModelState.IsValid)
{
// at this point the human readable (and assuming < 20 length) password
// would be getting validated
return View(user);
}
// now when you're writing the record to the DB, encrypt the password
var crypto = new SimpleCrypto.PBKDF2();
var encryptedPass = crypto.Compute(user.Password);
user.Password = encryptedPass;
user.PasswordSalt = crypto.Salt;
_db.Users.Add(user);
_db.SaveChanges();
// return or redirect to whatever route you need
}
If you wanted to specifically control your validation, then try implementing IValidatableObject on your view model class and perform validation here, instead of via attributes. For example:
public class UserViewModel : IValidatableObject
{
public string Username { get; set; }
public string Password { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate the unencrypted password's length to be < 20
if (this.Password.Length > 20)
{
yield return new ValidationResult("Password too long!");
}
}
}
If i understand correctly you have a database table with a password field.
According to your model this password field is 20 characters long
[StringLength(20, MinimumLength = 6)]
And you want to insert a value greater then 20 characters.
If entity framework did not stop you then you would get a database error.(Entity framework doesn't know there is a mismatch between your data model and the database and it doesn't wanna take the risk of pushing the insert through)
I assume however that you ment to specify a clientSide validation rule on your viewmodel and not a length constraint on the database.
I hope you see why this is a confusing setup.
My advice would be to either split your viewModel and model up so you can post a viewModel with unencrypted password of maxlength 20 that you can convert to a model password with length 100.
If you find that too much hassle you could create an unmapped password property which you set from html when you post it and you convert it to the password property in your controller.
Your class could look like this :
public class RegisterModel
{
[Required]
public string UserName { get; set; }
[Required]
[NotMapped]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Password")]
public string PlainTextPassword { get; set; }
[Required]
[StringLength(300)]//This is optional
[DataType(DataType.Password)]
public string Password { get; set; }
}
I have two fields in one of my class for address like
public string Country { get; set; }
[Required(ErrorMessage = "Postcode is required")]
[RegularExpression(#"REGEX",
ErrorMessage = "Please enter a valid UK Postcode)]
public string postcode { get; set;}
However if the user selects a country other than UK then I want my Postcode field to ignore the REGEX atleast and in an ideal world validate using another REGEX depending upon the country. Can anyone suggest if this is possible in the model itself?
There are a few different options for you:
Create a 100% custom validation attribute that combines the Required and RegularExpression attributes to your needs. So inside that custom attribute you would do all the validation that you need and compare the value to the Country property to selectively apply the RegEx as needed.
Create a different postcode attribute per country that you care about and use something like the `RequiredIfAttribute (see RequiredIf Conditional Validation Attribute) to determine which one is actually required. You can then use Javascript to show/hide the appropriate input fields.
You can do this with IValidatableObject:
class MyClass : IValidatableObject {
public string Country { get; set; }
[Required(ErrorMessage = "Postcode is required")]
public string postcode { get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (!String.IsNullOrEmpty(Country)
&& !String.IsNullOrEmpty(postcode)) {
switch (Country.ToUpperInvariant()) {
case "UK":
if (!Regex.IsMatch(postcode, "[regex]"))
yield return new ValidationResult("Invalid UK postcode.", new[] { "postcode" });
break;
default:
break;
}
}
}
}