选择性地将要绑定的模型字段列入白名单

本文关键字:白名单 名单 字段 绑定 模型 选择性 | 更新日期: 2023-09-27 18:08:37

(我意识到这个问题非常类似于如何在ModelBinder/UpdateModel方法中将子对象字段列入白名单/黑名单?但我的情况略有不同,现在可能有一个更好的解决方案,而不是当时。

我们公司销售的基于网络的软件是由最终用户非常可配置的。这种灵活性的本质意味着我们必须在运行时做一些通常在编译时完成的事情。

关于谁可以读或读/写几乎所有的东西,有一些相当复杂的规则。

例如,以我们想要创建的模型为例:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace j6.Business.Site.Models
{
    public class ModelBindModel
    {
        [Required]
        [Whitelist(ReadAccess = true, WriteAccess = true)]
        public string FirstName { get; set; }
        [Whitelist(ReadAccess = true, WriteAccess = true)]
        public string MiddleName { get; set; }
        [Required]
        [Whitelist(ReadAccess = true, WriteAccess = true)]
        public string LastName { get; set; }
        [Required]
        [Whitelist(ReadAccess = User.CanReadSalary, WriteAccess = User.CanWriteSalary)]
        public string Salary { get; set; }
        [Required]
        [Whitelist(ReadAccess = User.CanReadSsn, WriteAccess = User.CanWriteSsn)]
        public string Ssn { get; set; }
        [Required]
        public string SirNotAppearingOnThisPage { get; set; }
    }
}

在控制器中,手动"解绑定"东西并不难。

var resetValue = null;
modelState.Remove(field);
pi = model.GetType().GetProperty(field);
if (pi == null)
{
    throw new Exception("An exception occured in ModelHelper.RemoveUnwanted.  Field " +
    field  +
    " does not exist in the model " + model.GetType().FullName);
}
// Set the default value.
pi.SetValue(model, resetValue, null);

使用HTML帮助器,我可以很容易地访问模型元数据,并抑制用户无法访问的任何字段的呈现。

问题:我不知道如何在CONTROLLER本身的任何地方访问模型元数据,以防止过度发布。

请注意,使用[Bind(Include…)]不是一个功能性的解决方案,至少在没有额外支持的情况下不是。要包含的属性依赖于运行时(而不是编译时),排除not属性不会将其从验证中删除。

ViewData.Model = null
ViewData.ModelMetaData = null

[AllowAnonymous]
[HttpPost]
// [Bind(Exclude = "Dummy1" + ",Dummy2")]        
public ViewResult Index(ModelBindModel dto)
{   
    zzz.ModelHelper.RemoveUnwanted(ModelState, dto, new string[] {"Salary", "Ssn"});
    ViewBag.Method = "Post";
    if (!ModelState.IsValid)
    {
        return View(dto);
    }
    return View(dto);
}

关于如何从控制器访问模型元数据有什么建议吗?或者在运行时将属性列入白名单的更好方法?


更新:

我从这个相当优秀的资源中借用了一页:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687

使用如下的模型:

[Required]
[WhiteList(ReadAccessRule = "Nope", WriteAccessRule = "Nope")]
public string FirstName { get; set; }
[Required]
[WhiteList(ReadAccessRule = "Database.CanRead.Key", WriteAccessRule = "Database.CanWrite.Key")]
public string LastName { get; set; }

类:

public class WhiteList : Attribute
{
    public string ReadAccessRule { get; set; }
    public string WriteAccessRule { get; set; }
    public Dictionary<string, object> OptionalAttributes()
    {
        var options = new Dictionary<string, object>();
        var canRead = false;
        if (ReadAccessRule != "")
        {
            options.Add("readaccessrule", ReadAccessRule);
        }
        if (WriteAccessRule != "")
        {
            options.Add("writeaccessrule", WriteAccessRule);
        }
        if (ReadAccessRule == "Database.CanRead.Key")
        {
            canRead = true;
        }
        options.Add("canread", canRead);
        options.Add("always", "be there");
        return options;
    }
}

并将这些行添加到链接中提到的MetadataProvider类:

var whiteListValues = attributes.OfType<WhiteList>().FirstOrDefault();
if (whiteListValues != null)
{
    metadata.AdditionalValues.Add("WhiteList", whiteListValues.OptionalAttributes());
}

最后,系统的核心:

public static void DemandFieldAuthorization<T>(ModelStateDictionary modelState, T model)
{
    var metaData = ModelMetadataProviders
        .Current
        .GetMetadataForType(null, model.GetType());
    var props = model.GetType().GetProperties();
    foreach (var p in metaData.Properties)
    {
        if (p.AdditionalValues.ContainsKey("WhiteList"))
        {
            var whiteListDictionary = (Dictionary<string, object>) p.AdditionalValues["WhiteList"];
            var key = "canread";
            if (whiteListDictionary.ContainsKey(key))
            {
                var value = (bool) whiteListDictionary[key];
                if (!value)
                {
                    RemoveUnwanted(modelState, model, p.PropertyName);
                }
            }
        }
    }
}

选择性地将要绑定的模型字段列入白名单

概括一下我对你问题的理解:

  • 字段访问是动态的;有些用户可以写入字段,有些则不能。
  • 你有一个解决方案来控制这个视图。
  • 你想防止恶意表单提交发送受限制的属性,然后模型绑定器将这些属性分配给你的模型。

也许是这样的?

// control general access to the method with attributes
[HttpPost, SomeOtherAttributes]
public ViewResult Edit( Foo model ){
    // presumably, you must know the user to apply permissions?
    DemandFieldAuthorization( model, user );    
    // if the prior call didn't throw, continue as usual
    if (!ModelState.IsValid){
        return View(dto);
    }
    return View(dto);
}
private void DemandFieldAuthorization<T>( T model, User user ){
    // read the model's property metadata
    // check the user's permissions
    // check the actual POST message
    // throw if unauthorized
} 

我在大约一年前写了一个扩展方法,从那以后它对我有过几次帮助。我希望这是一些帮助,尽管可能不是完整的解决方案为您。它实际上只允许对发送给控制器的表单中存在的字段进行验证:

internal static void ValidateOnlyIncomingFields(this ModelStateDictionary modelStateDictionary, FormCollection formCollection)
{
    IEnumerable<string> keysWithNoIncomingValue = null;
    IValueProvider valueProvider = null;
    try
    {
        // Transform into a value provider for linq/iteration.
        valueProvider = formCollection.ToValueProvider();
        // Get all validation keys from the model that haven't just been on screen...
        keysWithNoIncomingValue = modelStateDictionary.Keys.Where(keyString => !valueProvider.ContainsPrefix(keyString));
        // ...and clear them.
        foreach (string errorKey in keysWithNoIncomingValue)
            modelStateDictionary[errorKey].Errors.Clear();
    }
    catch (Exception exception)
    {
        Functions.LogError(exception);
    }
}

用法:

ModelState.ValidateOnlyIncomingFields(formCollection);

你需要一个FormCollection参数在你的ActionResult声明中,当然:

public ActionResult MyAction (FormCollection formCollection) {