2012-11-08 38 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

我已经为我的问题添加了一个示例,请看看这个,并告诉我您对此有何看法。 –