テーブルセキュリティ
Power Portals Proは、ユーザーが触れられるテーブル、そのテーブルのどの列を表示・変更できるか、そしてどの行が戻ってくるかという3レベルでデータを保護します。これら3つすべては、テーブルごとに1つの登録で決定されます。その登録をAddTableAccessで説明してください— それはテーブルを付与する方法であり、プロジェクトテンプレートが登録するすべてのテーブルで何を使うかです。ルールが説明できない場合は、同じ3つの回答をITablePermissionProviderクラスとして書くこともできます。両者とも同じものを生成し、同じ方法で構成します。いずれかが許可するまでは何も付与されません:登録対象外のテーブルはアクセス不可であり、これが失敗クローズドデフォルトです。
ヒント
これを白紙のページから書く必要はありません。 生成されたプロジェクトには
secure-a-tableスキルがあります。コーディングエージェントにテーブルの保護を依頼し、どのテーブル、誰のため、どの行、どの操作、どの列を解析し、登録を書き、Power Portals Pro MCPサーバーを通じてDataverseで対応する変更を行います。アクセスがロールバウンドの場合はポータルロールを作成し、回答が示唆する権限をアプリケーションユーザーに付与します。その後、check_setupページがバインドされているテーブルでアクセス権が付与されないものや、カラムセキュリティをオプトアウトする登録を報告します。 MCPサーバー→はじめをご参照ください。
みんな、あるいは一部の人だけ
すべての登録は誰のためのものかを示しており、答えは二つあります。ポータルに対して登録すると、 コードネー ム(全員、またはサインインしているすべてのユーザー)に適用されます。 AddPortalRole に登録すると、その役割の保持者に適用されます。これはDataverseで管理者が行う データ 上の決定です。アクセス自体は両者で同じように説明されます。
| 登録 | 適用対象 | 誰がそれを手に入れるかを変えるために |
|---|---|---|
AddTableAccess("account", t => t.ForSignedInUsers()…) |
観客がカバーするすべての人、あらゆるリクエストに応じて | コードを編集して再デプロイしてください |
AddPortalRole("Credit Manager", role => role.WithTableAccess("account", …)) |
そのポータル役割を持つ連絡先だけで、他の誰もいない | Dataverseで役割の割り当てまたは解除 — デプロイメントなし |
各ポータル全体のオーディエンスごとにテーブルを一度登録できます — ForEveryone() 誰でも到達できるもの、サインイン ForSignedInUsers() 追加されるもの — そして2つを合計します。訪問者は最初のものを得ます。サインインしたユーザーは両方を得て、それぞれの操作はそれを付与する登録の行に限定されます。以下の2つの登録があれば、全員がすべての記事を読み、サインインした著者は記事を書き、自分の記事を編集することもできます。
builder.Services.AddTableAccess("ppp_article", t => t
.ForEveryone()
.AllRows()
.Allow(TableSecurityPermission.Read)
.Reading("ppp_title", "ppp_body"));
builder.Services.AddTableAccess("ppp_article", t => t
.ForSignedInUsers()
.RowsOwnedByContact("ppp_authorid")
.Allow(TableSecurityPermission.Create | TableSecurityPermission.Write)
.Writing("ppp_title", "ppp_body"));
ロールは加算しますが、決して減算しません。 ユーザーのアクセスはポータル全体の登録と、その人が持つすべてのロールの統合です。つまり、ポータル全体の登録は全員が立つ床であり、ロールはそれを上げるだけです。ロールはポータル全体の登録で付与された列や行を取り消すことはできません。ほとんどの人から何かを差し止めたい場合は、ポータル全体ではなくロールで付与してください。(すべてのグループが同意する必要がある場合は SecurityOptions.Roles.CompositionBehavior を Restrictive に設定してください。)
つまり、 そのような すべてのユーザーがアクセス権を持つべきなら、ポータルに対して登録してください。もし一部のユーザーだけがアクセス権を持つなら――そしてどの人が誰かはリリースなしで答えが変わる質問ですが――それをロールに入れるのです。ロール登録は、誰かがその名前のポータルロールレコードを作成し割り当てるまで何も付与しません。だからこそ、新しいプロジェクトに無害に置いてもいいのです。
テーブルのアクセスの記述
ほとんどのテーブルは同じルールを求めています:テーブルが誰のためか、どの行に到達するか、何をしてよいか、どの列を見るか。 AddTableAccess 4つすべてを1つの登録で表示し、通常の ITablePermissionProvider を生成します。下流の誰も違いを知りません。購入できるのは、合意しなければならない3つの決定(フィルター、作成、書き込み可能な列)が3回ではなく1回行われることです。
builder.Services.AddTableAccess("incident", t => t
.ForSignedInUsers()
.RowsOwnedByContact("customerid")
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Create)
.Reading("ticketnumber", "statuscode", "createdon")
.Writing("title", "description"));
オーディエンス、行の範囲、 Allow(…) はすべて必要です。しかし、どれもデフォルトは存在しません。なぜなら、すべての妥当なデフォルトは何かを省略してデータを公開する方法であり、登録が間違えれば起動時に失敗し、本番環境では失敗しないからです。
ロウスコープ
スコープはテーブルのすべてのクエリ—グリッド、ルックアップ、サブグリッド、チャート、そして保存や削除前のフェッチ—をフィルタリングします。名前付きスコープはポータルが常に必要とする形状をカバーしています:
| どの列が | 範囲 | オンクリエイト |
|---|---|---|
| すべての列 | AllRows() |
何も設定されていません |
| ユーザー自身の記録 | RowsOwnedByContact("customerid") |
検索はそれらに設定されています |
| 同じアカウントで同僚の記録も | RowsOfTheirAccount("customerid") |
検索は相手のアカウントに設定されています |
| 親にぶら下がる記録は、彼らが届く範囲に | RowsViaParent("salesorderid", "salesorder", p => …) |
何も決まっていません — 親自身のアクセスが決めます |
| 複数の形で彼らを指し示す記録 | RowsMatchingAny(r => r.RowsOwnedByContact("customerid"), r => r.RowsOfTheirAccount("customerid")) |
最初に記載された所有範囲のセット |
| 彼ら自身の接触列 | OwnRecordOnly() |
作成しない |
所有権スコープは、権限チェックの後にレコードが作成される際にカラム自体を設定します。ユーザーは書き込み権限や、指すテーブルに付加する必要がなく、値はリクエストではなくセッションから来られ、後で変更されるセーブは拒否されます。これが、誰かが作成したレコードを自分が対象とするフィルター内に保持する仕組みです。所有カラムが行をフィルタリングするカラムでない場合、プラグインがすでに設定している場合はWithoutStamping()OwnedBy('column')を使いましょう。
読むときと変化の違い
1つのスコープは、特に指示がない限りすべての操作に対応します。 RowsForWriting 、ユーザーが変更できる行を絞り込むが、表示される行は絞り込まれません。「すべての人がすべての連絡先を読み、それぞれ自分のものだけを編集する」という区別だけで、それ以外は何もありません。 RowsForDeleting はさらに削除を狭めます。なぜなら、レコードを修正することを許すことは、それを破棄することを許されるのとは異なるからです。各記録はその上の記録に戻るため、区別されない登録は区別されないことを意味します。クリエイトは影響を受けません。まだフィルタリングすべき行は存在しません。
builder.Services.AddTableAccess("contact", t => t
.ForSignedInUsers()
.AllRows() // reads: every contact
.RowsForWriting(r => r.OwnRecordOnly()) // updates and deletes: their own row
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Write)
.ReadingAllColumns());
名前付きのスコープが合わないとき
RowsMatching 登録を離れずに自分でフィルターを取る。FetchXML — <filter> 要素や <fetch> 全体で、そのフィルターと結合がコピーされ、サインインした連絡先には {currentUserId} が付けられる — あるいはリクエストごとに実行され、サービスの応答から作成されたFetchXMLか、自ら作成したフィルターを返すコールバックを与える。どちらもプロバイダーと同じ TableQueryContext を受け取るため、誰が質問しているのか、行が読み取られる、更新されるのか、削除されるのかが分かる。何も返さなければ行は得られない: TableQueryFilter.AllRows 、行を「すべての行」と言う。
.RowsMatching("<filter><condition attribute='ppp_owner' operator='eq' value='{currentUserId}' /></filter>")
.RowsMatching(async (context, services) =>
{
var territories = services.GetRequiredService<ITerritoryService>();
var filter = context.CreateFilter();
filter.AddCondition("ppp_territory", ConditionOperator.In, await territories.ForContactAsync(context.User.Id));
return filter;
})
ロールのアクセスの記述
内部 AddPortalRole 役割はオーディエンスであり、登録は他の名前を挙げません。役割は加算し、決して減らしません。ユーザーのアクセスはポータル全体の登録と、彼らが持つすべての役割を統合して含みます。ポータルの役割自体――それが何であるか、どのように割り当てられ、どのように構成されるか――は次のセクションです。
builder.Services.AddPortalRole("Case Manager", role => role
.WithTableAccess("incident", t => t
.RowsOfTheirAccount("customerid")
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Write)
.Reading("ticketnumber", "statuscode")
.Writing("title", "description")));
注記
プロバイダークラスは記述では表現できないものを表現する手段であり、構成は同一で、
AddTablePermissionProvider<T>とWithPermissions<T>は変更されません。生成されたプロジェクトはもはやプロバイダークラスを出荷しません。登録したすべてのテーブルが記述されており、contactやクレジットマネージャーの役割も含まれます。
ポータルの役割
ポータルの役割はポータルを保護するための推奨される方法です。権限提供者を管理者がDataverse内の連絡先に割り当てる名前に紐づけ、提供者はその役割を持つ人にのみ適用されます。そして、その権限を持つ人はコードではなくデータです。比較すると、Program.csに直接登録されたプロバイダーはすべてのユーザーに適用されます。
この分割は意図的なものです。 Dataverseは名前と割り当てを所有しており、管理者がモデル駆動型アプリで管理します。あなたのコードが各名前 の意味を所有します。誰かのアクセスを付与または取り消すことはデータ変更にあたります。再構築も再デプロイもなしです。
1. 役割を作成し、割り当てる
ポータル ロール レコードを作成し、それを持つべき連絡先に割り当てます。ロールレコードには名前と説明しか含まれず、Dataverseには権限データが存在しません。
2. 役割を提供者に縛り付ける
役割レコードの名前と一致し、何が付与されるかを記載してください。プロバイダーの種類はあなたのために登録されているので、自分で登録する必要はありません。
builder.Services.AddPortalRole("Dealer", role => role
.WithPermissions<DealerAccountPermissionProvider>()
.WithPermissions<DealerOpportunityPermissionProvider>());
最後の点が重要です:ロールの提供者もAddTablePermissionProvider<T>()に登録してはいけません。それはグローバルセットに含まれ、全員に付与され、役割を静かに無効にします。すべてのユーザーに本当に適用されるルールには、AddTablePermissionProvider<T>()を取っておきましょう。
役割の組み合わせ
ユーザーの権限は、グローバルに登録されたプロバイダーと彼らが持つ各役割から得られ、これらのグループはこのようにまとめられます:
- ロール間での加算です。 どのロールでも操作を付与すれば、そのユーザーはその操作を持っています。ロールを複数保持すればアクセス数も増えるため、役割が何かを奪うことはできません。ロール同士を絞り込みたい場合は
SecurityOptions.Roles.CompositionBehaviorをRestrictiveに設定してください。 - 一つの役割内で、変更はありません。 同じ役割の複数の提供者は、グローバルに登録されたプロバイダーが常にそうしてきたように、
SecurityOptions.MultiplePermissionHandlersBehaviorのもとで解散します。 - カラムも積み重なります。 カラムアクセスはオプトイン制なので、カラムを一度も言及しない役割は他のロールが公開した内容を広げることはできませんが、カラム 名を付け る役割は意図的な行為であるため、アクセス権を付与します。
RoleCompositionBehaviorをRestrictiveに切り替えると、操作とカラムの両方が交差します。
ヒント
加法的合成では 何も否定されません — 何かはどのグループも開かない間だけ閉じたままです。グローバルに登録されたプロバイダーは、すべてのユーザーに適用されるルールの正しい居場所です。なぜなら、常にフォールドに含まれているからです。しかし、それは拒否権ではありません。操作を付与するか、カラムに名前を付ける役割が再開します。ユーザーが持つ役割を閉じたままにするには、全く役割に付与しないか、すべてのグループが合意しなければならない
SecurityOptions.Roles.CompositionBehavior「Restrictive」に設定するのが正しいです。
ページ上の役割とナビゲーション
ロール名は ASP.NET 標準的なロールクレームとしてブラウザに届くため、ページ認証や条件付きUIは追加処理なしで機能します。
@attribute [Authorize(Roles = "Dealer")]
<AuthorizeView Roles="Dealer">
<p>Dealer-only content</p>
</AuthorizeView>
Reactスタックは同じ名前を auth.user.rolesから読み込み、ゲート <AuthorizedView roles="Dealer"> コンテンツも同じ方法で処理します。内部(システムユーザー)サインインはここでDataverseのセキュリティロール名を持ち、両方のユーザーを一つのチェックでカバーできます。
適切な役割の承認
ポータルロールとDataverseセキュリティロールは名前を共有し、 [Authorize(Roles = "…")] 区別できません。これは両方を含む型なしロールの主張に一致します。2つの属性がより狭い問いを投げかけます。
@attribute [AuthorizePortalRole("Dealer")]
// どちらの役割でも入ります。
[AuthorizePortalRole("Dealer", "Distributor")]
public class PricingController : Controller { }
// 「システム管理者」というポータルロールはこれを満たしません。
[AuthorizeSecurityRole("System Administrator")]
public class ImportController : Controller { }
ロール名は逐語的に使えます。コンマ、コロン、引用符はすべて問題ありません。なぜなら名前はリストから解析されないためです。2つの属性をスタックするには、スタックされた[Authorize]属性が常にそうであるように両方が必要です。1つの属性に2つの名前をリストアップするには、どちらかが必要です。同じチェックはUser.IsInPortalRole("Dealer")やUser.IsInSecurityRole("Sales Manager")と同様にコード内で利用可能です。
Blazorマークアップでも同じ区別があります。 <AuthorizeView Roles="…"> は型なしの役割主張を読み取るため、plain属性と同じ曖昧さを持ちます; <PortalRoleView> 一種類を求めます。単一の名前に対して PortalRole / SecurityRole 、または複数に対して PortalRoles / SecurityRoles を与えてください。これはカンマで区切られた文字列ではなくコレクションです。なぜならDataverseの名前は通常コンマを含むからです:
<PortalRoleView PortalRole="Dealer">
<a href="/pricing">Dealer pricing</a>
</PortalRoleView>
@* 「システム管理者」という名前のポータルロールでは、この情報は開きません。 *@
<PortalRoleView SecurityRole="System Administrator">
<BulkImport />
</PortalRoleView>
Reactの <AuthorizedView> は、既存の roles ゲートと並んで同じ2つのゲートを受け入れます。ゲートに複数の名前を与えることは、いずれの名前でも意味します。2つのゲートを指定する場合は、両方を意味します。それらの背後にある述語 — hasPortalRole、 hasSecurityRole、 hasRole — は命令チェックのためにエクスポートされます:
<AuthorizedView portalRoles="Dealer">
<Link to="/pricing">Dealer pricing</Link>
</AuthorizedView>
<AuthorizedView securityRoles="System Administrator">
<BulkImport />
</AuthorizedView>
ベストプラクティスは、ソースからフィルタリングすること
保護対象が本当に管理的なものであれば、これらの権限を活用しましょう。ポータルロールの作成は、Dataverseのセキュリティロールを付与されるよりも意図的に低いハードルなので、どちらかを承認するチェックは、管理者がDataverseの役割を守っていたアクセス権をポータルロールに名前付けすることで与えられるようにします。
注記
ロールは無料ではありません。ユーザーが保持する各ロールは認証クッキーに2回書き込みされます。1回は標準的なロールクレームとして使われ
[Authorize(Roles)]、もう1回は上記のチェックを可能にする種類ごとのクレームとして、1ロールあたり約160バイトです。ユーザーあたり約15の役割は1つのクッキーに収まります。それ以降はクッキーがチャンクに分割され、最初に動くのは通常リバースプロキシのヘッダー制限で、役割に関する記述ではなく不透明な400の表示が出ます。狭い役割よりもいくつかの広範な役割を好む。
新規ユーザーにスタートロールを与える
新しいアカウントが最初に持つべき役割と、サインアウトした訪問者に適用される役割を挙げてください:
builder.Services.Configure<SecurityOptions>(options =>
{
options.Roles.DefaultRoleNames = ["Customer"];
options.Roles.AnonymousRoleName = "Anonymous";
});
またはリリースなしでDataverse上で決定することもできます。 Portal Role Configuration tableは役割と割り当てモードをペアリングし、管理者はモデル駆動型アプリから誰が何を受け取るかを変更できます:
- なし — 設定されているが休眠状態。行はモードを変えるまで何も得られない。
- 新規ユーザーに自動割り当て — 設定行と名前のあるロールの両方がアクティブであれば、すべてのアカウントは作成時にこの役割を受け取ります。どちらか一方を非アクティブにすると停止します。設定行は役割を割り当てる定時命令であり、非アクティブロールは何も付与しません。したがって、アクティブ行に非アクティブロールを名付けると何もない命令となります。再アクティベートすると再開されます。
- Assign To New Users From Criteria — 新規ユーザー自身の情報でマッチングするために予約されています。 基準はまだ評価されていないため、このモードの行は「全員」ではなく「Nobody」にロールを割り当て、ポータルは「誰にもマッチしない」と認識されないように警告を記録します。
役割がその人(メールドメイン、サインインしたプロバイダー、招待など)に依存する場合は、代わりに IPortalRoleAssignmentProvider を登録してください。登録済みのすべてのプロバイダーに相談されるため、あなたの役割は設定済みリストに加えられ、置き換えられません。いずれにせよ、割り当てはアカウント作成時に一度だけ実行されます。サインイン時に再適用されることはなく、管理者が削除した役割はそのまま削除されます。
注記
ユーザーは新たに割り当てられたロールをサインアウトして再ログインすることで取得します。サインインするとロールが再読みされます。ロール割り当てはユーザーごとに24時間キャッシュされるため、すでにサインインしている人に変更を適用するには、サイト管理エリアから Portal Roles キャッシュをクリアしてください。セッションが次に更新された際に10分以内にキャッシュされ、削除されたロールは機能しなくなります。ちょうど追加されたロールが始まると同時に。Portal Role Configuration テーブルは15分間別にキャッシュされるため、そこに追加された行は自動的に次の登録に到達します — 「PortalRoleConfiguration」キャッシュをクリアして即時登録にします。設定行を非アクティブにすると役割の割り当てが停止され、役割を無効化すると同様に取り消されます。
代わりにプロバイダーを書く
説明だけでは意味が伝わらないクラスに手を伸ばしましょう。例えば、コードだけが知っているルールや、レコードを操作しなければならないルールなどです。 ITablePermissionProvider 、どの登録も同じ結果になります。1つのプロバイダーが1つのテーブルを管理し、ユーザーがそのテーブルで何をできるか、どの列を読み書きできるか、そして下のクエリフィルターを通じてどの行が見えるかを把握します。説明された登録と書き込まれた登録は同じ構成なので、ポータルは両方を使い、一方でテーブルを付与し、もう一方で拡大できます。
インターフェースには1人の必須メンバーがいます。 GetPermissionsAsync はユーザーのID、ポータルロール、Dataverseセキュリティロールを含む UserInfo を受け取り、そのユーザーが受け取るすべての情報を説明した返 TablePermissions を返します。
public class AccountPermissionProvider : ITablePermissionProvider
{
public string Table => "account";
public Task<TablePermissions> GetPermissionsAsync(UserInfo user)
{
if (!user.IsAuthenticated)
return Task.FromResult(TablePermissions.None);
// 列はオプトイン制で、ここで挙げられたものだけが戻ってきます。
return Task.FromResult(TablePermissions
.Allowing(TableSecurityPermission.Read | TableSecurityPermission.Write)
.Reading("name", "telephone1", "websiteurl", "creditlimit")
.Writing("telephone1", "websiteurl"));
}
}
プロバイダーの登録
このように登録されたプロバイダーは、サインインしているかどうかに関わらずすべてのユーザーに適用されます:
builder.Services.AddTablePermissionProvider<AccountPermissionProvider>();
また、これは全員から何かを 奪 う唯一の方法でもあります。役割はデフォルトで加算的に合成されるため、役割はアクセスを追加しても削除はできません。ポータル全体の制限は、グローバルに登録されたプロバイダーに適用されるべきであり、それも常にその一部です。
運用
TablePermissions.Operations は TableSecurityPermission フラグの値 — Read、 Create、 Write、 Delete、 Append 、 AppendToです。知っておくべきことが2つあります。
- これはテーブルレベルのゲートであり、行のフィルターではありません。もしユーザーがテーブルのどの行も見られるなら、
GetQueryFilterAsyncの行を絞り込むようにRead。 - プロバイダーのないテーブルは到達できません。これがフェイルクローズドのデフォルトであり、カラムにも当てはまります。
Readは付与し、列名を付けないプロバイダーは、IDのみを含むレコードを返します。
注記
「読み込み後に各行を検査する」というフックは意図的にありません。その方法でフィルタリングするとページングが壊れます。50ページ分のページはより少なくなります。合計カウントが誤りになり、Dataverseに行を回収して捨てる行を請求します。代わりにクエリをスコープ化することでページングが続き、計算自体が正しくなります。
ベストプラクティスは、ソースからフィルタリングすること
クエリ内のスコープデータであって、クエリの後ではなく。フレームワークは、フェッチされた各行を検査するフックを意図的に提供していません。この方法でフィルタリングするとページングが壊れ(50ページが短くなり)、合計カウントが誤り、Dataverseに行の取得を依頼して破棄します。
GetQueryFilterAsyncクエリ実行前に条件を追加するため、ページングやカウントは構成上正しく保たれ、ユーザーが見られない行は一度も取得されません。
行レベルのセキュリティ
GetQueryFilterAsync フレームワークがDataverseを実行する 前に 質問に追加するフィルターを返すため、ユーザーが見られない行は決して取得されません。このクエリは、ポータルがテーブルに対して発行するすべてのクエリ(グリッド、ルックアップ、サブグリッド、チャート、シングルレコードロード)に適用され、両方のセキュリティモデルで動作するため、Dataverseネイティブのセキュリティがオペレーションゲートを回避しても有効に機能します。
context.CreateFilter()でフィルターを作成し返します。nullを返すということは、行の制限がありません。コンテキストには現在のユーザーEntityReference、彼らが持つ役割、そして行が使われる予定の内容が含まれます。条件は、他の場所で使われているのと同じFetchXMLBuilderAPIを通じて追加されます:
FetchXMLBuilder
このプロバイダーは3つの列 contact を付与し、すべての連絡先クエリをサインインしたユーザー自身のレコードにスコープします。
public class ContactPermissionProvider : ITablePermissionProvider
{
public string Table => "contact";
public Task<TablePermissions> GetPermissionsAsync(UserInfo user)
=> Task.FromResult(TablePermissions
.Allowing(TableSecurityPermission.Read | TableSecurityPermission.Write)
.ReadingAndWriting("firstname", "lastname", "telephone1"));
public Task<TableQueryFilter?> GetQueryFilterAsync(TableQueryContext context)
{
var filter = context.CreateFilter();
filter.AddCondition("contactid", ConditionOperator.Equal, context.CurrentUser.Id);
return Task.FromResult<TableQueryFilter?>(filter);
}
}
読み込み、更新、削除
TableQueryContext.Operation なぜ行が取られるのかが説明されているので、プロバイダーごとに異なるスコープを割り当てることができます:
Read— グリッド、ルックアップ、サブグリッド、チャートまたはレコードロードです。残りの行はユーザーが見ることができる行です。Update— セーブ前のプリフライト取得です。残りの行はユーザーが変更できる行であり、通常はより狭いセットです。「すべてのアカウントを閲覧し、私だけを編集」はこの区別であり、それ以外はありません。Delete— 削除前の事前フェッチです。ほとんどのポータルではさらに狭いです:レコードを訂正することを許されることと、それを破棄することを許されることとは異なります。
public Task<TableQueryFilter?> GetQueryFilterAsync(TableQueryContext context)
{
// 全員がすべて読みます。書き込みのみが所有者にスコープされます。
if (context.Operation == TableQueryOperation.Read)
return Task.FromResult<TableQueryFilter?>(TableQueryFilter.AllRows);
var filter = context.CreateFilter();
filter.AddCondition("ownerid", ConditionOperator.Equal, context.CurrentUser.Id);
return Task.FromResult<TableQueryFilter?>(filter);
}
複数のフィルターがどのように組み合わさるか
テーブルは複数のプロバイダーを含みます — グローバルに登録されたプロバイダーと、ユーザーが持つ各役割から1つ。フレームワークはフィルターの組み方を決定するため、プロバイダーは他のフィルターが何をしたかを知る必要はありません。
- 一つのグループ内では交差します。 グローバルに登録された2つのプロバイダー、あるいは同じ役割に縛られた2つのプロバイダーは一致するはずなので、議論は両方を満たさなければなりません。
- 役割間で統合します。別の役割を持つと、表示される行が広がり、操作の構成と一致します。
RoleCompositionBehaviorRestrictiveに切り替えると交差します。 - ジョインは管理されます。 クエリで既に使われているエイリアスと衝突したエイリアスは名前が変更され、フィルターの参照がそれに従います。フィルタのユニオンを行う際は、内側のジョインが外側に緩和され、一方の役割のジョインが他の役割で許可される行を除外できないようにします。同じレコードを複数回返せるジョインは、その重複が崩壊します。
注記
行は閉じて失敗します。列のように、
nullを返すプロバイダーや条件のないフィルターは行を 割り当てず、 グループ内ではグループを空にします。すべての行はTableQueryFilter.AllRowsで求められます。そこに非対称性があります。すべての行の和集合はすべての行であり、テーブルに答えるグループがAllRowsすると、他のロールがスコープを慎重に設定しても、テーブルはフィルターなしで戻ってきます。これが加法合成の意味ですが、役割のフィルターが無視されているように見える唯一の方法です。
関連テーブルを通じたフィルタリング
AddLink テーブルを結合し、フィルターがカラムをテストできるようにします。テストをリンクではなくフィルター内に配置してください。フレームワークが外側への結合を緩めてもフィルター内の条件は動作し続け、リンク上の条件は以下の通りに機能しません。
public Task<TableQueryFilter?> GetQueryFilterAsync(TableQueryContext context)
{
var filter = context.CreateFilter(LogicalOperator.Or);
// クエリがすでにエイリアスを使用している場合、エイリアスは一意にします。
filter.AddLink("ppp_membership", from: "ppp_accountid", to: "accountid", alias: "m");
filter.AddCondition("primarycontactid", ConditionOperator.Equal, context.CurrentUser.Id);
filter.AddCondition("m", "ppp_contactid", ConditionOperator.Equal, context.CurrentUser.Id);
return Task.FromResult<TableQueryFilter?>(filter);
}
注記
上記の結合は多くの行に 及 ぶため、Dataverseはメンバーシップごとに1回だけアカウントを返します。行を繰り返すグリッドと合計でカウントします。フレームワークはその形状を認識し、クエリ
distinctにマークし、テーブルのプライマリキーを投影して、重複解除で結合が追加された行だけを削除できます。代わりに 上 に伸びて関連するテーブルのプライマリキーに結合するジョインは、最大でも1行だけ一致し、そのままそのままになります。いずれにせよ、フィルターだけを書き、それ以外は書かず、ジョインは無料ではなく、テーブル自身の列にフラットな条件として表現できるルールの方が安価な形状であることを知っておく価値があります。
ヒント
テーブルごとに異なるスコープが必要な場合でも、 1つの プロバイダーを書いて
context.User.PortalRolesにブランチをすることはできますが、もはやその必要はありません。2つのロールはそれぞれ独自のプロバイダーを持ち、フレームワークのユニオンがフィルターを割り当ててくれます。
新記録の所有者は誰か
行フィルターはスコープ、読み取り、更新、削除を行いますが、作成はフィルターすべき行を持ちません。したがって、「私が所有する行」にスコープが設定されたテーブルでも、ユーザーは他の誰かの所有する行や、誰も所有していない行を作成でき、作成者はそれを見ることができません。レコードの所有者を示す列を宣言すると、フレームワークがそれを設定します:
// アカウントは作成した連絡先のものです。
builder.Services.AddContactOwnership("account", "primarycontactid");
- 作成時には、権限チェックの後にカラムがサインイン中の連絡先に設定され、リクエストが送った値は破棄されます。ユーザーはカラムへの書き込み権限や
contactのAppendToを必要としません。レコードを自分にリンクすることは信頼されていません。 - 更新時に 所有者を変更するリクエストは拒否され、保存された所有者を繰り返し表示するリクエストは無視されます。
- 内部ユーザー (systemuserサインイン)は免除されます。Dataverseはセキュリティを厳格に管理しており、レコードの再割り当ては管理の一部です。
同じ列に行フィルターを組み合わせて、ユーザーが作成するレコードが見えるレコードになります。単なる作成連絡先(例えばクリエイターのアカウント)でない所有者権については、 ITableRecordOwnershipProvider を実装して AddTableRecordOwnershipProvider<T>()に登録してください。代わりに ITableRecordInterceptor で所有者を設定しないでください。インターセプターは許可チェックの前に動作するため、設定したものはユーザー自身の書き込みと判断されます。
public sealed class CaseOwnershipProvider(IOrganizationServiceProvider organizationServiceProvider)
: ITableRecordOwnershipProvider
{
public string Table => "incident";
public IReadOnlyList<string> Columns { get; } = ["customerid"];
public async Task<IReadOnlyDictionary<string, LookupValue>> GetOwnersAsync(TableRecordOwnershipContext context)
{
// アプリケーションユーザーとして読むと、誰がどの会社に属しているかはリクエストの対象ではありません。
var contact = await organizationServiceProvider.GetOrganizationService()
.RetrieveAsync("contact", context.CurrentUser.Id, new ColumnSet("parentcustomerid"), context.CancellationToken);
var company = contact.GetAttributeValue<EntityReference>("parentcustomerid");
// 訴訟は彼らの会社名義で、または会社名義で起訴してください。
var owner = company?.LogicalName == "account"
? new LookupValue { TableName = "account", Value = company.Id }
: new LookupValue { TableName = "contact", Value = context.CurrentUser.Id };
return new Dictionary<string, LookupValue> { ["customerid"] = owner };
}
}
// Program.cs
builder.Services.AddTableRecordOwnershipProvider<CaseOwnershipProvider>();
フレームワークはそのテーブルの作成ごとに権限チェックの後に呼び出し、 Columns 内の各列を返すものに設定します。リクエストが送ったものはすべて破棄し、その後の更新で変更を拒否します。これは OwnedByと同じです。設定内容に一致する行フィルターを組み合わせ、記録をファイルできる相手に設定します。ここでは RowsMatchingAny(r => r.RowsOfTheirAccount("customerid"), r => r.RowsOwnedByContact("customerid"))、 WithoutStamping() で登録がこの提供者に列を残します。1つの列を異なる設定で設定した2人はセーブに失敗します。
カラムレベルのセキュリティ
カラムのセキュリティは オプトイン制です。プロバイダーは公開したいカラムを付与し、それ以外(来年誰かがテーブルに追加するカラムも含めて)は名前が指定されるまで保留されます。レコードのプライマリIDは常に読み取れます。フレームワークが特定できないレコードは誰にとっても役に立たないからです。
読み取りと書き込みは独立した集合です。列は読み取れるが書き込み不可(「クレジット限度額を表示し、変更させない」ケース)または書き込み可能だが読み取れない場合があります。 Reading、 Writing 、 ReadingAndWritingで名前を付けるか、 ReadingAllColumns / WritingAllColumnsで次元を完全に除外することもできます。
public Task<TablePermissions> GetPermissionsAsync(UserInfo user)
{
// すべての列が読める;そのうち二つは書き写せない。
return Task.FromResult(TablePermissions
.Allowing(TableSecurityPermission.Read | TableSecurityPermission.Write)
.ReadingAllColumns()
.WritingAllColumnsExcept("creditlimit", "creditonhold"));
}
注記
ReadingAllColumns()意図的にタイプしなければならないものです。後からテーブルに追加されるものに対しては無効になりますが、これはテーブルが本当に公開されている場合に限り正しい取引です。したがって、フレームワークはあなたの代わりにそれを仮定しません。
書き込みはレコード の作成 と 変更 の際にも異なる場合があります。 Writing は両方をカバーしています。 WritingOnCreate ユーザーがレコードを作成する際に列を設定し、それ以降は読み取り専用にできます。これはケースの優先度であり、ログ時に選択されます。 WritingOnUpdate は逆で、既存のレコードでは変更可能で、開始時に設定可能ではありません。どちらもカラムを読みやすくし、 Writingと同様に、自分のプロバイダーに対して TablePermissions に存在します。Dataverse自身のフィールドセキュリティが付与する別個の権限の作成と更新権限をミラーリングしています:
builder.Services.AddTableAccess("incident", t => t
.ForSignedInUsers()
.RowsOwnedByContact("customerid")
.Allow(TableSecurityPermission.Read | TableSecurityPermission.Create | TableSecurityPermission.Write)
.Reading("ticketnumber", "statuscode")
.Writing("title", "description") // 作成時に設定、後に変更可能
.WritingOnCreate("prioritycode") // 作成時に設定、その後は読み取り専用
.WritingOnUpdate("resolution")); // 変更可能で、最初から設定しない
フレームワークは、データがサーバー境界を通過するすべての経路で結果を強制します:
- 読み取り — 保留された列はレコードから完全に省略されるか、値がマスクされたまま戻され、テーブルのメタデータやグリッドカラムから隠されるため、フォームはエディタを決して届かない値に縛り付けることはありません。
- クエリ — カラム権限を理由に読み取りが拒否されることはありません。ビューはオーディエンス間で定期的に共有され、あるユーザーが別のユーザーで見られないカラムが、オーディエンスごとに別々のビューを維持するエラーになってはいけません。保留されたカラムへのすべての参照は代わりに削除されます:投影、ソート、フィルター条件(空になった場合はフィルターも含む)、集計やグループバイ。条件をドロップすると結果セットが広がりますが、これはユーザーがすでに読んでいる可能性のある行間のみです。行の可視性はプロバイダーのクエリフィルターから得られ、このフィルターはこのパス後に追加され、決して操作されません。
- 書き込み — 読み取りとは異なり、ユーザーが書き込めない列を変更する作成や更新は、静かに切り取られるのではなく拒否されます。拒否された保存はユーザーに編集が当たっていないことを知らせる唯一の方法であり、狭められた読み取りは見えず無害です。作成は作成時に設定される内容と更新が単一セーブまたはバッチで変更される可能性に基づいて判断されます。
注記
テーブルのプライマリID列は、読み取りセットが何と書かれていても決して保留されません。idはデータではなくアイデンティティであり、呼び出し者がレコードにアクセスするために使ったURL内にあります。行キー、編集リンク、サブグリッド関係、削除はすべてIDなしでは解体されます。ページに必要な列の名前を付けてIDを忘れる許可リストは、ポータルを壊し、何も保護しないでしょう。
コンポーネントの挙動
フレームワークのコンポーネントは単独で列権限を尊重しており、何かを配線する必要はありません。ユーザーが 書 いてはいけない列は表示専用で表示され、表示される場所は無効化されます。すべてのエディタは共有の読み取り専用解像度を通じてそれを拾い、グリッドはセルにインラインエディタを割り当てないため、プレーン表示テキストに戻されます。消費者提供の readOnly="false" はそれを上書きしません — どちらにせよサーバーはセーブを拒否します。
ユーザーが 読 んではいけないマスキングされた列は、両方のスタックでエディタではなく、 <ColumnDisplayValue> を通じてレンダリングされます。これは単なる細かい点ではありません。マスクされたカラムは何の値もなく、マスクだけが フォーマットされた 値なので、それに割り当てられたエディタは空欄に表示されます。 ColumnDisplayValue はフォーマット済みの値を読み取り、カラムの型に関係なくプレースホルダーを表示します。省略によって保留されたカラムは、レコードに表示されず、テーブルのメタデータにも記載されません。
クライアントが受け取るもの
カラム権限はColumnPermissionsマップとして提供されます。これはアクセスが狭まるカラムに対してColumnSecurityPermissionフラグペアへのカラム名であり、Writeは既存のレコード上でカラムを変更することを意味します。欠如したものは完全なアクセス権を持つため、制限のないテーブルは何も含まれません。単独で取得したレコードはマップを保持します。同時に取得したレコードは1つのマップを共有し、レスポンス時に一度送信されます。権限は行ではなくユーザーとテーブルに属するため、両方のスタックのクライアントが各レコードに戻すため、記録の権限の読み取りはどちらの場合も同じです。グリッド応答もまた、列ごとに報告ResolvedColumn.Permissions。作成中のレコードにはまだ独自のマップがないため、テーブル権限エンドポイントもNewRecordColumnPermissionsを返します。これは作成時に設定されるものによって判断される同じ形状で、両方のスタックのRecordContextが新しいレコードに割り当てます。省略された列は意図的にすべてのマップに欠けています。省略は存在しないため、回答に列名が現れず、名前を付けるとその意味が元に戻ってしまいます。Blazorのrecord.CanReadColumn(column) / record.CanWriteColumn(column)、またはReactの@powerportalspro/coreの対応するcanReadColumn / canWriteColumn helperで自分で読んでみてください。
省略またはマスキング
デフォルトでは、保留された列は マスクされます。レコードはその値を持ち、 SecurityOptions.WithheldColumnMask (デフォルトで***** )がフォーマット値なので、フォームやグリッドでもフィールドを表示し、値が空白ではなく保留されていることを明確に示します。マスキングは値があるかどうかに関わらず同じなので、それも隠します。 SecurityOptions.WithheldColumnBehavior を Omit に設定すると、クエリがその列を要求したことがなかったかのようにレコードからその列を完全に削除し、応答からその列の存在を示さないようにします。これは、列の存在自体が機密の場合に正しい選択です。列を保留すると、再公開する列も差し止められます。例えば、検索の名称の伴いやマネー列の基準通貨の双子などです。つまり、ある列の名前を付けた拒否リストが別の列を回避できないようにします。単一のテーブルでサイト全体の選択を上書きできます TableColumnPermissions.WithheldColumnBehavior。
builder.Services.Configure<SecurityOptions>(options =>
{
options.WithheldColumnBehavior = WithheldColumnBehavior.Mask;
options.WithheldColumnMask = "*****";
});
注記
マスクされたカラムと書き込み拒否を組み合わせます。マスクされたカラムはレコードに値がないため、ロードされたレコード全体を投稿した呼び出し者は何も送りません。しかしフレームワークはセイブペイロードをフェイスバリューで受け取るため、マスクを含むカラムを設定するリクエストは、そのカラムが書き換えられる場合に保存された値を上書きします。書き込み拒否がそれを拒否します。
別のコラムを再掲載するコラム
列を保留すると、同じ値を持つ列も2つ目の名前で差し止められます。Dataverseはこれらを作成します:ルックアップの …name と …yominame パートナー、選択肢のラベル、そして各通貨列ごとにベース通貨の双子( creditlimit_base creditlimitと並んで)。これらは通常の列なので、拒否リストの命名 creditlimit は creditlimit_base何も言わず、これがなければマスクされた金額はその双子を通って直接戻ってきます。リンクは命名ルールではなくプラットフォーム自身のメタデータから来るため、自分のカラムにも当てはまります。読み取り時、コンパニオンを明示的に名付けるクエリ、テーブルメタデータ、グリッドカラム、セーブデータにも適用されます。
ベストプラクティスは、ソースからフィルタリングすること
フレームワークがあなたに表示できないケースが一つあります。計算された列やロールアップの列、そしてプラットフォームの複合列(
fullname、yomifullname、address1_compositeとその兄弟列)です。Dataverseはどの列がそれらを供給しているかを報告しないので、差し止めてもfullnamelastnameを差し止めることはなく、拒否した列の上に計算された列を割り当てても結果は表示されます。入力が敏感な場合は、自分で読み取りセットに名前を付けてください。許可リスト(ColumnPermissions.Allow(…))はこの問題を完全に回避します。なぜなら、名前を付けるまで何も読み取れないため、差し止めに値するものを保持するテーブルの方が良いデフォルトだからです。
注記
複数の提供者が1つのテーブルをカバーする場合、その列セットは操作と同じように構成されます。すなわち、役割をまたぐ加算的で、1つのグループ内で
MultiplePermissionHandlersBehaviorに分類されます。行のスコープのみを目的とし、列について何も言わない提供者は列折り目から完全に除外されるため、他の提供者が与えた内容を広げたり狭めたりしません。
Dataverse-Native Security (プレビュー)
今後推奨されるセキュリティモデルでは、 Dataverse自身 がPower Pagesの 統一されたDataverseセキュリティ 機能を基盤に、ポータルユーザーのためにセキュリティを強制できるようになります。権限ハンドラーをコードで書く代わりに、Power Pagesの ウェブロール や テーブル権限でアクセスを管理し、プラットフォームはそれらを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と同じように、 Webロール や テーブル権限 を設定してください。Power Pages管理アプリや、Power Portals Pro Enhanced Authorizationソリューションの Webロール ページで、連絡先にウェブロールを割り当てます(管理アプリを通じて、プログラム的に、または同じページにルールを持つ新規ユーザーに自動で)。プラットフォームはそれらを自動生成されるセキュリティロール( PowerPages_*と名付けられた)に同期し、ウェブロールのメンバーシップ変更として自動的に割り当てます。設定は完全にPower Pages上で行われているため、ポータルは実際のPower Pagesサイトとスムーズに移動できます。役割や権限の変更は、Dataverseのキャッシュ内で伝播するまでに数秒から数分かかることがあります。
外部サインインを連絡先に照合する
Microsoft、Google、またはその他の外部プロバイダーにサインインすると、ポータルはそのサインインがどの既存の連絡先(もしあれば)に属しているかを特定しなければなりません。この問題は2つのステップで解決されます。
- 既存のリンクです。 もしそのアカウントが以前にサインインしているなら、
adx_externalidentity行ですでに連絡先に紐づけられます。これは明示的に保存された「このアカウントはこの人物です」という文で、常に勝利し、下記の設定に影響されません。 - メールのフォールバック。 まだリンクがありませんが、ポータルはプロバイダーが提供したメールアドレスと連絡先の列を照合します。このステップで設定できるものであり、リピーターが既存の連絡先に加入するか重複するかを決めます。
デフォルトでは2つの列を検索し、まずポータルのユーザー名を先に検索します。ポータルで作成された連絡先は adx_identity_username を持ちますが、スタッフがインポートまたは入力した連絡先は通常 emailaddress1 しか持たないことが多いです。両方を検索することで、既存の顧客がサインインして新しい記録ではなく自分の記録に表示できるようになります:
builder.Services.Configure<SecurityOptions>(options =>
{
// The default: portal username first, then the primary email column.
options.ContactMatching.ExternalLoginEmailColumns =
[
ContactEmailColumn.IdentityUsername,
ContactEmailColumn.EmailAddress1,
];
});
順序は重要です。順位は単なるリストではなく優先順位です。列は単一のクエリで一緒に検索され、リスト内で最初に一致した列が勝者を決定します。つまり、ある連絡先がそのアドレスをポータルのユーザー名として保持し、もう一方がemailaddress1に保持した場合、デフォルトの順序ではユーザー名の一致が勝ちます。
マッチングを絞るために列数を減らす: [ContactEmailColumn.IdentityUsername] はポータルで作成されたアカウントに限定し、空のリストはメールのフォールバックを完全にオフにし、既存の外部識別リンクだけがアカウントが連絡先に繋がる唯一の手段となります。 ContactEmailColumn.EmailAddress2 と ContactEmailColumn.EmailAddress3 を追加して範囲を広げましょう。これらは下記の理由から意図的にデフォルトから除外されています。
ベストプラクティスは、ソースからフィルタリングすること
あなたがリストするすべての列は、そのメールアドレスを管理する人がその連絡先として認識されるためのものです。リストリストは、データ入力プロセスが連絡先本人のものとみなす列のみを挙げます。もしチームがアシスタントの住所や共有会社の受信箱を登録している場合
emailaddress2、その受信トレイを読んだ人がこの連絡先としてサインインできるようにします。
ベストプラクティスは、ソースからフィルタリングすること
マッチングはメールアドレスを単なるラベルではなく識別子にし、サインインがどの既存の連絡先になるかを決定します。つまり、ポータルは確認メールをスキップします。IDプロバイダーがその正確なアドレスを確認した場合だけです。もしアドレスがプロバイダーが提供したアドレスの上に入力されていたり、プロバイダーが確認しなかった場合、確認リンクが送信され、そのリンクをたどるまで外部ログインは連絡先に関連付けられません。
どのサインインが認証済みとみなされるかはプロバイダーの特性ではありません。Googleは主張するすべてのアドレスを検証します。 Microsoftは確認しません。 個人のMicrosoftアカウントがアドレスを認証しましたが、Entraの職場や学校アカウントはディレクトリの
verified_primary_emailやverified_secondary_emailの請求がそのアドレスを記載しているか、xms_edovが自分のドメインがユーザー自身のテナントに属していると言っている場合だけです。Entraはアプリ登録時にのみこれらの請求を送信します( Entra IDサインイン設定参照)。これらがなければ、職場や学校のサインインは初回メールで確認されます。
注記
これらの設定は外部サインインにのみ適用されます。ローカルのユーザー名/パスワードサインイン、登録、パスワードリセットは
adx_identity_usernameだけで意図的に一致し続けます。パスワードリセットフローは入力したアドレスへのリンクをメール送信するため、二次的なメール欄を照合することで誰かが他人のパスワードをリセットできるようになります。
既存の連絡先のみサインインを許可する
デフォルトでは誰でもポータルユーザーになれます。登録または外部プロバイダーで「No Contact」のアドレスでサインインすると、連絡先が作成されます。ポータルがDataverseで既に保持している人(顧客、メンバー、パートナー)向けの場合、サインアップをオフにし、その人だけがサインインできます:
builder.Services.Configure<AuthClientOptions>(options => options.SignUpEnabled = false);
レジスタページとそのリンクは消え、サーバーはどんな方法でもユーザーを作成しません。外部サインは上記のように連絡先を見つけてリンクします。誰も一致しないアカウントは、連絡先を作成する代わりに拒否されます。すでにローカルアカウントを持っている人には影響を受けません。
注記
サインイン拒否は、プロバイダーがその正確な住所を確認した場合にのみ画面上で説明されます。なぜなら、サインインした人はすでにその住所の所有者であることを証明しているからです。その他の住所については、ポータルは連絡先のある住所と同じように「メールを確認してください」と答え、登録されていないと短期間で通知します。訪問者に直接伝えれば、誰でもフォームに住所を入力して誰がアカウントを持っているか確認できます。
通知はポータル自身のメール送信者を通じて送信されます。それを自社のメールスタックに置き換えるホストも、
ISignUpRefusalEmailSenderを登録すべきです。がなければ拒否は記録され、通知は送信されません。
組み込みの基本クラス
このフレームワークは、一般的なセキュリティパターンを簡素化するための基本クラスを提供します:
AnonymousPermissionHandler— は、サインインしているかどうかに関わらず、すべての列を含めて全員にテーブルを付与します。ここでオプトインのデフォルトが免除される唯一の場所であり、そこから得られるのはテーブルの公開を決定するからです。オーバーライドGetPermissionsAsync公開を減らすことができます。
API参照
AuthorizePortalRoleAttribute クラス
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
AuthenticationSchemes | string? | ||
Policy | string? | ||
RoleNames | IReadOnlyList<string> | この属性に必要な役割名、いずれかがそれを満たす。 | |
Roles | string? | ||
TypeId | Object |
AuthenticationSchemesPolicyRoleNamesRolesTypeId方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
IsDefaultAttribute | bool | ||
Match | Object obj | bool |
IsDefaultAttributeMatchITablePermissionProvider Interface
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
Table | string | このプロバイダーが適用されるテーブルの論理名。マッチングは正確に一致しているので、 Dataverseの論理名は小文字で表記します。 |
Table方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
GetPermissionsAsync | UserInfo user | Task<TablePermissions> | この表 user どうすればいいのか、どの列を読むのか そして書きます。アクセスできないはずのユーザーに TablePermissions.None を返します。 TableSecurityPermission.Read ユーザーがテーブルの どれか を見ることができるかどうか 行と Services.TableQueryContext)のどの項目をスコープするか。 TableSecurityPermission.Read だけではレコードのIDのみが露出します。使用 Reading(System.String[]) および Writing(System.String[]) 名前をつけるか、あるいは このテーブルではカラムセキュリティをオプトアウトする TablePermissions.AllColumns 。 |
GetQueryFilterAsync | TableQueryContext context | Task<TableQueryFilter> | ユーザーがこのテーブルのどの行に対応できるかのスコープ:以下で構築されたフィルター Query.LogicalOperator)、TableQueryFilter.AllRows 操作が許す行を、行がない場合はnull。 閉じられた行はnull — または条件のないフィルター — は条件を与えないので テーブルのスコープを忘れることは、すべての行を公開する代わりにそれを否定します。例えば TableQueryFilter.AllRows、あなたが言いたいのは「すべての列」のことです。 TableQueryContext.Operationが区別する点です。また、実行も行います 両方のセキュリティモデルの下で、Dataverseネイティブのセキュリティでも有効に保たれます Services.UserInfo)をバイパスします。 PortalRoleOptions.CompositionBehavior を に設定します。 また、役割をまたいで交差するのも良いRoleCompositionBehavior.Restrictiveです。 null応答すると、グループに行が残りません。 TableQueryFilter.AllRows他のプロバイダーのフィルターに任せます。横断 グループは、TableQueryFilter.AllRowsを付与するグループでデフォルトの加算を広げます すべての行に統合 — つまり、ロールのフィルターは他のグループが テーブルのフィルターも同様です。 |
GetPermissionsAsyncuser どうすればいいのか、どの列を読むのか そして書きます。アクセスできないはずのユーザーに TablePermissions.None を返します。 TableSecurityPermission.Read ユーザーがテーブルの どれか を見ることができるかどうか 行と Services.TableQueryContext)のどの項目をスコープするか。 TableSecurityPermission.Read だけではレコードのIDのみが露出します。使用 Reading(System.String[]) および Writing(System.String[]) 名前をつけるか、あるいは このテーブルではカラムセキュリティをオプトアウトする TablePermissions.AllColumns 。 GetQueryFilterAsyncQuery.LogicalOperator)、TableQueryFilter.AllRows 操作が許す行を、行がない場合はnull。 閉じられた行はnull — または条件のないフィルター — は条件を与えないので テーブルのスコープを忘れることは、すべての行を公開する代わりにそれを否定します。例えば TableQueryFilter.AllRows、あなたが言いたいのは「すべての列」のことです。 TableQueryContext.Operationが区別する点です。また、実行も行います 両方のセキュリティモデルの下で、Dataverseネイティブのセキュリティでも有効に保たれます Services.UserInfo)をバイパスします。 PortalRoleOptions.CompositionBehavior を に設定します。 また、役割をまたいで交差するのも良いRoleCompositionBehavior.Restrictiveです。 null応答すると、グループに行が残りません。 TableQueryFilter.AllRows他のプロバイダーのフィルターに任せます。横断 グループは、TableQueryFilter.AllRowsを付与するグループでデフォルトの加算を広げます すべての行に統合 — つまり、ロールのフィルターは他のグループが テーブルのフィルターも同様です。 TablePermissions クラス
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
Columns | TableColumnPermissions | ユーザーが読む・書くことができる列。 TableColumnPermissions.None 何かに名前が付けられています:列へのアクセスは許可されるもので、決して仮定されません。 | |
Operations | TableSecurityPermission | ユーザーがテーブル上で行う操作はフラグ値として行われます。これはテーブルレベルです 行フィルターではなくゲート — ユーザーが可能であれば含めて TableSecurityPermission.Read テーブルの どの行でも 確認し、どの行をスコープするか Services.TableQueryContext)。 |
ColumnsTableColumnPermissions.None 何かに名前が付けられています:列へのアクセスは許可されるもので、決して仮定されません。OperationsTableSecurityPermission.Read テーブルの どの行でも 確認し、どの行をスコープするか Services.TableQueryContext)。方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
AllColumns | TablePermissions | カラムレベルのセキュリティを完全に除外し、すべてのカラムが読み書き可能であることを選びます。その コラムセキュリティ以前の挙動で、継承されるのではなく明確に示されています。 | |
Allow | TableSecurityPermission operations | TablePermissions | この値がすでに提供しているものに operations を加えます。 |
Allowing | TableSecurityPermission operations | TablePermissions | operationsを付与し、柱はなし。鎖のReading(System.String[])、 Writing(System.String[])やTablePermissions.ReadingAllColumnsで列を開けることができます。 |
Reading | string[] columns | TablePermissions | 既に許可されている列に加えて、名前のある列の閲覧を許可します。大文字を区別しません。 つまり、メイカーポータルの綴りも論理的な名前も使えます。 |
ReadingAllColumns | TablePermissions | このテーブルのカラムレベルの読み取りセキュリティのオプトアウト:すべての列(追加された列も含む) 後でテーブルに。意図的に明示的に — フレームワークはそれを前提にしません。 | |
ReadingAllColumnsExcept | string[] columns | TablePermissions | 列レベルの読み取りセキュリティは、名前付き列を除くすべての列にオプトアウトします。 これらは後から追加されたものも含めて。新しいものは無効なので、 機密データが保管されているテーブルに、 Reading(System.String[]) 。 |
ReadingAndWriting | string[] columns | TablePermissions | 名前のある列の読み書きの両方が可能です。 |
WithColumns | TableColumnPermissions columns | TablePermissions | カラムルールを丸ごと置き換え、プロバイダーが Services.TableColumnPermissions 流暢な手法を連鎖させる。 |
Withholding | WithheldColumnBehavior behavior | TablePermissions | この表の保留された列がどのように戻ってくるか――省略されたのか、マスクされた数値で現れるのか。 このテーブルのサイト全体の SecurityOptions.WithheldColumnBehavior を上書きします。 |
Writing | string[] columns | TablePermissions | レコードが作成されるときと更新されたとき、名前付きの列を書き込むことを許可します すでに許可されているものに加えることができます。コラムを書くことは読むことを意味するのではなく、名前を付けてください もしユーザーが自分の書いた内容を見られるなら、 Reading(System.String[]) も。 |
WritingAllColumns | TablePermissions | このテーブルのカラムレベルの書き込みセキュリティをオプトアウトします:ユーザーは任意の列を書き込むことができます。 作成時と更新時の両方。両方の書き込みセットを置き換えるので、呼び出します。 WritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) はその前にではなく、その後です。 | |
WritingAllColumnsExcept | string[] columns | TablePermissions | 列レベルの書き込みセキュリティはオプトアウトしますが、名前付き列は読み取り専用のままです。 両方の書き込みセットを置き換えるので、 WritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) その前ではなく、その後に。 |
WritingOnCreate | string[] columns | TablePermissions | レコードが作成されたときに名前付き列を設定することを許可し、その時点でのみ記録が存在します これらは読み取り専用であり、以前の呼び出しであっても — Writing(System.String[])TablePermissions.WritingAllColumns — 書くことができるものにした。事件の優先事項、選ばれた時のことだ それ以降はログされ修正されました。 Writing(System.String[])と同様に、読むことを意味しません。 |
WritingOnUpdate | string[] columns | TablePermissions | 既存のレコードで名前の列を変更することは許可しますが、設定する場合は変更できません 作成された、たとえ以前の呼び出しで書き取れるものになっていても — 決議メモは ケースが存在すると、何かを使います。 Writing(System.String[])と同様に、読み取ることを意味しません。 |
AllColumnsAllowoperations を加えます。Allowingoperationsを付与し、柱はなし。鎖のReading(System.String[])、 Writing(System.String[])やTablePermissions.ReadingAllColumnsで列を開けることができます。ReadingReadingAllColumnsReadingAllColumnsExceptReading(System.String[]) 。ReadingAndWritingWithColumnsServices.TableColumnPermissions 流暢な手法を連鎖させる。WithholdingSecurityOptions.WithheldColumnBehavior を上書きします。WritingReading(System.String[]) も。WritingAllColumnsWritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) はその前にではなく、その後です。WritingAllColumnsExceptWritingOnCreate(System.String[]) / WritingOnUpdate(System.String[]) その前ではなく、その後に。WritingOnCreateWriting(System.String[])TablePermissions.WritingAllColumns — 書くことができるものにした。事件の優先事項、選ばれた時のことだ それ以降はログされ修正されました。 Writing(System.String[])と同様に、読むことを意味しません。WritingOnUpdateWriting(System.String[])と同様に、読み取ることを意味しません。AuthClientOptions クラス
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
LocalAccountsEnabled | bool | True | ポータルがローカルのユーザー名やパスワードアカウントを受け入れているかどうか。デフォルトは |
SignUpEnabled | bool | True | サインインすることでポータルユーザーを作成できるかどうか。デフォルトは |
LocalAccountsEnabledSignUpEnabled