2012-11-08 39 views
1

讓我們有下列實體系統:用於對實體控制訪問ASP.MVC最佳安全實踐

public class Doctor 
{ 
    public int ID { get; set; } 
    public int DepartmentID { get; set; } 
    public string Name { get; set; } 
    public ICollection<Recipe> Recipes { get; set; } 
} 

public class Patient 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
    public ICollection<Recipe> Recipes { get; set; } 
} 

public class Recipe 
{ 
    public int ID { get; set; } 
    public int DoctorID { get; set; } 
    public int PatientID { get; set; } 
    public Doctor Doctor { get; set; } 
    public Patient Patient { get; set; } 
    public ICollection<RecipeDetails> Details { get; set; } 
} 

public class RecipeDetails 
{ 
    public int ID { get; set; } 
    public Guid SomeGuid { get; set; } 
    public double SomeValue { get; set; } 
} 

我們也有要求:

  • 醫生應該可以編輯自己的食譜
  • 醫生應該能看到他的部門
  • 醫生內醫生只食譜應該能夠提供的食譜
  • 進行搜索
  • 醫生應該能夠提供的食譜來生成報告的詳細信息

現在我實現了以下安全檢查:

public void ValidateAccess(Doctor doctor, Recipe aRecipe, EntityAction action) 
{ 
    if (action == EntityAction.Modify && doctor.ID == aRecipe.Doctor.ID) 
     return; 
    if (action == EntityAction.Read && doctor.DepartmentID == aRecipe.Doctor.DepartmentID) 
     return 
    throw new SecurityException(); 
} 

這工作適合簡單的方法,當我有receipe實體,我可以通過在我的邏輯方法開始時調用此方法來輕鬆驗證訪問。

但現在我有問題,該解決方案將無法搜索,當我沒有確切的實體,而是對他們的一些統計上報工作。

讓我們想象,我要生成患者的名爲「aName」是有分量「someGuid」 receipes報告,我會有一些查詢與2個標準:

var res = RecipeRepository.Query(r => aName.Contains(r.Patient.Name)).SelectMany(r => r.Details).Where(d => d.SomeGuid == someGuid).Sum(d => d.SomeValue); 

這個查詢是不正確的,它將顯示所有食譜的統計數據,包括應該隱藏的食譜。 爲了解決這個問題,我們應該加上我們的接入條件,我們的查詢:

currentDoctor.DepartmentID == r.Doctor.DepartmentID 

所以現在我有查詢:

var res = RecipeRepository.Query(r => aName.Contains(r.Patient.Name) && currentDoctor.DepartmentID == r.Doctor.DepartmentID).SelectMany(r => r.Details).Where(d => d.SomeGuid == someGuid).Sum(d => d.SomeValue); 

的問題是,我應該在系統中,這部分添加到每個查詢它對信息進行任何計算。

更新(2012年11月12日):

第一個例子是非常簡單的,並且StuartLC在他的帖子中提到的可以解決的。 但是我們的系統中有更復雜的報告。例如 - 顯示所有患者的食譜中含有某種guid的患者。 現在我們的查詢從另一個存儲庫開始,所以我們不能應用RecipeRepository中的私有或受保護的方法。 下面是示例查詢:

var res = PatientRepository.Query(p => p.Name.Contains(aName) && p.Recipes.Any(r => r.Details.Any(d => d.SomeGuid == someGuid))); 

在這種情況下,我們仍然需要直接添加我們的過濾器進入查詢:

var res = PatientRepository.Query(p => p.Name.Contains(aName) && p.Recipes.Any(r => currentDoctor.DepartmentID == r.Doctor.DepartmentID && r.Details.Any(d => d.SomeGuid == someGuid))); 

結束時更新。

什麼模式或做法可以適用於使該解決方案更容易,並防止複製粘貼表達每個查詢? 我會感謝您的回答和建議。

+1

難道不應該是術士和藥水,醫生和處方,或廚師和食譜? – Tr1stan

+0

可能是你對的域名,實體應該是處方...我會考慮它,但問題是關於訪問限制。 –

+0

當然,我只是沒有幫助,有時經過漫長的一天後...... – Tr1stan

回答

1

如果你的倉庫模式Query()方法返回一個非物化IQueryable<T>,那麼你就可以重構數據訪問限制的關切轉化的輔助方法,每一個「restrictable」實體,例如:

private IQueryable<Recipe> ApplyAccessFilters(IQueryable<Recipe> query, User user) 
{ 
    IQueryable<Recipe> filteredQuery = query; 

    // Custom method to determine if user is restricted to just his/her recipes 
    if (!CheckUserPermission(currentUser, Access.MaySeeAllRecipies))) 
    { 
     filteredQuery = filteredQuery 
           .Where(r => r.DepartmentId = currentUser.DepartmentId) 
    } // Else no restriction, e.g. Admin Users can access all recipes 

    // Other access related filters here 

    return filteredQuery; 
} 

每需要訪問限制則可以使用這種方法建立起來的結果過濾器表達你的MVC控制器動作,如:

var recipes = RecipeRepository.Query(r => r.SomeFields == someFilters); // NB, do NOT materialize the lambda 
var recipesForDoctor = ApplyAccessFilters(recipes, currentUser) // Access Filter 
... 
return View(recipesForDoctor); // [AsEnumerable()] - Consider materializing here 

您可以處理其他問題,如分頁,以同樣的方式。

更妙的是,你可以讓這個訪問過濾器fluent,在這種情況下,過濾器是容易對眼睛:

return View(RecipeRepository 
      .Query(r => r.SomeFields == someFilters) 
      .ApplyAccessFilters(currentUser) 
      .Paginate(pagingInfo) 
      .AsEnumerable()); 
+0

謝謝你的回答。它適用於從RecipeRepository開始的所有查詢,但這並非總是如此。我需要一些更通用的解決方案,即使在查詢從另一個存儲庫開始時也可以應用,並且食譜在查詢內...我會考慮更好地說明我的問題。 –

+0

我已經爲我的問題添加了一個示例,請看看這個,並告訴我您對此有何看法。 –