このページは、セキュリティルールの構造化とセキュリティルールの条件の記述の概念に基づいており、Cloud Firestoreセキュリティルールを使用して、クライアントがドキュメント内の一部のフィールドで操作を実行できるようにするルールを作成する方法を説明しています。
ドキュメントレベルではなく、フィールドレベルでドキュメントへの変更を制御したい場合があります。
たとえば、クライアントがドキュメントを作成または変更できるようにし、そのドキュメントの特定のフィールドを編集することは許可しないようにすることができます。または、クライアントが常に作成するドキュメントに特定のフィールドのセットが含まれるようにすることもできます。このガイドでは、CloudFirestoreセキュリティルールを使用してこれらのタスクの一部を実行する方法について説明します。
特定のフィールドに対してのみ読み取りアクセスを許可する
Cloud Firestoreでの読み取りは、ドキュメントレベルで実行されます。ドキュメント全体を取得するか、何も取得しません。部分的なドキュメントを取得する方法はありません。セキュリティルールだけを使用して、ユーザーがドキュメント内の特定のフィールドを読み取れないようにすることはできません。
一部のユーザーから非表示にしておきたい特定のフィールドがドキュメント内にある場合、最良の方法はそれらを別のドキュメントに配置することです。たとえば、次のようにprivate
サブコレクションにドキュメントを作成することを検討できます。
/ employees / {emp_id}
name: "Alice Hamilton",
department: 461,
start_date: <timestamp>
/ employees / {emp_id} / private / Finances
salary: 80000,
bonus_mult: 1.25,
perf_review: 4.2
次に、2つのコレクションに対して異なるレベルのアクセス権を持つセキュリティルールを追加できます。この例では、カスタム認証クレームを使用して、 Finance
と等しいカスタム認証クレームrole
を持つユーザーのみが従業員の財務情報を表示できることを示しています。
service cloud.firestore {
match /databases/{database}/documents {
// Allow any logged in user to view the public employee data
match /employees/{emp_id} {
allow read: if request.resource.auth != null
// Allow only users with the custom auth claim of "Finance" to view
// the employee's financial data
match /private/finances {
allow read: if request.resource.auth &&
request.resource.auth.token.role == 'Finance'
}
}
}
}
ドキュメント作成のフィールドを制限する
Cloud Firestoreはスキーマレスです。つまり、ドキュメントに含まれるフィールドにデータベースレベルでの制限はありません。この柔軟性により開発が容易になりますが、クライアントが特定のフィールドを含むドキュメントのみを作成できるようにしたい場合や、他のフィールドを含まないようにしたい場合があります。
これらのルールは、 request.resource.data
オブジェクトのkeys
メソッドを調べることで作成できます。これは、クライアントがこの新しいドキュメントに書き込もうとしているすべてのフィールドのリストです。このフィールドのセットをhasOnly()
やhasAny()
)などの関数と組み合わせることで、ユーザーがCloudFirestoreに追加できるドキュメントの種類を制限するロジックを追加できます。
新しいドキュメントで特定のフィールドを要求する
restaurant
コレクションで作成されたすべてのドキュメントに、少なくともname
、 location
、 city
フィールドが含まれていることを確認したいとします。これを行うには、新しいドキュメントのキーのリストでhasAll()
を呼び出します。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document contains a name
// location, and city field
match /restaurant/{restId} {
allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
}
}
}
これにより、レストランを他のフィールドで作成することもできますが、クライアントによって作成されたすべてのドキュメントに少なくともこれら3つのフィールドが含まれるようになります。
新しいドキュメントで特定のフィールドを禁止する
同様に、禁止されているフィールドのリストに対してhasAny()
を使用することにより、クライアントが特定のフィールドを含むドキュメントを作成しないようにすることができます。ドキュメントにこれらのフィールドのいずれかが含まれている場合、このメソッドはtrueと評価されるため、特定のフィールドを禁止するために結果を否定することをお勧めします。
たとえば、次の例では、 average_score
またはrating_count
フィールドを含むドキュメントをクライアントが作成することは許可されていません。これらのフィールドは、後でサーバー呼び出しによって追加されるためです。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document does *not*
// contain an average_score or rating_count field.
match /restaurant/{restId} {
allow create: if (!request.resource.data.keys().hasAny(
['average_score', 'rating_count']));
}
}
}
新しいドキュメントのフィールドの許可リストを作成する
新しいドキュメントで特定のフィールドを禁止する代わりに、新しいドキュメントで明示的に許可されているフィールドのみのリストを作成することをお勧めします。次に、 hasOnly()
関数を使用して、作成された新しいドキュメントにこれらのフィールド(またはこれらのフィールドのサブセット)のみが含まれ、他のフィールドが含まれていないことを確認できます。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document doesn't contain
// any fields besides the ones listed below.
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
必須フィールドとオプションフィールドの組み合わせ
セキュリティルールでhasOnly
hasAll
を組み合わせて、一部のフィールドを要求し、他のフィールドを許可することができます。たとえば、この例では、すべての新しいドキュメントにname
、 location
、およびcity
のフィールドが含まれている必要があり、オプションでaddress
、 hours
、およびcuisine
のフィールドが許可されています。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document has a name,
// location, and city field, and optionally address, hours, or cuisine field
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
(request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
実際のシナリオでは、次のように、このロジックをヘルパー関数に移動して、コードの重複を回避し、オプションのフィールドと必須のフィールドを1つのリストに簡単に組み合わせることができます。
service cloud.firestore {
match /databases/{database}/documents {
function verifyFields(required, optional) {
let allAllowedFields = required.concat(optional);
return request.resource.data.keys().hasAll(required) &&
request.resource.data.keys().hasOnly(allAllowedFields);
}
match /restaurant/{restId} {
allow create: if verifyFields(['name', 'location', 'city'],
['address', 'hours', 'cuisine']);
}
}
}
更新時にフィールドを制限する
一般的なセキュリティ慣行は、クライアントが一部のフィールドのみを編集できるようにし、他のフィールドは編集できないようにすることです。前のセクションで説明したrequest.resource.data.keys()
リストを見るだけでは、これを達成することはできません。このリストは、更新後の完全なドキュメントを表しているため、クライアントが含まなかったフィールドが含まれるためです。変化する。
ただし、 diff()
関数を使用する場合は、 request.resource.data
を、更新前のデータベース内のドキュメントを表すresource.data
オブジェクトと比較できます。これにより、 mapDiff
オブジェクトが作成されます。これは、2つの異なるマップ間のすべての変更を含むオブジェクトです。
このmapDiffでaffectedKeys()
メソッドを呼び出すことにより、編集で変更された一連のフィールドを思い付くことができます。次に、 hasOnly()
やhasAny()
)などの関数を使用して、このセットに特定のアイテムが含まれる(または含まれない)ようにすることができます。
一部のフィールドが変更されないようにする
affectedKeys()
Keys()によって生成されたセットでhasAny()
メソッドを使用し、結果を否定することにより、変更したくないフィールドを変更しようとするクライアント要求を拒否できます。
たとえば、クライアントがレストランに関する情報を更新できるようにし、平均スコアやレビュー数は変更しないようにすることができます。
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow the client to update a document only if that document doesn't
// change the average_score or rating_count fields
allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['average_score', 'rating_count']));
}
}
}
特定のフィールドのみを変更できるようにする
変更したくないフィールドを指定する代わりに、 hasOnly()
関数を使用して、変更したいフィールドのリストを指定することもできます。新しいドキュメントフィールドへの書き込みは、セキュリティルールで明示的に許可するまでデフォルトで許可されないため、これは一般により安全であると見なされます。
たとえば、 average_score
フィールドとrating_count
フィールドを禁止するのではなく、クライアントがname
、 location
、 city
、 address
、 hours
、 cuisine
のフィールドのみを変更できるようにするセキュリティルールを作成できます。
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow a client to update only these 6 fields in a document
allow update: if (request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
つまり、アプリの将来の反復で、レストランのドキュメントにtelephone
フィールドが含まれている場合、戻ってセキュリティルールのhasOnly()
リストにそのフィールドを追加するまで、そのフィールドの編集は失敗します。
フィールドタイプの適用
Cloud Firestoreがスキーマレスであることの別の効果は、特定のフィールドに格納できるデータのタイプについて、データベースレベルでの強制がないことです。これはセキュリティルールで適用できるものですが、 is
演算子を使用します。
たとえば、次のセキュリティルールでは、レビューのscore
フィールドは整数である必要があり、 headline
、 content
、およびauthor_name
フィールドは文字列であり、 review_date
はタイムスタンプです。
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if (request.resource.data.score is int &&
request.resource.data.headline is string &&
request.resource.data.content is string &&
request.resource.data.author_name is string &&
request.resource.data.review_date is timestamp
);
}
}
}
}
is
演算子の有効なデータ型は、 bool
、 bytes
、 float
、 int
、 list
、 latlng
、 number
、 path
、 map
、 string
、 timestamp
です。 is
演算子は、 constraint
、 duration
、 set
、およびmap_diff
データ型もサポートしますが、これらはセキュリティルール言語自体によって生成され、クライアントによって生成されないため、ほとんどの実用的なアプリケーションで使用することはめったにありません。
list
およびmap
のデータ型は、ジェネリックスまたは型引数をサポートしていません。つまり、セキュリティルールを使用して、特定のフィールドにリストまたはマップが含まれるように強制できますが、フィールドにすべての整数またはすべての文字列のリストが含まれるように強制することはできません。
同様に、セキュリティルールを使用して、リストまたはマップの特定のエントリにタイプ値を適用できます(それぞれブレーキ表記またはキー名を使用)が、マップまたはリストのすべてのメンバーのデータ型を適用するショートカットはありません。一度。
たとえば、次のルールは、ドキュメントのtags
フィールドにリストが含まれ、最初のエントリが文字列であることを保証します。また、 product
フィールドにマップが含まれていることを確認します。マップには、文字列である製品名と整数である数量が含まれています。
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderId} {
allow create: if request.resource.data.tags is list &&
request.resource.data.tags[0] is string &&
request.resource.data.product is map &&
request.resource.data.product.name is string &&
request.resource.data.product.quantity is int
}
}
}
}
ドキュメントの作成と更新の両方で、フィールドタイプを適用する必要があります。したがって、セキュリティルールの作成セクションと更新セクションの両方で呼び出すことができるヘルパー関数の作成を検討することをお勧めします。
service cloud.firestore {
match /databases/{database}/documents {
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp;
}
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
}
}
}
}
オプションフィールドの強制タイプ
foo
が存在しないドキュメントでrequest.resource.data.foo
を呼び出すとエラーが発生するため、その呼び出しを行うセキュリティルールは要求を拒否することを覚えておくことが重要です。この状況は、 request.resource.data
のget
メソッドを使用して処理できます。 get
メソッドを使用すると、マップから取得するフィールドが存在しない場合に、そのフィールドにデフォルトの引数を指定できます。
たとえば、レビュードキュメントにオプションのphoto_url
フィールドと、確認するオプションのtags
フィールドがそれぞれ文字列とリストである場合、 reviewFieldsAreValidTypes
関数を次のように書き換えることでこれを実現できます。
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp &&
docData.get('photo_url', '') is string &&
docData.get('tags', []) is list;
}
これにより、 tags
は存在するがリストではないドキュメントは拒否されますが、 tags
(またはphoto_url
)フィールドを含まないドキュメントは許可されます。
部分的な書き込みは許可されません
Cloud Firestoreセキュリティルールに関する最後の注意点は、クライアントがドキュメントに変更を加えることを許可するか、編集全体を拒否することです。同じ操作で他のフィールドを拒否しながら、ドキュメントの一部のフィールドへの書き込みを受け入れるセキュリティルールを作成することはできません。