特定のフィールドへのアクセスを制御する

このページは、セキュリティルールの構造化とセキュリティルールの条件の記述の概念に基づいており、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コレクションで作成されたすべてのドキュメントに、少なくともnamelocationcityフィールドが含まれていることを確認したいとします。これを行うには、新しいドキュメントのキーのリストで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を組み合わせて、一部のフィールドを要求し、他のフィールドを許可することができます。たとえば、この例では、すべての新しいドキュメントにnamelocation 、およびcityのフィールドが含まれている必要があり、オプションでaddresshours 、および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フィールドを禁止するのではなく、クライアントがnamelocationcityaddresshourscuisineのフィールドのみを変更できるようにするセキュリティルールを作成できます。

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フィールドは整数である必要があり、 headlinecontent 、および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演算子の有効なデータ型は、 boolbytesfloatintlistlatlngnumberpathmapstringtimestampです。 is演算子は、 constraintdurationset 、および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.datagetメソッドを使用して処理できます。 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セキュリティルールに関する最後の注意点は、クライアントがドキュメントに変更を加えることを許可するか、編集全体を拒否することです。同じ操作で他のフィールドを拒否しながら、ドキュメントの一部のフィールドへの書き込みを受け入れるセキュリティルールを作成することはできません。