セキュリティ
PowerPortalsProは、 テーブル レベルと レコードレベルの両方でアクセスを管理できる、柔軟でコード駆動型のセキュリティモデルを提供します。セキュリティは、権限ハンドラークラスを作成し、依存性インジェクションコンテナに登録することで実装されます。
概要
必要な制御レベルに応じて実装できるインターフェースは2つあります:
ITablePermissionHandler— ユーザーが特定のテーブルに対して作成、読み取り、更新、削除、追加、または追加操作を実行できるかどうかを制御します。意思決定はユーザーのアイデンティティのみに基づいて行われます。ITableRecordPermissionHandler— 実際の操作TableRecordを受け取るレコードレベルのオーバーロードをITablePermissionHandler拡張し、「ユーザーは自分のレコードのみを編集できる」といった細かいルールを可能にします。
ヒント
テーブルレベルのチェック(例:「認証済みのユーザーがこのテーブルを読める」など)だけが必要な場合は、
ITablePermissionHandlerを使いましょう。レコードレベルのロジック(例:「ユーザーは自分が所有するレコードしか削除できない」)が必要な場合はITableRecordPermissionHandlerを使います。
ベストプラクティスは、ソースからフィルタリングすること
許可ハンドラーはアクセスを 強制します が、 フィルタリングの代わりにはなりません。レコードレベルのハンドラは、Dataverseからレコードのページを取得し た後 に動作するため、行を隠すためにそれらに頼ることはグリッドページング(50ページで表示される数が少なくなることもあります)や総数を歪め、行を取得して破棄するクエリの無駄を生みます。データを現在のユーザーにスコープ化する必要がある場合は、クエリレベルでフィルタリングを行い、
IFetchXmlQueryFilterInterceptor(下記参照)や現在のユーザーを考慮したカスタムビューで、Dataverseはユーザーが見るべき行だけを返し、ページを正確にグリッド化します。許可ハンドラーは執行保証として使っておきましょう。
Dataverse-Native Security (プレビュー)
今後推奨されるセキュリティモデルでは、Power Pagesの統合されたDataverseセキュリティ機能を基盤に、Dataverse自身がポータルユーザーに対してセキュリティを強制できるようになります。コード内で権限ハンドラーを書く代わりに、標準的なDataverseセキュリティロールでアクセスを管理します。これは内部ユーザーで使われる役割、権限、管理ツールと同じです。このモデルは現在プレビュー段階にあり、Microsoftの機能とPower Portals Proのサポートの両方が対象であり、Power Portals ProはMicrosoftのリリーススケジュールに従って一般提供される予定です。
有効化されると、PowerPortalsProは各ポータル連絡先に対してシステム管理(C2)systemuserを自動的にプロビジョニングします。新規連絡先の登録時と、既存連絡先のサインイン時に遅く対応する際の対応で、連絡先、システムユーザー、Power Pagesサイトをリンクするpowerpagesusermappingレコードも含まれます。セッションのDataverseコールはすべてそのシステムユーザーになりすまし、Power Pagesがあなたのウェブロールとテーブル権限から同期するセキュリティロールが、ユーザーが何を見たり何をしたりできるかを正確に管理します。セッションには多くのContactStubUserがPortalUserTypeされるため、UIはプロビジョニング済みユーザーと実際の内部ユーザーを常に区別できます。
注記
このセキュリティモデルを使うには、Dataverse環境内でPower Pagesのウェブサイトを作成する必要があります。これがMicrosoft Frameworkコンポーネントをインストールし、統一セキュリティ機能を有効にするためのものです。有料のPower Pagesプランは不要で、機能のインストールと有効化後はすぐにウェブサイト自体を削除できます。
PowerPagesSiteIdが参照するPower Pagesのサイトレコード(powerpagesite)は環境内に存在している必要があります。なぜなら、ユーザーマッピングやセキュリティロール同期がそれに対してリゾリュートするからです。
Dataverseネイティブセキュリティの実現
サーバーのProgram.csでSecurityOptionsを有効にし、Power PagesのサイトアカウントのIDを入力してください:
builder.Services.Configure<SecurityOptions>(options =>
{
options.DataverseSecurity.ProvisionSystemUsers = true;
options.DataverseSecurity.PowerPagesSiteId = new Guid("<your powerpagesite id>");
});
構成はアプリケーション起動時に検証されます。 ProvisionSystemUsers が有効になったら、 PowerPagesSiteId を設定し、既存の powerpagesite レコードに解決しなければならず、そうでなければアプリケーションはガイダンスから起動できません。
Power Pagesと同じようにアクセス権を付与してください:Power Pages管理アプリで ウェブロール と テーブル権限 を設定し、連絡先にウェブロールを割り当てる(管理アプリを通じてまたはプログラムで)。プラットフォームはそれらを自動生成されるセキュリティロール( PowerPages_*と名付けられた)に同期し、ウェブロールのメンバーシップ変更時に自動的に割り当てます。設定は完全にPower Pages上で行われているため、ポータルは実際のPower Pagesサイトとスムーズに移動できます。役割や権限の変更は、Dataverseのキャッシュ内で伝播するまでに数秒から数分かかることがあることに注意してください。
はじめに
1. 権限ハンドラーを作成する
ITablePermissionHandlerやITableRecordPermissionHandlerを実装するクラスを作成しましょう。Tableプロパティをハンドラが適用するDataverseテーブルの論理名に設定し、各権限メソッドを実装します。
以下は、特定のテーブルに対してすべてのユーザーに完全なアクセス権を与える簡単な例です:
public class MyTablePermissionHandler : ITablePermissionHandler
{
public string Table => "my_customtable";
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanUpdateAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanDeleteAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanAppendAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanAppendToAsync(Guid? userId) => Task.FromResult(true);
}
2. 依存注入におけるレジスタ
プロジェクトの Program.cs ファイル内のDIコンテナ(またはサービス収集拡張メソッド)にハンドラーを登録してください:
builder.Services.AddSingleton<ITablePermissionHandler, MyTablePermissionHandler>();
ハンドラーはDIコンテナから解決されるため、必要なサービス(データベースコンテキストやユーザーサービスなど)をインジェクション(コンストラクタインジェクション)で注入できます。
記録レベルのセキュリティ
アクセスが操作される特定のレコードに依存するシナリオでは、ITableRecordPermissionHandlerを実装してください。このインターフェースはTableRecordを受信する過負荷を拡張してITablePermissionHandlerします。
以下の例では、すべてのユーザーがレコードをグローバルに読み取れるようにしますが、書き込み操作は現在のユーザーが所有するレコードに限定しています。
public class ContactPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "contact";
public List<string> RequiredColumns => [];
// テーブルレベル:広くアクセスを許可する
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId)
=> Task.FromResult(userId != null && userId != Guid.Empty);
// レコードレベル:レコード所有者への書き込みを制限する
public Task<bool> CanUpdateAsync(
Guid? userId, TableRecord recordWithUpdates, TableRecord currentRecord)
{
return Task.FromResult(userId == currentRecord.Id);
}
public Task<bool> CanDeleteAsync(Guid? userId, TableRecord record)
{
return Task.FromResult(userId == record.Id);
}
// ...残りのメソッドを実装
}
注記
CanUpdateAsyncレコードレベルのオーバーロードは、提案された更新情報(recordWithUpdates)と現在永続化状態(currentRecord)のレコードの両方を提供し、値を比較したり特定のフィールド変更を検証したりすることを可能にします。
必須欄
RequiredColumnsプロパティは、ハンドラーがレコードレベルの権限を評価するためにどの列を取得する必要があるかを指定します。フレームワークは、これらの列がまだ含まれていない場合、自動的にFetchXMLクエリに追加し、ビューやクエリにこれらの列が含まれていなくても、ハンドラーが常に必要なデータを保持できるようにします。
以下の例では、 ppp_owningportaluserid ルックアップ列を使って、現在のユーザーが所有するレコードへのアクセスを制限しています。
public class RegionPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "ppp_region";
public List<string> RequiredColumns => ["ppp_owningportaluserid"];
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(userId != null);
public Task<bool> CanReadAsync(Guid? userId, TableRecord record)
{
return Task.FromResult(
record.GetValueOrDefault<LookupValue>("ppp_owningportaluserid")?.Value == userId);
}
// ...残りのメソッドを実装
}
注記
RequiredColumnsが空のリストを返し、ハンドラーのレコードレベルのメソッドがrecord.Idのみを使用する場合、クエリに追加の列は追加されません。ownerプロパティは、RequiredColumnsに関係なく常にプラットフォームから提供されます。
fetchXMLクエリフィルタインターセプタ
テーブルレベルやレコードレベルのセキュリティに加え、PowerPortalsProは IFetchXmlQueryFilterInterceptor をサポートしています。これは、FetchXMLクエリを実行前に修正できるインターフェースです。これは、ユーザーが自分やチームの記録のみを見られるようにするなど、行レベルのフィルタリングを強制するのに役立ちます。
IFetchXmlQueryFilterInterceptorITablePermissionHandlerを拡張するため、各インターセプタはTableプロパティと標準パーミッションメソッドを通じてテーブルレベルの権限も定義します。インターセプタのOnQueryAsyncメソッドは、指定されたテーブルを対象としたクエリに対してのみ呼び出されます。
レコードレベルの権限ハンドラが各レコードを取得し た後 に評価するのとは異なり、クエリフィルターインターセプターはクエリがDataverseに到達 する前に クエリを修正します。フィルタアウトされたレコードは一度も取得されないため、パフォーマンスとセキュリティが向上し、さらに重要なのは、検索後にフィルタリングをかけるとページ数が短くなり、合計数が誤ってカウントされるため、グリッドページや総カウントの精度を保つことができます。
1. クエリフィルターインターセプタの作成
IFetchXmlQueryFilterInterceptorを実装し、Tableプロパティをテーブルの論理名に設定し、権限メソッドを実装し、OnQueryAsyncのFetchXMLBuilderを使ってクエリにフィルターを追加します。FetchXmlParametersレコードは現在のユーザーのEntityReferenceを提供します。
FetchXMLBuilder
以下の例は、 contact テーブルへのクエリを現在のユーザーと一致するレコードのみを返すように制限しています。
public class ContactQueryFilterInterceptor : IFetchXmlQueryFilterInterceptor
{
public string Table => "contact";
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanUpdateAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanDeleteAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanAppendAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanAppendToAsync(Guid? userId) => Task.FromResult(false);
public Task<FetchXMLBuilder> OnQueryAsync(
FetchXMLBuilder fetchXmlBuilder, FetchXmlParameters parameters)
{
var filter = fetchXmlBuilder.Fetch.Entity.AddFilter();
var condition = filter.AddCondition();
condition.Column = "contactid";
condition.Operator = ConditionOperator.Equal;
condition.Value = parameters.CurrentUser.Id.ToString();
return Task.FromResult(fetchXmlBuilder);
}
}
2. 依存注入におけるレジスタ
インターセプターを IFetchXmlQueryFilterInterceptor と ITablePermissionHandler の両方として登録し、クエリフィルタリングとテーブルレベルの権限の両方を適用してください。複数のインターセプターを登録でき、それぞれがテーブルを対象としたクエリに対して実行されます。
builder.Services.AddTransient<ContactQueryFilterInterceptor>();
builder.Services.AddTransient<ITablePermissionHandler>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
builder.Services.AddTransient<IFetchXmlQueryFilterInterceptor>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
ヒント
IFetchXmlQueryFilterInterceptorがITablePermissionHandlerを拡張するため、単一のクラスでテーブルレベルの権限とクエリレベルのフィルタリングの両方を処理します。OnQueryAsyncメソッドはインターセプタのTableプロパティに一致するクエリにのみ呼び出されるため、メソッド内のテーブル名を確認する必要はありません。
組み込みの基本クラス
このフレームワークは、一般的なセキュリティパターンを簡素化するための基本クラスを提供します:
AnonymousPermissionHandler— すべての操作をすべてのユーザー(匿名ユーザーを含む)に付与します。公開可能なテーブルに便利です。単純に相続してTableプロパティを設定するだけです。
API参照
ITablePermissionHandler Interface
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
Table | string | この権限ハンドラーが適用されるテーブルの論理名。 |
Table方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | ユーザーがレコードを追加できるかどうかを判断する方法。 |
CanAppendToAsync | Guid? userId | Task<bool> | ユーザーがレコードに付加できるかどうかを判断する方法。 |
CanCreateAsync | Guid? userId | Task<bool> | ユーザーがレコードを作成できるかどうかを判断する方法。 |
CanDeleteAsync | Guid? userId | Task<bool> | ユーザーがレコードを削除できるかどうかを判断する方法。 |
CanReadAsync | Guid? userId | Task<bool> | ユーザーがレコードを読み取れるかどうかを判断する方法。 |
CanUpdateAsync | Guid? userId | Task<bool> | ユーザーがレコードを更新できるかどうかを判断する方法。 |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncITableRecordPermissionHandler Interface
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
RequiredColumns | List<string> | このハンドラがレコードレベルの権限を評価するために取得しなければならない列の論理名の一覧。 | |
Table | string | この権限ハンドラーが適用されるテーブルの論理名。 |
RequiredColumnsTable方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
CanAppendAsync | Guid? userId TableRecord record | Task<bool> | ユーザーが提供されたレコードを付加できるかどうかを判断する方法。 |
CanAppendToAsync | Guid? userId TableRecord record | Task<bool> | ユーザーが提供されたレコードに付加可能かどうかを判断する方法。 |
CanCreateAsync | Guid? userId TableRecord record | Task<bool> | ユーザーが提供されたレコードを作成できるかどうかを判断する方法。 |
CanDeleteAsync | Guid? userId TableRecord record | Task<bool> | ユーザーが提供されたレコードを削除できるかどうかを判断する方法。 |
CanReadAsync | Guid? userId TableRecord record | Task<bool> | ユーザーが提供されたレコードを読めるかどうかを判断する方法。 |
CanUpdateAsync | Guid? userId TableRecord recordWithUpdates TableRecord currentRecord | Task<bool> | ユーザーが提供されたレコードを更新できるかどうかを判断する方法。 |
CanAppendAsyncTableRecord record
CanAppendToAsyncTableRecord record
CanCreateAsyncTableRecord record
CanDeleteAsyncTableRecord record
CanReadAsyncTableRecord record
CanUpdateAsyncTableRecord recordWithUpdates
TableRecord currentRecord
IFetchXmlQueryFilterInterceptor Interface
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
Table | string | この権限ハンドラーが適用されるテーブルの論理名。 |
Table方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | ユーザーがレコードを追加できるかどうかを判断する方法。 |
CanAppendToAsync | Guid? userId | Task<bool> | ユーザーがレコードに付加できるかどうかを判断する方法。 |
CanCreateAsync | Guid? userId | Task<bool> | ユーザーがレコードを作成できるかどうかを判断する方法。 |
CanDeleteAsync | Guid? userId | Task<bool> | ユーザーがレコードを削除できるかどうかを判断する方法。 |
CanReadAsync | Guid? userId | Task<bool> | ユーザーがレコードを読み取れるかどうかを判断する方法。 |
CanUpdateAsync | Guid? userId | Task<bool> | ユーザーがレコードを更新できるかどうかを判断する方法。 |
OnQueryAsync | FetchXMLBuilder fetchXmlBuilder FetchXmlParameters parameters | Task<FetchXMLBuilder> | FetchXMLクエリを実行前に修正するために呼び出されます。 |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncOnQueryAsyncFetchXmlParameters parameters
