Firestore 规则未按预期工作,在不应该的情况下抛出错误

问题描述 投票:0回答:1

我正在将 Firebase (firestore) 实施到我的 nextjs adhd 库存应用程序中。 我想通过安全规则来保护我的数据库,但我很难让它们发挥作用。

它在 firebase 仪表板上的规则模拟中工作,当我尝试通过 id 获取特定文档时也工作,但在尝试使用过滤器获取时它给了我一个一般的 firebase 错误。 (

VM21513 useFetchThing.ts:29 FirebaseError: Missing or insufficient permissions.
)

请注意,我有两个主要收藏:

  1. things(包含人们添加的东西)
  2. collections(包含每个用户拥有的集合) -- 它有一个子集合“协作者”,其中包含允许协作者的文档

在我的 React 代码中,我正在获取用户创建的最后一个内容(pending = true,creator_uid=user uid),并将其限制为一个文档。我实施了安全规则,以防止用户在不是关联集合中的所有者或协作者的情况下访问某个事物。因此我必须为每个元素获取所述集合。

const thingQuery = query(
    collection(db, 'things').withConverter(thingConverter),
    where('creator_uid', '==', user?.uid),
    where('pending', '==', true),
    orderBy('creation_date', 'desc'),
    limit(1)
  );

  const [things, loading, error] = useCollectionData(thingQuery);

以下是我正在使用的规则:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    function isSignedIn() {
      return request.auth != null;
    }
    
    // return true if the request.auth.uid is in /collections/{collectionID}/collaborators/{collaboratorID}
    function isCollaborator(collectionID) {
      return exists(/databases/$(database)/documents/collections/$(collectionID)/collaborators/$(request.auth.uid));
    }

    function isEditor(collectionID) {
      return get(/databases/$(database)/documents/collections/$(collectionID)/collaborators/$(request.auth.uid)).data.role == 'editor';
    }
      
    function isOwner(collectionID) {
      return get(/databases/$(database)/documents/collections/$(collectionID)).data.owner_uid == request.auth.uid;
    }

    // Rules for Thing documents
    match /things/{thingID} {
        allow read: if isSignedIn()
                            && (isOwner(resource.data.collection_id) || isCollaborator(resource.data.collection_id));
      allow create: if isSignedIn() 
                                && (isOwner(resource.data.collection_id) || isEditor(resource.data.collection_id))
                                && validateThingData(request.resource.data);
      allow update: if isSignedIn() 
                                && (isOwner(resource.data.collection_id) || isEditor(resource.data.collection_id))
                                && validateThingEdits(request.resource.data);
      allow delete: if isSignedIn() && (isOwner(resource.data.collection_id) || isEditor(resource.data.collection_id));

      function validateThingData(data) {
        return data.keys().hasAll(['creator_uid', 'creation_date', 'collection_uid', 'pending'])
          && (data.creator_uid is string && data.creator_uid == request.auth.uid)
          && data.creation_date is timestamp
          && data.collection_id is string
          && data.pending is bool
          && (data.pending == true || data.pending == false)
          && (data.name is string && data.name.size() <= 32 || !('name' in data))
          // && (data.pictures is list || !('pictures' in data))
          && (data.quantity is int || !('quantity' in data))
          && (data.origin is string && data.origin.size() <= 32 || !('origin' in data))
          && (data.date is timestamp || !('date' in data))
          && (data.price is float || !('price' in data))
          && (data.notes is string && data.notes.size() <= 140 || !('notes' in data))
          && (data.filtering_tag is string && data.filtering_tag.size() <= 10 || !('filtering_tag' in data));
      }
      
      function validateThingEdits(data) {
        return (!data.keys().hasAny(['creator_uid', 'creation_date']))
                        && data.collection_id is string
                && data.pending is bool
                && data.name is string && data.name.size() <= 32
                // && (data.pictures is list || !('pictures' in data))
                && data.quantity is int
                && data.origin is string && data.origin.size() <= 32
                && data.date is timestamp
                && data.price is float
                && data.notes is string && data.notes.size() <= 140
                && data.filtering_tag is string && data.filtering_tag.size() <= 10;
      }
    }

    // Rules for Collection documents
    match /collections/{collectionID} {
        allow read: if isSignedIn() 
                            && (isCollectionOwner(resource.data) || isCollaborator(collectionID));
      allow create: if isSignedIn() && validateCollectionData(request.resource.data);
      allow update: if isSignedIn() 
                                && isCollectionOwner(resource.data)
                                && validateCollectionEdits(request.resource.data);
      allow delete: if isSignedIn() 
                                && isCollectionOwner(resource.data);

      function validateCollectionData(data) {
        return data.keys().hasAll(['name', 'creation_date', 'owner_uid'])
                && data.name is string && data.name.size() <= 32
                && data.creation_date is timestamp
                && (data.owner_uid is string && data.owner_uid == request.auth.uid);
      }
      
        function validateCollectionEdits(data) {
        return (!data.keys().hasAny(['owner_uid', 'creation_date']))
                && data.name is string && data.name.size() <= 32;
      }
      
      function isCollectionOwner(data) {
        return data.owner_uid == request.auth.uid;
      }
      
      match /collaborators/{collaboratorID} {
        allow read: if isSignedIn()
                    && (isOwner(collectionID) || isCollaborator(collectionID));
        allow create: if isSignedIn() 
                      && isOwner(collectionID)
                      && validateCollaboratorData(request.resource.data);
        allow update: if isSignedIn()
                      && isOwner(collectionID)
                      && validateCollaboratorData(request.resource.data);
        allow delete: if isSignedIn()
                      && isOwner(collectionID);

        function validateCollaboratorData(data) {
          return data.keys().hasAll(['uid', 'role'])
            && data.uid is string
            && data.role is string && (data.role == 'editor' || data.role == 'viewer');
        }
      }
    }
  }
}

这些是同一级别的两个单独的集合,因为我希望能够快速切换事物中的 collection_id 以将其移动到另一个集合。

我尝试了很多方法来解决这个问题,仍然得到相同的

VM21513 useFetchThing.ts:29 FirebaseError: Missing or insufficient permissions.
错误。

  1. 尝试访问 Firebase 面板中模拟器中的特定文件,它可以在那里工作
  2. 尝试在我的下一个项目中获取具有 id 的特定文档,它也有效

我知道规则中我可以执行的外部文档获取数量有限制。 firebase 文档 firestore 限制

我还知道,如果我查询一个特定文档,或者基于过滤器查询一个或多个文档,安全规则的处理方式会有所不同。 保镖参考,如来自 firebase 的视频中所示

javascript google-cloud-firestore firebase-security
1个回答
0
投票

Firebase 安全规则不会主动过滤数据。相反,他们会检查您执行的任何操作,以确保它只能访问您的规则允许的数据。

在您的情况下,您的

isCollaborator
isEditor
isOwner
函数都需要从另一个集合中读取文档以检查是否满足条件。对于“列表”

© www.soinside.com 2019 - 2024. All rights reserved.