Vac*_*ano 46 c# odata asp.net-web-api-odata
背景:
我有一个非常大的OData模型,目前正在使用WCF数据服务(OData)来公开它.但是,微软已经表示WCF数据服务已经死亡,Web API OData就是他们的目标.
所以我正在研究如何使Web API OData与WCF数据服务一起工作.
问题设置:
模型的某些部分不需要保护,但有些部分需要保护.例如,客户列表需要安全性来限制谁可以读取它,但我有其他列表,如产品列表,任何人都可以查看.
Customers实体有许多可以访问它的关联.如果您计算2个级别的关联,那么可以通过关联(通过关联)获得数百种方式.例如Prodcuts.First().Orders.First().Customer.由于客户是我系统的核心,因此您可以从大多数实体开始,最终将您的方式与客户列表相关联.
WCF数据服务有一种方法可以通过以下方法为特定实体提供安全性:
[QueryInterceptor("Customers")]
public Expression<Func<Customer, bool>> CheckCustomerAccess()
{
return DoesCurrentUserHaveAccessToCustomers();
}
Run Code Online (Sandbox Code Playgroud)
当我看到Web API OData时,我没有看到这样的东西.另外,我非常担心,因为我正在制作的控制器在跟随关联时似乎没有被调用.(意思是我不能把安全放在CustomersController.)
我担心我将不得不尝试以某种方式列举协会如何获得客户并为每个客户提供安全性的所有方式.
问题:
有没有办法将安全性放在Web API OData中的特定实体上? (无需枚举所有可能以某种方式扩展到该实体的关联?)
SKl*_*ous 44
更新:此时我建议您遵循由vaccano发布的解决方案,该解决方案基于OData团队的输入.
您需要做的是创建一个继承自EnableQueryAttribute for OData 4的新属性(或QuerableAttribute,具体取决于您正在与之交谈的Web API\OData的版本)并覆盖ValidateQuery(与继承自QuerableAttribute时的方法相同)检查是否存在合适的SelectExpand属性.
要设置新的新项目以进行测试,请执行以下操作:
码:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
builder.EntitySet<OrderDetail>("OrderDetails");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
//config.AddODataQueryFilter();
config.AddODataQueryFilter(new SecureAccessAttribute());
Run Code Online (Sandbox Code Playgroud)
在上文中,Customer,Order和OrderDetail是我的实体框架实体.config.AddODataQueryFilter(新的SecureAccessAttribute())注册我的SecureAccessAttribute以供使用.
码:
public class SecureAccessAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
if(queryOptions.SelectExpand != null
&& queryOptions.SelectExpand.RawExpand != null
&& queryOptions.SelectExpand.RawExpand.Contains("Orders"))
{
//Check here if user is allowed to view orders.
throw new InvalidOperationException();
}
base.ValidateQuery(request, queryOptions);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,我允许访问Customers控制器,但我限制对订单的访问.我实施的唯一控制器如下:
public class CustomersController : ODataController
{
private Entities db = new Entities();
[SecureAccess(MaxExpansionDepth=2)]
public IQueryable<Customer> GetCustomers()
{
return db.Customers;
}
// GET: odata/Customers(5)
[EnableQuery]
public SingleResult<Customer> GetCustomer([FromODataUri] int key)
{
return SingleResult.Create(db.Customers.Where(customer => customer.Id == key));
}
}
Run Code Online (Sandbox Code Playgroud)
我只想对其他一些解决方案发表评论:
Vac*_*ano 18
当我问Web API OData团队时,我得到了这个答案.它似乎与我接受的答案非常相似,但它使用了IAuthorizationFilter.
为了完整性,我想我会在这里发布:
对于实体集或导航属性出现在路径中,我们可以定义消息处理程序或授权过滤器,并在该检查中检查用户请求的目标实体集.例如,一些代码片段:
public class CustomAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get { return false; } }
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
// check the auth
var request = actionContext.Request;
var odataPath = request.ODataProperties().Path;
if (odataPath != null && odataPath.NavigationSource != null &&
odataPath.NavigationSource.Name == "Products")
{
// only allow admin access
IEnumerable<string> users;
request.Headers.TryGetValues("user", out users);
if (users == null || users.FirstOrDefault() != "admin")
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
return continuation();
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new CustomAuthorizationFilter());
Run Code Online (Sandbox Code Playgroud)
对于查询选项中的$ expand授权,一个示例.
或者为每个用户或每组edm模型创建.一个样品.
虽然我认为@SKleanthous 提供的解决方案非常好。但是,我们可以做得更好。它有一些在大多数情况下不会成为问题的问题,我觉得它们已经足够了,我不想让它碰运气。
TL;DR:我们希望保护自己免受特定实体的侵害,但更具体地说,它们的类型没有误报。
这是从 ODataQueryOptions 类中获取所有类型(技术上为 IEdmTypes)的扩展方法:
public static class ODataQueryOptionsExtensions
{
public static List<IEdmType> GetAllExpandedEdmTypes(this ODataQueryOptions self)
{
//Define a recursive function here.
//I chose to do it this way as I didn't want a utility method for this functionality. Break it out at your discretion.
Action<SelectExpandClause, List<IEdmType>> fillTypesRecursive = null;
fillTypesRecursive = (selectExpandClause, typeList) =>
{
//No clause? Skip.
if (selectExpandClause == null)
{
return;
}
foreach (var selectedItem in selectExpandClause.SelectedItems)
{
//We're only looking for the expanded navigation items, as we are restricting authorization based on the entity as a whole, not it's parts.
var expandItem = (selectedItem as ExpandedNavigationSelectItem);
if (expandItem != null)
{
//https://msdn.microsoft.com/en-us/library/microsoft.data.odata.query.semanticast.expandednavigationselectitem.pathtonavigationproperty(v=vs.113).aspx
//The documentation states: "Gets the Path for this expand level. This path includes zero or more type segments followed by exactly one Navigation Property."
//Assuming the documentation is correct, we can assume there will always be one NavigationPropertySegment at the end that we can use.
typeList.Add(expandItem.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType);
//Fill child expansions. If it's null, it will be skipped.
fillTypesRecursive(expandItem.SelectAndExpand, typeList);
}
}
};
//Fill a list and send it out.
List<IEdmType> types = new List<IEdmType>();
fillTypesRecursive(self.SelectExpand?.SelectExpandClause, types);
return types;
}
}
Run Code Online (Sandbox Code Playgroud)
太好了,我们可以在一行代码中获得所有扩展属性的列表!这很酷!让我们在属性中使用它:
public class SecureEnableQueryAttribute : EnableQueryAttribute
{
public List<Type> RestrictedTypes => new List<Type>() { typeof(MyLib.Entities.Order) };
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
List<IEdmType> expandedTypes = queryOptions.GetAllExpandedEdmTypes();
List<string> expandedTypeNames = new List<string>();
//For single navigation properties
expandedTypeNames.AddRange(expandedTypes.OfType<EdmEntityType>().Select(entityType => entityType.FullTypeName()));
//For collection navigation properties
expandedTypeNames.AddRange(expandedTypes.OfType<EdmCollectionType>().Select(collectionType => collectionType.ElementType.Definition.FullTypeName()));
//Simply a blanket "If it exists" statement. Feel free to be as granular as you like with how you restrict the types.
bool restrictedTypeExists = RestrictedTypes.Select(rt => rt.FullName).Any(rtName => expandedTypeNames.Contains(rtName));
if (restrictedTypeExists)
{
throw new InvalidOperationException();
}
base.ValidateQuery(request, queryOptions);
}
}
Run Code Online (Sandbox Code Playgroud)
据我所知,唯一的导航属性是EdmEntityType(单一属性)和EdmCollectionType(集合属性)。获取集合的类型名称有点不同,因为它会将其称为“Collection(MyLib.MyType)”而不仅仅是“MyLib.MyType”。我们并不真正关心它是否是一个集合,所以我们得到了内部元素的类型。
我已经在生产代码中使用它一段时间了,并取得了巨大的成功。希望您会在此解决方案中找到相同的数量。