防止用户通过服务器上的角色/权限更新只读viewmodel属性的策略

时间:2011-07-29 17:57:34

标签: c# asp.net-mvc data-annotations model-binding

该平台是ASP.NET MVC 2。

我们有一个用户故事说明:

  

在[view]上,除非用户,否则不允许用户编辑[property]   一个[适当的角色]。他们仍然必须能够查看[property]。

因此,我必须为这些人显示字段,只是阻止他们更改或更新属性值。

I know that I can place a read only control in the view using an attribute for the current user.这应该给客户一个视觉提示,即不允许编辑。但CSS样式不会阻止某人破解他们的帖子来改变该属性的价值。

我的问题涉及保护服务器端的属性。在这种情况下,我可以使用哪些方法来检测传入视图模型的更改 - 用户无法编辑某个属性?

修改

我需要远离绑定和白名单 - 我很欣赏这些想法!他们让我意识到我省略了一条关键信息。

我的产品所有者希望不惜一切代价添加属性,并且我很高兴 - 我读过这些内容:非静态解决方案无需应用。此外,她希望将其他条件逻辑应用于他们的应用程序 - “如果相关属性的状态为'X',那么他们可以编辑而不管许可”等等我可以处理该部分。我只需要知道动态应用它们的位置。

我认为这是一个自定义模型粘合剂解决方案。

顺便说一句,我们将这一特定权限附加到角色:

var hasPermission = User.IsInRole(permission);

3 个答案:

答案 0 :(得分:0)

您可以使用Bind属性来指定要在绑定中包含或排除的属性。这是一篇很好的基础文章,可以获得更多信息。

Exclude attributes using Bind attribute

答案 1 :(得分:0)

我决定使用自定义模型绑定器。我已经可以禁用HTML控件了。但我需要有选择地授权他们。

我知道示例对象是设计的 - 当然,你不会让用户发布一个具有两个不可编辑属性的对象 - 但重点是我不想让用户持久保存它们的值。我将NULL任何值,然后不更新任何NULL值。通过忽略NULL值,我不必转到数据访问来检索当前值以替换有问题的更新。

这段代码让我在路上(使用MSPEC作为测试框架):

public class TestSplitDetailViewModel
{
    public int Id { get; set; }

    [CanEdit]
    public string RestrictedProperty { get; set; }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class CanEditAttribute : Attribute
{
}

public class CanEditAttributeBinder : DefaultModelBinder
{
    private readonly ISecurityTasks _securityTask;

    private readonly ISecurityContext _securityContext;

    public CanEditAttributeBinder(ISecurityTasks securityTask, ISecurityContext securityContext)
    {
        this._securityTask = securityTask;
        this._securityContext = securityContext;
    }

    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
        var canEditAttribute = propertyDescriptor.Attributes
          .OfType<CanEditAttribute>()
          .FirstOrDefault();

        if (canEditAttribute != null)
        {
            bool allowed = IsAllowed();
            if (allowed)
            {
                propertyDescriptor.SetValue(bindingContext.Model, null);
            }
            else
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }
        else
        {
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }

    private bool IsAllowed()
    {
        return !this._securityTask.DoesUserHaveOperation(this._securityContext.User.Username, UserOperations.ReclassAllowed);
    }
}

public class TestModelSpec : Specification<CanEditAttributeBinder>
{
    protected static HomeController controller;
    private static MockRepository mocks;

    protected static ISecurityTasks securityTasks;
    private static ISecurityContext securityContext;

    protected static ModelBindingContext bindingContext;

    Establish context = () =>
    {
        ServiceLocatorHelper.AddUserServiceWithTestUserContext();
        securityTasks = DependencyOf<ISecurityTasks>().AddToServiceLocator();
        securityContext = DependencyOf<ISecurityContext>().AddToServiceLocator();

        user = new User("CHUNKYBACON");
        securityContext.User = user;

        // When we restricted access on the client, 
        // Chunky submitted a FORM POST in which he HACKED a value 
        var formCollection = new NameValueCollection
                { 
                    { "TestSplitDetailViewModel.Id", "2" }, 
                    { "TestSplitDetailViewModel.RestrictedProperty", "12" } // Given this is a hacked value
                };

        var valueProvider = new NameValueCollectionValueProvider(formCollection, null);
        var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TestSplitDetailViewModel));

        bindingContext = new ModelBindingContext
        {
            ModelName = "TestSplitDetailViewModel",
            ValueProvider = valueProvider,
            ModelMetadata = modelMetadata
        };

        controller = new HomeController(null, null, null, null, null);
        mocks = new MockRepository();
        MvcMockHelpers.SetFakeControllerContext(mocks, controller);
    };

    protected static User user;

    protected static TestSplitDetailViewModel incomingModel;
}

public class when_a_restricted_user_changes_a_restricted_property : TestModelSpec
{
    private Establish context = () => securityTasks.Stub(st =>
                               st.DoesUserHaveOperation(user.Username, UserOperations.ReclassAllowed)).Return(false);


    Because of = () => incomingModel = (TestSplitDetailViewModel)subject.BindModel(controller.ControllerContext, bindingContext);

    It should_null_that_value_out = () => incomingModel.RestrictedProperty.ShouldBeNull();
}

public class when_an_unrestricted_user_changes_a_restricted_property : TestModelSpec
{
    private Establish context = () => securityTasks.Stub(st =>
                               st.DoesUserHaveOperation(user.Username, UserOperations.ReclassAllowed)).Return(true);


    Because of = () => incomingModel = (TestSplitDetailViewModel)subject.BindModel(controller.ControllerContext, bindingContext);

    It should_permit_the_change = () => incomingModel.RestrictedProperty.ShouldEqual("12");
}

修改

这是我现在的答案。我看到有些人可能会质疑我的测试DefaultModelBinder.BindProperty。我正在测试我的自定义覆盖。

答案 2 :(得分:-1)

我会使用白名单或黑名单,并在您的模型上明确调用模型绑定。 e.g。

[HttpPost]
public ActionResult Edit(int id) {
var item = db.GetByID(id); // get from DB
var whitelist = [ "Name", "Title", "Category", etc ]; // setup the list of fields you want to update

UpdateModel(item, whitelist);
}