简体   繁体   中英

How to maintain model values after postback without exposing them

I'm working on a UserProfile edit page, where only a subset of the fields of a UserProfile model can be edited. A number of fields are only editable for users with a special role, and the field UserName is of course uneditable and hidden.

Right now I'm thinking about including hiddenfields for all the fields that are not to be edited by users, and decorating my model like this:

[Table("UserProfile")]
public partial class UserProfile
{
    public UserProfile()
    {
        webpages_Roles = new HashSet<Role>();
    }

    [Key]
    public int UserId { get; set; }

    [Required]
    [StringLength(56)]
    [Display(Name="Email")]
    [Editable(false)] // is this the way to go?
    public string UserName { get; set; }

    [Required]
    [Display(Name = "First name")]
    [StringLength(256)]
    public string FirstName { get; set; }

    [Editable(false)] // is this the way to go?
    public bool SomeSetting { get; set; }

    // ... more properties are unimportant for this example
}

Other related bits of code:

    //
    // GET: /Account/Profile

    public ActionResult UserProfile()
    {
        var userProfile = db.UserProfiles.Find(WebSecurity.CurrentUserId);

        return View(userProfile);
    }

    //
    // POST: /Account/Profile

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult UserProfile(UserProfile model)
    {
        // if I dont include UserName, validation will fail since it 
        // is now null and the field is [Required]
        if (ModelState.IsValid)
        {
            // if I dont include hidden fields, UserId, UserName
            // and SomeSetting will be null here
            db.Entry(model).State = EntityState.Modified;
            db.SaveChanges();
        }

        return View(model);
    }

Related view code:

@Html.HiddenFor(m => m.UserId)
@Html.HiddenFor(m => m.UserName)
@Html.HiddenFor(m => m.SomeSetting)

However, I'm worried about exposing these fields through a hidden input. Won't clever or malicious users be able to edit them anyway? I understand that I HAVE to include them though, otherwise the properties will be null after postback. Can someone enlighten me?

You could do something like generate a hash as a property on your model which is the hash of all the values in a concatenated string - for example:

Add a new property to your ViewModel (you can use the [NotMapped] annotation if this is also your Database Object).:

public string SecurityHash {get;set;}

Create a simple hashing function in a helper (or your controller):

public string CalculateMD5Hash(string input)
{
    // step 1, calculate MD5 hash from input
    MD5 md5 = System.Security.Cryptography.MD5.Create();
    byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
    byte[] hash = md5.ComputeHash(inputBytes);

    // step 2, convert byte array to hex string
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < hash.Length; i++)
    {
        sb.Append(hash[i].ToString("X2"));
    }
    return sb.ToString();
}

Now set the hash value in your controller:

//
// GET: /Account/Profile

public ActionResult UserProfile()
{
    var userProfile = db.UserProfiles.Find(WebSecurity.CurrentUserId);
    userProfile.SecurityHash = MyHashHelper.CalculateMD5Hash(userProfile.UserID + userProfile.UserName + userProfile.SomeSetting)
    return View(userProfile);
}

Then in your View, persist your hashed value:

@Html.HiddenFor(m => m.UserId)
@Html.HiddenFor(m => m.UserName)
@Html.HiddenFor(m => m.SomeSetting)
@Html.HiddenFor(m => m.SecurityHash)

Then finally, you can check if your values have been tampered with buy re-hashing after the POST:

//
// POST: /Account/Profile

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult UserProfile(UserProfile model)
{
    string hashCheckVal = MyHashHelper.CalculateMD5Hash(model.UserID + model.UserName + model.SomeSetting)
    if(string.Compare(hashCheckVal, model.SecurityHash) != 0)
    {
        throw new Exception("tampered with!");
    }

    // if I dont include UserName, validation will fail since it 
    // is now null and the field is [Required]
    if (ModelState.IsValid)
    {
        // if I dont include hidden fields, UserId, UserName
        // and SomeSetting will be null here
        db.Entry(model).State = EntityState.Modified;
        db.SaveChanges();
    }

    return View(model);
}

If you are concerned about security then you should retrieve your entity from the database after postback and then update the editable properties from the submitted model. To retrieve your model I think it is ok to keep the UserId as a hidden field: @Html.HiddenFor(m => m.UserId)

I hope this answer helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM