web-dev-qa-db-ja.com

Firestoreセキュリティルール-フィールドが変更されているかどうかを確認するにはどうすればよいですか?

私の人生では、書き込みを許可するために次の結果がfalseになる理由を理解できません。 usersコレクションを開始するには空であると仮定し、次の形式のドキュメントをAngularフロントエンドから作成しています:

_{
  displayName: 'FooBar',
  email: '[email protected]'
}
_

私の現在のセキュリティルール:

_service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      function isAdmin() {
        return resource.data.role == 'ADMIN';
      }

      function isEditingRole() {
        return request.resource.data.role != null;
      }

      function isEditingOwnRole() {
        return isOwnDocument() && isEditingRole();
      }

      function isOwnDocument() {
        return request.auth.uid == userId;
      }

      allow read: if isOwnDocument() || isAdmin();
      allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());
    }
  }
}
_

一般的に、ユーザーが自分の役割を編集できないようにします。それ以外の場合、通常のユーザーは自分のドキュメントを編集でき、管理者は誰でも編集できます。

falseisEditingRole()をスタブすると、期待される結果が得られるため、その式に絞り込みました。

書き込みが誤って戻ってくるので、理由を判断できません。どんなアイデアや修正も役立つでしょう!

編集1

私が試したこと:

_function isEditingRole() {
  return request.resource.data.keys().hasAny(['role']);
}
_

そして

_function isEditingRole() {
  return 'role' in request.resource.data;
}
_

そして

_function isEditingRole() {
  return 'role' in request.resource.data.keys();
}
_

Edit 2

最終的に、管理者はユーザーのロールを設定するため、最終的にドキュメントにロールが存在することに注意してください。つまり、以下の Firestore docs によれば、元のリクエストに含まれていなくても、リクエストにはroleキーが含まれます。

リソースに存在するリクエストで提供されていないフィールドは、_request.resource.data_に追加されます。ルールは、_request.resource.data.foo_を_resource.data.foo_と比較することで、フィールドが変更されているかどうかをテストできます。これは、書き込み要求で送信されなかった場合でも、resourceのすべてのフィールドが_request.resource_にも存在することを知っています。

それによると、「編集1」の3つのオプションは除外されていると思います。私は_request.resource.data.role != resource.data.role_の提案を試みましたが、それも機能していません...私は迷っていて、実際にFirestoreにバグがあるのではないかと思い始めています。

13
menehune23

結局、実際にfalseを返すときに_resource.data.nonExistentField == null_がErrorを返すと仮定していたようです( this と私のテストによると) )。だから、私の元の解決策はそれに走っていたかもしれません。 docs に従って反対が機能するはずであるため、これは不可解です。しかし、ドキュメントは、キーではなく「存在しない」値を参照している可能性があります-微妙な違い。

私はまだ100%の明確さを持っていませんが、これは私がうまくいったことになりました:

_function isAddingRole() {
  return !('role' in resource.data) && 'role' in request.resource.data;
}

function isChangingRole() {
  return 'role' in resource.data && 'role' in request.resource.data && resource.data.role != request.resource.data.role;
}

function isEditingRole() {
  return isAddingRole() || isChangingRole();
}
_

依然として私を困惑させているのは、ドキュメントによると、isChangingRole()の_&& 'role' in request.resource.data_部分は必要ないということです。Firestoreによって自動的に挿入されるためです。これを削除すると、権限の問題で書き込みが失敗するため、そうではありませんでした。

書き込みをallow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());だけでなく、createupdate、およびdeleteの部分に分割することで、明確化/改善される可能性があります。

2
menehune23

更新を確認するためのカスタム関数を作成すると、ルールがはるかに読みやすく、保守しやすくなります。例えば:

service cloud.firestore {
  match /databases/{database}/documents {
    function isUpdatingField(fieldName) {
      return (!fieldName in resource.data && fieldName in request.resource.data) || resource.data[fieldName] != request.resource.data[fieldName];
    }

    match /users/{userId} {
      //read rules here...
      allow write: if !isUpdatingField("role") && !isUpdatingField("adminOnlyAttribute");
    }
  }
}
4
Tom Bailey

writeFieldsを使用して解決しました。このルールを試してください。

allow write: if !('role' in request.writeFields);

私の場合、listを使用してフィールドの更新を制限しています。それも機能します。

allow update: if !(['leader', '_created'] in request.writeFields);
4
gekijin

ドキュメント内のwriteFieldsへの参照がなくなったため、writeFieldsでできることを行うための新しい方法を考え出す必要がありました。

function isSameProperty(request, resource, key) {
    return request.resource.data[key] == resource.data[key]
}

match /myCollection/{id} {
    // before version !request.writeFields.hasAny(['property1','property2','property3', 'property4']);
  allow update: isSameProperty(request, resource, 'property1')
    && isSameProperty(request, resource, 'property2')
    && isSameProperty(request, resource, 'property3')
    && isSameProperty(request, resource, 'property4')
  }
2
Chonghyuk Won

この単一の機能を使用すると、フィールドが作成/変更されているかどうかを確認できます。

function incomingDataHasFields(fields) {
    return ((
        request.writeFields == null
        && request.resource.data.keys().hasAll(fields)
    ) || (
        request.writeFields != null
        && request.writeFields.hasAll(fields)
  ));
}

使用法:

match /xxx/{xxx} {    
    allow create:
        if incomingDataHasFields(['foo'])              // allow creating a document that contains 'foo' field
           && !incomingDataHasFields(['bar', 'baz']);  // but don't allow 'bar' and 'baz' fields to be created
1
Metu

これは過剰な殺しのように思えるかもしれませんが、ユーザーが生成していない他のフィールドがある可能性のあるドキュメントを更新する場合などです。作成された役割などには、それらのフィールドが変更されないことをテストできる関数が必要です。したがって、これら3つのFNを満たします。

function hasOnlyFields(fields) {
  if request.resource.data.keys().hasOnly(fields) 
}
function hasNotChanged(fields) {
  return (fields.size() < 1 || equals(fields[0]))
    && (fields.size() < 2 || equals(fields[1]))
    && (fields.size() < 3 || equals(fields[2]))
    && (fields.size() < 4 || equals(fields[3]))
    && (fields.size() < 5 || equals(fields[4]))
    && (fields.size() < 6 || equals(fields[5]))
    && (fields.size() < 7 || equals(fields[6]))
    && (fields.size() < 8 || equals(fields[7]))
    && (fields.size() < 9 || equals(fields[8]))
}
function equals(field) {
  return field in request.resource.data && field in resource.data && request.resource.data[field] == request.resource.data[field]
}

そのため、ユーザーは名前、年齢、住所のみを更新できますが、ロールとメールは更新できません。

allow update: if hasOnlyFields(['name', 'age', 'address']) && hasNotChanged(['email', 'roles'])

HasNotChangedは最大9つのフィールドをチェックできます。また、実行したいチェックはこれらだけではありません。ドキュメントのタイプと所有権も確認する必要があります。

1
Gerardlamo

クライアントでreadonlyであるフィールドを強制するには、writeFields(- https://firebase.google.com/docs/reference/ rules/rules.firestore.Request#writeFields )。 gekijinはこれを提案しましたが、構文が少し間違っています。

match /myCollection/{id}
  // Readonly fields
  allow update: if !(request.writeFields.hasAny(['field1', 'field2']));
}

FirestoreがwriteFieldsを生成するため、上記のチェックはupdateおよびcreateルールで安全に実行できます。

2018年10月9日編集

@ dls101は、GoogleがwriteFieldsの言及をドキュメントから削除したように思われることを知らせるのに十分親切でした。したがって、このソリューションの使用には注意してください。

0
DarkNeuron

トム・ベイリーの解決策( https://stackoverflow.com/a/48177722/5727205 )は有望に見えました。

しかし、私の場合は、フィールドが編集されないようにする必要があり、既存のデータにフィールドが存在しないというケースがあります。それにより、フィールドが存在するかどうかのチェックを追加しました。

このソリューションは2つのチェックをチェックします。

  1. フィールドがリクエスト内になく、既存のデータ内にない場合(フィールドは変更されない)
  2. またはリクエストと既存のデータが同じである(等しいフィールドは変更されない)
 function isNotUpdatingField(fieldName) {
   return
     ( !(fieldName in request.resource.data) && !(fieldName in resource.data) ) || 
     request.resource.data[fieldName] == resource.data[fieldName];
}
0
Laszlo Schürg