49

背景:
現在、WCF Data Services (OData) を使用して公開している非常に大きな OData モデルがあります。ただし、Microsoft は、WCF Data Services は終了しており、Web API ODataが進むべき道であると述べています。

そのため、Web API OData と WCF Data Services を機能させる方法を研究しています。

問題の設定:
モデルの一部は保護する必要はありませんが、一部は保護する必要があります。たとえば、顧客リストには、それを読むことができる人を制限するためのセキュリティが必要ですが、製品のリストなど、誰でも表示できる他のリストがあります。

Customers エンティティには、それに到達できる多くの関連付けがあります。2 つ以上のレベルのアソシエーションを数えると、(アソシエーションを介して) 何百もの方法で顧客に到達できます。たとえばProdcuts.First().Orders.First().Customer。顧客は私のシステムの中核であるため、ほとんどのエンティティから始めて、最終的に顧客リストに関連付けることができます。

WCF Data Services には、次のような方法で特定のエンティティにセキュリティを設定する方法があります。

[QueryInterceptor("Customers")]
public Expression<Func<Customer, bool>> CheckCustomerAccess()
{
     return DoesCurrentUserHaveAccessToCustomers();
}

Web API OData を見ると、このようなものは見当たりません。さらに、私が作成しているコントローラーは、関連付けが行われたときに呼び出されないように見えるため、非常に心配しています。(つまり、セキュリティを に入れることはできませんCustomersController。)

アソシエーションが顧客に到達する方法をすべて列挙し、それぞれにセキュリティを設定する必要があるのではないかと心配しています.

質問:
Web API OData の特定のエンティティにセキュリティを設定する方法はありますか? (どういうわけかそのエンティティに展開できるすべての関連付けを列挙する必要はありませんか?)

4

7 に答える 7

47

更新: 現時点では、OData チームからの入力に基づいて、vaccano によって投稿されたソリューションに従うことをお勧めします。

あなたがする必要があるのは、OData 4 の EnableQueryAttribute (または、話している Web API\OData のバージョンに応じて QuerableAttribute) から継承する新しい属性を作成し、ValidateQuery (QuerableAttribute から継承する場合と同じメソッド) をオーバーライドすることです。適切な SelectExpand 属性の存在を確認してください。

これをテストするために新しいプロジェクトをセットアップするには、次の手順を実行します。

  1. Web API 2 を使用して新しい ASP.Net プロジェクトを作成する
  2. エンティティ フレームワークのデータ コンテキストを作成します。
  3. 新しい「Web API 2 OData コントローラー ...」コントローラーを追加します。
  4. WebApiConfigRegister(...) メソッドで、以下を追加します。

コード:

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());

上記の Customer、Order、および OrderDetail は、私のエンティティ フレームワーク エンティティです。config.AddODataQueryFilter(new SecureAccessAttribute()) は、私の SecureAccessAttribute を使用できるように登録します。

  1. 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);
    }
}

Customers コントローラーへのアクセスは許可していますが、Orders へのアクセスは制限していることに注意してください。私が実装した唯一のコントローラーは以下のものです。

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));
    }
}
  1. 保護するすべてのアクションに属性を適用します。EnableQueryAttribute とまったく同じように機能します。完全なサンプル (Nuget パッケージを含むすべてが終了するため、50Mb のダウンロードになります) は、http://1drv.ms/1zRmmVj にあります

他のいくつかのソリューションについても少しコメントしたいだけです。

  1. Leyenda のソリューションは、単純にその逆であるという理由だけで機能するわけではありませんが、それ以外は非常に近いものでした! 実際のところ、ビルダーはエンティティ フレームワークを調べてプロパティを展開し、Customers コントローラーにはまったくヒットしません。セキュリティ属性を削除しても、expand コマンドをクエリに追加すると、注文は正常に取得されます。
  2. モデル ビルダーを設定すると、削除したエンティティへのグローバルなアクセスと全員からのアクセスが禁止されるため、適切な解決策ではありません。
  3. Feng Zhao のソリューションはうまくいく可能性がありますが、すべてのクエリで保護したいアイテムを手動で削除する必要があり、これは適切なソリューションではありません。
于 2014-08-03T15:34:21.377 に答える
19

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());

クエリ オプションの $expand 認可の場合、サンプル。

または、ユーザーごとまたはグループごとの edm モデルを作成します。サンプル。

于 2014-08-07T17:51:44.970 に答える
5

@SKleanthousが提供するソリューションは非常に優れていると思いますが。しかし、もっとうまくやることができます。ほとんどの場合、問題にならない問題がいくつかありますが、偶然に任せたくないほど十分な問題だと感じています.

  1. このロジックは、入れ子になった $selects と $expands に基づいて、多くのものを持つことができる RawExpand プロパティをチェックします。つまり、情報を取得できる唯一の合理的な方法は、Contains() を使用することですが、これには欠陥があります。
  2. 強制的に Contains を使用すると、他のマッチングの問題が発生します。たとえば、その制限されたプロパティを部分文字列として含むプロパティを $select するとします。例: Ordersと ' OrdersTitle ' または ' TotalOrders '
  3. 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)" と呼ばれるため、少し異なります。コレクションであるかどうかはあまり気にしないので、内部要素のタイプを取得します。

私はこれを本番コードでしばらく使用しており、大きな成功を収めています。うまくいけば、このソリューションで同じ量を見つけることができます.

于 2015-11-09T22:02:55.427 に答える
1

プログラムで EDM から特定のプロパティを削除できます。

var employees = modelBuilder.EntitySet<Employee>("Employees");
employees.EntityType.Ignore(emp => emp.Salary);

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-security-guidanceから

于 2014-08-01T18:36:20.963 に答える
0

ValidateQuery オーバーライドは、ユーザーがナビゲート可能なプロパティを明示的に展開または選択したことを検出するのに役立ちますが、ユーザーがワイルドカードを使用している場合は役に立ちません。たとえば、/Customers?$expand=*. 代わりに、特定のユーザーのモデルを変更することをお勧めします。これは、EnableQueryAttribute の GetModel オーバーライドを使用して行うことができます。

たとえば、最初に OData モデルを生成するメソッドを作成します。

public IEdmModel GetModel(bool includeCustomerOrders)
{
    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

    var customerType = builder.EntitySet<Customer>("Customers").EntityType;
    if (!includeCustomerOrders)
    {
        customerType.Ignore(c => c.Orders);
    }
    builder.EntitySet<Order>("Orders");
    builder.EntitySet<OrderDetail>("OrderDetails");

    return build.GetModel();
}

...次に、EnableQueryAttribute から継承するクラスで、GetModel をオーバーライドします。

public class SecureAccessAttribute : EnableQueryAttribute
{
    public override IEdmModel GetModel(Type elementClrType, HttpRequestMessage request, HttpActionDescriptor actionDescriptor)
    {
        bool includeOrders = /* Check if user can access orders */;
        return GetModel(includeOrders);
    }
}

これにより、複数の呼び出しで同じモデルの束が作成されることに注意してください。各呼び出しのパフォーマンスを向上させるために、IEdmModel のさまざまなバージョンをキャッシュすることを検討してください。

于 2016-12-29T21:56:01.570 に答える
0

これをデータベースに移動することは可能でしょうか? SQL サーバーを使用していると仮定して、各クライアント プロファイルに必要なプロファイルに一致するユーザーを設定します。簡単に言うと、1 つのアカウントに顧客アクセスがあり、もう 1 つのアカウントがアクセスできないということです。

次に、データ要求を行うユーザーをこれらのプロファイルのいずれかにマップし、接続文字列を変更して関連する資格情報を含める場合。次に、許可されていないエンティティにリクエストを行うと、例外が発生します。

まず、これが問題の誤解である場合は申し訳ありません。私はそれを提案していますが、データベース内の余分なデータアクセス制御とメンテナンスであることが最も差し迫った多くの落とし穴を見ることができます。

また、エンティティ モデルを生成する T4 テンプレート内で何かできるのではないかと考えています。アソシエーションが定義されている場合、そこにパーミッション制御を挿入できる可能性があります。繰り返しますが、これはコントロールを別のレイヤーに配置します - 私よりもT4をよく知っている人がこれを機能させる方法を見ることができる場合に備えて、そこに配置しています.

于 2014-08-02T20:40:28.693 に答える
-2

独自の Queryable 属性を Customers.Get() または Customers エンティティにアクセスするために使用されるメソッド (直接またはナビゲーション プロパティを介して) に配置できます。属性の実装では、ValidateQuery メソッドをオーバーライドして、次のようにアクセス権を確認できます。

public class MyQueryableAttribute : QueryableAttribute
{
    public override void ValidateQuery(HttpRequestMessage request, 
    ODataQueryOptions queryOptions)
    {
        if (!DoesCurrentUserHaveAccessToCustomers)
        {
            throw new ODataException("User cannot access Customer data");
        }

        base.ValidateQuery(request, queryOptions);
    }
}

コントローラーがナビゲーション プロパティで呼び出されない理由がわかりません。そのはず...

于 2014-08-02T23:49:19.427 に答える