虽然我认为@SKleanthous 提供的解决方案非常好。但是,我们可以做得更好。它有一些在大多数情况下不会成为问题的问题,我觉得它们足以解决我不想让它碰运气的问题。
- 逻辑检查 RawExpand 属性,它可以根据嵌套的 $selects 和 $expands 包含很多东西。这意味着您可以获取信息的唯一合理方法是使用 Contains(),这是有缺陷的。
- 被迫使用 Contains 会导致其他匹配问题,例如您 $select 一个包含该受限属性作为子字符串的属性,例如:Orders和“ OrdersTitle ”或“ TotalOrders ”
- 没有什么可以证明名为 Orders 的属性属于您试图限制的“OrderType”。导航属性名称不是一成不变的,并且可以在不更改此属性中的魔术字符串的情况下进行更改。潜在的维护噩梦。
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;
}
}
太好了,我们可以在一行代码中获得所有扩展属性的列表!这很酷!让我们在属性中使用它:
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);
}
}
据我所知,唯一的导航属性是EdmEntityType(单一属性)和EdmCollectionType(集合属性)。获取集合的类型名称有点不同,因为它将调用它“Collection(MyLib.MyType)”而不是“MyLib.MyType”。我们并不关心它是否是一个集合,所以我们得到了内部元素的类型。
我已经在生产代码中使用了一段时间,并取得了巨大的成功。希望您能找到与此解决方案相同的数量。