如何使用自定义角色更新 Firestore 规则

How to update Firestore Rules with custom Roles

如何配置规则以根据另一个 collection(用户)中定义的自定义用户角色控制 Reads/Writes 和 collection(记录)上的删除?

---- 用户 Collection ----

USERS: {
    <RandomID1> : { uid: 234, email: "abc@xyz.com", role: "ENDUSER" },
    <RandomID2> : { uid: 100, email: "def@xyz.com", role: "ADMIN" }
}

----记录Collection----

RECORDS: {
    <RandomID1> : { uid: "234", name: "Record 123" },
    <RandomID2> : { uid: "234", name: "Record 456" },
    <RandomID3> : { uid: "999", name: "Record 999" } /* another user's record */
}

---- 当前规则----

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

假设用户已登录网络应用程序并使用 client-side Firebase SDK,如何实现以下情况?

国际足联:USERS.RandomID1.role = 'ENDUSER'

  1. 如何限制用户只能从 RECORDS 中读取他们的记录, 但不是别人的?
  2. 如何将更新仅限于他们的 RECORDS 中的记录?

  3. 如何限制对 RECORDS 中所有记录的删除?

  4. 如何限制 collections(/COLLECTION**) 其余部分的所有 CRUD 操作,RECORDS 除外?

国际足联:USERS.RandomID1.role = 'ADMIN'

  1. 如何启用此(管理员)用户执行所有 CRUD 操作 记录?

那么,如何重写或更新规则来控制这些操作呢?如果没有,是否有更好的设计或替代方案?

注意:我们需要处理这些情况,以阻止一些 users/hackers 可能尝试打开浏览器 console/inspect window,并在有或没有的情况下执行 firestore 查询任何条件。

感谢您的帮助!

对于 5 你需要为你的管理员定义 custom claims,下面我假设一个字段 isadmin 为管理员设置为 true。

以下规则应该是一个好的开始:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 4: Will restrict all others access
    match /records/{recordid} {
      // 1+5(read): restrict reads       
      allow read: if request.auth != null
        && (resource.data.uid == request.auth.uid || request.auth.token.isadmin);
      // 2+3+5(write): +create as a bonus
      allow write: if request.auth != null
        && (request.resource.data.uid == request.auth.uid || request.auth.token.isadmin);
    }
  }
}

这是我的最终答案:

rules_version = '2';
service cloud.firestore {
    match /databases/{database}/documents {
        //Rule on a Collection Groups, instead of simple Collection
        match /{NestedSubCollections=**}/<COMMON_COLLECTION-NAME>/{doc} {
            allow create: if isSignedIn() && isAdmin();
            allow read: if isSignedIn() && (isAdmin() || isThisUserRecord(resource.data.<CUST_USER-ID-FIELD>));
            allow update: if isSignedIn() && (isAdmin() || isThisUserRecord(resource.data.<CUST_USER-ID-FIELD>));
            allow delete: if isSignedIn() && isAdmin();
        }
        //Collection UserDB, to validate profile updates
        match /UserDB/{userId=**} {
            allow create: if isSignedIn() && isAdmin();
            allow read: if isSignedIn() && (isAdmin() || hasUserProfile(resource.data.<CUST_USER-ID-FIELD>));
            allow update: if isSignedIn() && (isAdmin() || hasUserProfile(resource.data.<CUST_USER-ID-FIELD>));
            allow delete: if isSignedIn() && isAdmin();
        }
        //Checking if user is signed-in
        function isSignedIn() {
            return request.auth != null;
        }
        //Checking user's admin status, without the context of accessing collection, but directly
        function isAdmin() {
            return get(/databases/$(database)/documents/UserDB/$(request.auth.uid)).data.<CUST_ROLE-FIELD> == "<ADMIN-ROLE-NAME>";
        }
        //Check whether a current record belong to logged-in user; by <CUST_USER-ID-FIELD>, not oAuth-ID
        function isThisUserRecord(custUserId) {
            return get(/databases/$(database)/documents/UserDB/$(request.auth.uid)).data.<CUST_USER-ID-FIELD> == custUserId;
        }
        //Check whether a logged-in user has existing profile or not
        function hasUserProfile(userOauthId) {
            return userOauthId == request.auth.uid;
        }
    }
}

它在我的生产应用程序中运行得非常好,并且无需在应用程序中编写任何代码,一切都得到了保护!

如何测试?

  1. 更新了 UserDB 中的一条用户记录作为值
  2. 然后转到您的前端 Web 应用程序,转到浏览器控制台,尝试访问 Firestore 查询并验证记录和结果
  3. 如果是非管理员用户,那么他只能Read/Update按照我们上面的规则,其他查询都会失败。
  4. 请注意,出于安全原因,create/delete 是在后端通过云功能执行的。

总而言之,管理员可以对所有记录执行任何操作,但非管理员用户只能对自己的记录进行有限的Read/Update操作。

您可能需要根据其他用例进行一些调整。有什么事请告诉我。