用于访问控制的Odata控制器中的附加过滤

时间:2016-04-14 20:46:29

标签: asp.net-mvc odata

我们正在使用WebAPI / OData项目为我们的应用程序公开数据层。

  

.NET Framework 4.6.1
  System.Web.OData,Version = 5.9.0.0

我们为各种数据集(外科医生,患者等)提供了几个OData控制器。如果我正在尝试搜索患者,我会发出类似以下内容的OData查询,以获得符合我所需标准的患者列表。

 http://localhost/MyService/Patients?$filter=contains($it/Name,”Joe”)

问题是我可能不被允许查看所有患者。由于RESTful API将在外部公开,因此我无法通过操纵URL来依赖客户端来强制执行安全性要求。

如果我使用声明授予用户访问特定患者的权限,我可以切实保护对单个用户的访问权限,但搜索匹配用户列表根本不起作用。

 http://localhost/MyService/Patients(Id)

如果我尝试解决这个问题并通过拥有患者的实体进行访问,则会在下一个更高级别发生相同类型的问题。换句话说,如果外科医生拥有患者,那么我如何获得我可以访问的外科医生名单?

有没有办法为OData构建的查询注入一些额外的标准来强制执行访问控制?

1 个答案:

答案 0 :(得分:1)

这是患者OData控制器的GET方法

public class PatientsController : BaseODataController<Patient>
{
    public override IQueryable<Patient> Get()
    {
        return base.Get();
    }
    ...
}

它返回 IQueryable&lt; Patient&gt; ...

添加访问控制过滤我们唯一需要做的就是添加其他谓词...例如:

public override IQueryable<Patient> Get()
{
    var qry = base.Get();
    return qry.Where(itm => itm.Name.FirstName.Contains("R"));
}

当控制器基础结构枚举 IQueryable&lt; T&gt; 时,它会评估整个工作单元并生成最终查询。

如果我对数据库运行跟踪并为其提供更复杂的OData查询,如

http://localhost/MyService/Patients?$filter=Surgeries/any(d:d/PreOpDataComplete%20eq%20true).

(换句话说,找到PreOpDataComplete标志为true的Surgeries)我将看到执行以下查询。

    exec sp_executesql N'SELECT 
        [Project1].[C1] AS [C1], 
        [Project1].[Id] AS [Id], 
        [Project1].[Name_FirstName] AS [Name_FirstName], 
        [Project1].[Name_LastName] AS [Name_LastName], 
        [Project1].[Gender] AS [Gender], 
        [Project1].[BirthDate] AS [BirthDate], 
        [Project1].[MedicalRecordId] AS [MedicalRecordId], 
        [Project1].[Surgeon_Id] AS [Surgeon_Id]
        FROM ( SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Name_FirstName] AS [Name_FirstName], 
            [Extent1].[Name_LastName] AS [Name_LastName], 
            [Extent1].[Gender] AS [Gender], 
            [Extent1].[BirthDate] AS [BirthDate], 
            [Extent1].[MedicalRecordId] AS [MedicalRecordId], 
            [Extent1].[Surgeon_Id] AS [Surgeon_Id], 
            1 AS [C1]
            FROM [dbo].[Patient] AS [Extent1]
            WHERE [Extent1].[Name_FirstName] LIKE N''%R%''
        )  AS [Project1]
        WHERE  EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Surgery] AS [Extent2]
            WHERE ([Project1].[Id] = [Extent2].[Patient_Id]) AND ([Extent2].[PreOpDataComplete] = @p__linq__0)
        )',N'@p__linq__0 bit',@p__linq__0=1

我得到了预期的结果

{
    "@odata.context":"http://localhost/MyService/$metadata#Patients",
    "value":[{
        "Name":{
            "FirstName":"Ronn",
            "LastName":"Black"
        },
        "Gender":"M",
        "BirthDate":"1917-02-02T00:00:00-08:00",
        "MedicalRecordId":"MRN 0001",
        "Id":"8bf6dcc4-3f00-4a40-980c-ceb13f8f5360"
    }]
}

如果我假设一个简单的安全模型,其中外科医生拥有他们自己的患者,并且我有一个外科医生ID列表,我可以在索赔中访问。我会有一个看起来像这样的外科医生实体:

public partial class Surgeon : IBaseEntity
{
    [Key]
    public Guid Id { get; set; }
    public virtual ICollection<Patient> Patients { get; set; }
    . . .
}

现在,如果我进行以下修改,我可以将所有搜索限制在我允许的患者身上:

public override IQueryable<Patient> Get()
{
    //IQueriable from the OData Selection
    var qry = base.Get();
 
    //Enforce Access Security
    var accessList = {get list of authorized surgeon id's from claims};
    var finalQry = uow.Surgeons
        .Where(s => accessList.Contains(s.Id))      //Restrict to Surgeons I'm allowed to see
        .SelectMany(surgeon => surgeon.Patients)    //All the patients I'm allowed to see (Left)
        .Join(qry,                                 //Join to Query (Right)
            allowedPatients => allowedPatients.Id, //Key for Left
            qryPatients => qryPatients.Id,         //Key for Right
            (allowedPatients, qryPatients) =>      //Iterate through Matches
            qryPatients);                          //Return the Matches from Right
 
    return finalQry;
}