@SKleanthousが提供するソリューションは非常に優れていると思いますが。しかし、もっとうまくやることができます。ほとんどの場合、問題にならない問題がいくつかありますが、偶然に任せたくないほど十分な問題だと感じています.
- このロジックは、入れ子になった $selects と $expands に基づいて、多くのものを持つことができる RawExpand プロパティをチェックします。つまり、情報を取得できる唯一の合理的な方法は、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;
}
}
1 行のコードで展開されたすべてのプロパティのリストを取得できます。それはいいね!属性でそれを使用しましょう:
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 (コレクション プロパティ) だけです。コレクションの型名の取得は、単に "MyLib.MyType" ではなく "Collection(MyLib.MyType)" と呼ばれるため、少し異なります。コレクションであるかどうかはあまり気にしないので、内部要素のタイプを取得します。
私はこれを本番コードでしばらく使用しており、大きな成功を収めています。うまくいけば、このソリューションで同じ量を見つけることができます.