2011-07-29 37 views
1

的平臺上更新由角色/權限只讀視圖模型的屬性是ASP.NET MVC 2策略,以防止用戶從服務器

我們有狀態的用戶故事:

請在[查看],除非用戶爲 a [適當的角色],否則不允許用戶編輯[屬性]。他們仍然必須能夠查看[財產]。

所以,我必須顯示這些人的領域,只是防止他們改變或更新屬性值。

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

我會爲您的屬性建議一個自定義IsInRole驗證規則,如果規則失敗,它將忽略模型的更改。 –

+0

@託德 - 感謝您的提示。沒有想過驗證規則。 模型綁定解決方案迫使我評估PropertyInfo是否存在我的目標屬性,然後才能檢查。在驗證規則中,我已經知道要執行檢查,因爲屬性本​​身會觸發它。將檢查出來並回來。 –

+0

@託德 - 也沒有看到這一點。我需要檢查模型的值是否在持久性中相對於此實體發生了變化。如果我將屬性放在屬性上,則無法獲取父對象類型和ID,也無法檢索相應的數據訪問對象。我不希望這個屬性在課堂上進行裝飾。也許我需要更努力的谷歌類似的例子。 –

回答

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。我正在測試自定義覆蓋。

-1

我會使用一個白名單或黑名單並調用模型上的模型明確約束力。例如

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

我無法做一個靜態列表。 –

+0

我沒有* down * downvote這個,順便說一句。 –

0

您可以使用Bind屬性,該屬性允許您指定要在綁定時包含或排除的屬性。這裏是一個很好的基本文章以獲取更多信息。

Exclude attributes using Bind attribute

+0

我更新了我的問題以排除靜態列表。產品所有者將會更改何時只讀這些內容。我正在爲此創建一個策略 - 一組布爾評估。仍然需要確定適用我的策略的最佳位置。我認爲它必須是一個自定義模型綁定器。看那裏。謝謝! –