2012-09-19 61 views
11

在我的控制器模型被修改前(更新或刪除)我試圖驗證用戶執行操作實際上擁有他們正在嘗試修改的對象。模型所有權檢查

我目前正在做這個方法級別,它似乎有點多餘。

[HttpPost] 
public ActionResult Edit(Notebook notebook) 
{ 
    if (notebook.UserProfileId != WebSecurity.CurrentUserId) { return HttpNotFound(); } 

    if (ModelState.IsValid) 
    { 
     db.Entry(notebook).State = EntityState.Modified; 
     db.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 
    return View(notebook); 
} 

是否有一種通用的方法可以在各種模型中重複使用?

是否可以用ActionFilter來做到這一點?

回答

2

我可以看到你有什麼問題 - 你依靠用戶輸入來執行安全檢查。

考慮您的代碼

if (notebook.UserProfileId != WebSecurity.CurrentUserId) 

筆記本都來自模型綁定。所以UserProfileId來自模型綁定。而且你可以非常高興地假冒它 - 例如我使用Firefox的TamperData來改變隱藏的UserProfileId的值,以匹配我的登錄信息,然後離開我去。

我最終在做什麼(在服務中,而不是在控制器中)是基於傳遞的唯一ID(例如Edit/2將使用2)從數據庫中拉回記錄,然後檢查User.Identity。對我在返回的數據庫記錄中擁有的當前所有者字段命名(以及傳遞的標識參數)。

因爲我從數據庫(存儲庫,無論什麼地方)撤回一個屬性不會爲此工作,而且我不確定在屬性的方法中您是否足夠通用。

+0

不會添加[綁定(排除=「UserProfileID」)]防止由模型綁定更改?理想情況下,我想要返回一個ViewModel,它不包含任何ID字段,只是將它們存儲在TempData或其他東西中(不太確定)... – mezmi

+0

是的,但它會從哪裏來? :) – blowdart

+0

是的,我想我沒有真正想過。就像我說的,我想返回一個不包含任何id字段的ViewModel,我只是不知道在MVC中臨時將這個ID存儲在哪裏,而不是使用Session>:)....這樣我就可以返回我的POCO到控制器,並使用AutoMapper或一些水合ViewModel。 – mezmi

3

該過濾器聽起來像一個好方法,但它有點有限。這將是很好,如果你能有這樣的過濾器:

[RequireOwnership<Notebook>(n => n.UserProfileId)] 

...但Attributes被限制在該數據類型都是允許的,而且我不認爲泛型是不允許發佈。所以,你可以有一個[RequireOwnership]屬性,通過使用反射檢查模型性質的作品,或者你可以創建一個自定義的驗證,而不是,你的模式是這樣的:

public class Notebook 
{ 
    [MatchesCurrentUserId] 
    public int UserProfileId { get; set; } 
} 

然後你ModelState.IsValid檢查就足夠了。

編輯:

另一種選擇發生在我身上。您可以在模型上使用過濾器和屬性(不一定是ValidationAttribute)。篩選器可以檢查您的請求模型,並與[MatchesCurrentUserId]進行比較,並與當前用戶標識進行比較以檢查屬性。

+0

我一直來回使用ValidationAttribute的想法。我喜歡這個主意,但同時覺得我應該返回Http 401 Unauthorized與驗證錯誤。有什麼想法嗎? – mezmi

+0

未經授權的回覆肯定會更好。您可以檢查驗證結果並確定是否應該返回401響應。或者,也許驗證屬性可能會拋出一個異常,最終轉化爲401響應(如可能拋出一個'HttpException') – Jacob

+0

啊,但是如果你必須檢查它們,那麼你是不是再次在控制器中寫更多的代碼? – jocull

1

當我在過去做過這樣的事情時,它確實沒有多好。對於我們的項目,我們有一個方法可以接受Notebook對象,並根據當前登錄的用戶進行檢查。

您可以使用所有不同的對象類型重載此方法,並使用一致的方法來檢查訪問權限。對不起,這是我知道的最好的方式。

[HttpPost] 
public ActionResult Edit(Notebook notebook) 
{ 
    if(!SessionUser.LoggedInUser.CheckAccess(notebook)) 
     return HttpNotFound(); 

    //Other code... 
} 

附: SessionUser是一個自定義類,我們基本上只是爲了管理當時登錄的人。你可以編寫類似的東西,但不要指望它默認爲.NET。

5

過濾器的方法可能是:

public class VerifyOwnership : IActionFilter 
{ 
    public void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     foreach(var parameter in filterContext.ActionParameters) 
     { 
      var owned = paramter.Value as IHaveAnOwner; 
      if(owned != null) 
      {      
       if(owned.OwnerId != WebSecurity.CurrentUserId) 
       { 
        // ... not found or access denied 
       } 
      } 
     } 
    } 

    public void OnActionExecuted(ActionExecutedContext filterContext) 
    { 

    } 
} 

,它假定模型,如筆記本電腦實現特定接口。

public interface IHaveAnOwner 
{ 
    int OwnerId { get; set; } 
} 

Blowdart好點,用戶可以在一個崗位與OWNERID篡改。我相信他們也可以篡改他們的身份驗證票證,但他們必須知道另一個用戶的票證,並篡改兩者以獲得與另一用戶相匹配的ID,我相信。

+0

這是一個很酷的解決方案 - 不得不圍繞新創建的對象和其他一些捕獲物進行編碼,我相信 – jocull