2

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

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

关于谁拥有对大多数内容的读或读/写访问权限,有一些相当复杂的规则。

例如,采用我们想要创建的这个模型:

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...)] 不是功能解决方案,至少在没有额外支持的情况下不是。要包含的属性取决于运行时(而不是编译时),并且排除该属性不会其从验证中删除。

ViewData.Modelnull
ViewData.ModelMetaDatanull

[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);
                }
            }
        }
    }
}
4

2 回答 2

2

回顾一下我对您问题的解释:

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

也许是这样的?

// 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
} 
于 2013-04-10T21:39:29.930 回答
1

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

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);

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

public ActionResult MyAction (FormCollection formCollection) {
于 2013-04-10T21:37:27.667 回答