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; }
}
Related
I've an ASP.NET Core app with Individual User Accounts authentication. The app by default creates AccountController, RegisterViewModel, Register.cshtml etc as shown below. I've added two properties for a Role dropdown in the RegisterViewModel and I'm displaying that dropdown in the Register.cshtml view so an admin can select a role for a newly created user.
Question: In the following Post method for Register, how do I retrieve the role admin selected from the Register.cshtml view? In other words, in this Post action method how do I use that selected role so I can add the newly created user to that role?
RegisterViewModel:
public class RegisterViewModel
{
[Required]
[Display(Name = "Login")]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Display(Name = "Select Role")]
public ApplicationRole SelectedRole { get; set; }
public IEnumerable<SelectListItem> Roles { get; set; }
}
AccountController Action Method:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.UserName, StateID=model.StateID, Region=model.Region };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
to your view model add a selected parameter
[Display(Name = "Selected Role")]
public int SelectedRole{ get; set; }
then on your view setup the drop down like this
#Html.DropDownListFor(x => x.SelectedRole, model.Roles)
this will tie the result of the drop down to your model. then on your controller model.SelectedRole should have your selected id
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 a PartialView that is a form used to create or modify a user and implements the ViewModel DynamicActionUserModel. The view that referrences this partialView shows a table of all MembershipUsers and gives the ability to create a new user Membership.CreateUser() or modify a user 'Membership.UpdateUser()`. The form in the partialView does an ajax post to my controller to submit data.
The issue I'm running into is that when a user is created their userName, email, password and role is serialized back to the controller as DynamicActionUserModel.RegisterModel and validated, but when a user is modified, password is not a property that is available (nor do I want to make it available to modify on the client side) so it isn't set in DynamicActionUserModel.RegisterModel and ModelState.IsValid is always false.
Perhaps the design of my model and view needs to change, or is there a way to validate the model but ignore password when a user is being modified? Not sure what the best practice is for this one.
I guess another option would be to create another ViewModel and another partialView specifically for modifying a user but that seems sloppy.
Model
public class DynamicActionUserModel {
public string Action { get; set; }
public RegisterModel RegisterModel { get; set; }
}
public class RegisterModel {
[Required]
[Display(Name = "User Name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
public string[] SelectedRoles { get; set; }
public MultiSelectList Roles { get; set; }
}
Controller
[HttpGet]
public ActionResult CreateUser() {
var model = new DynamicActionUserModel {
Action = "CreateUser",
RegisterModel = new RegisterModel {
Roles = new MultiSelectList(System.Web.Security.Roles.GetAllRoles())
}
};
return PartialView("_UserPartial", model);
}
[HttpGet]
public ActionResult ModifyUser() {
var model = new DynamicActionUserModel {
Action = "ModifyUser",
RegisterModel = new RegisterModel {
Roles = new MultiSelectList(System.Web.Security.Roles.GetAllRoles())
}
};
return PartialView("_UserPartial", model);
}
[HttpPost]
public ActionResult ModifyUser(DynamicActionUserModel model) {
bool isEqual = true;
if(!ModelState.IsValid) { // this is always false because password is empty
return PartialView("_UserPartial", model);
}
var user = Membership.GetUser(model.RegisterModel.UserName);
// do stuff
Membership.UpdateUser(user);
return Json(new {success = false});
}
View
#using RobotDog.Models
#model IEnumerable<RobotDog.Models.UserModel>
<!-- table of users -->
<div class="modify-form">
#Html.Action("ModifyUser")
</div>
<div class="create-user-form">
#Html.Action("CreateUser")
</div>
PartialView
#model RobotDog.Models.DynamicActionUserModel
#using(Html.BeginForm(Model.Action,"Admin", FormMethod.Post, new { #class = "ajax" })) {
<!-- Email -->
#Html.TextBoxFor(x => x.RegisterModel.Email, new { #class = inputSize, placeholder = "Email"})
<!-- UserName -->
#if(Model.Action == "ModifyUser") {
#Html.HiddenFor(x => x.RegisterModel.UserName)
<span class="input-xlarge uneditable-input">#Html.DisplayNameFor(x => x.RegisterModel.UserName)</span>
} else {
#Html.TextBoxFor(x => x.RegisterModel.UserName, new { #class = inputSize, placeholder = "User Name" })
}
<!-- Password -->
#if(Model.Action == "Createuser") {
#Html.PasswordFor(x => x.RegisterModel.Password, new { #class = inputSize, placeholder = "Password"})
}
<!-- Roles -->
#Html.ListBoxFor(x => x.RegisterModel.SelectedRoles, Model.RegisterModel.Roles)
<!-- Submit -->
<input type="submit" value="Submit" class="btn"/>
}
Try with ModelState.Remove("password") before calling ModelState.IsValid, but as suggested here if a property is not always required you should not mark it as required.
Have you looked at ModelState.IsValidField ?
Note: even as of MVC4 the documentation is backwards and should say :
Determines whether there are any ModelError objects that are associated with or prefixed with the specified key.
I've made a little extension method helper for this:
public static class ModelStateHelpers
{
public static bool IsValidFor<TModel, TProperty>(this TModel model,
System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression,
ModelStateDictionary modelState)
{
string name = ExpressionHelper.GetExpressionText(expression);
return modelState.IsValidField(name);
}
}
Usage is simple:
bool billingValid = model.IsValidFor(x => x.CheckoutModel.BillingAddress, ModelState);
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).