该平台是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);
答案 0 :(得分:0)
您可以使用Bind属性来指定要在绑定中包含或排除的属性。这是一篇很好的基础文章,可以获得更多信息。
答案 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);
}