【问题标题】:How do I achieve this security logic in Firebase FireStore?如何在 Firebase FireStore 中实现此安全逻辑?
【发布时间】:2021-12-27 14:27:07
【问题描述】:

下图显示了我需要为我的 Firebase Firestore 应用的安全规则的逻辑。

说明 我的应用程序允许用户身份验证,我手动激活客户帐户。激活后,他们可以将用户添加到他们的数据库集合,以及在他们的数据库上执行操作。我在下面将每个客户端说明为“组织”,每个客户端都有多个用户,这些用户应该只能访问数据库集合/文档的特定部分。 每个组织都有一个管理员用户,该用户具有对该特定组织的数据库集合的完全访问权限。每个用户(来自每个组织)都可以访问节点“元素”,以及他们自己的 UID 生成的文档和集合。

看来我需要自定义声明身份验证来实现这一点。我想知道是否存在一些替代方案,例如在我的 fireStore 中添加一些特定的安全规则以使其工作,或者除了 firebase admin sdk 工具之外的任何其他替代方案,因为这很耗时而且我不是专业的后端开发人员。

其他详情 我用颤振。我的应用程序允许客户进行身份验证并创建他们的数据库集合。客户添加具有不同角色的用户(作为团队成员)(这会影响他们可以访问的集合/文档) 这个安全规则逻辑是我现在坚持的主要内容。

我非常感谢可以帮助我实现这一目标的建议和示例。

插图

我现在的 FireStore 安全规则

【问题讨论】:

    标签: google-cloud-firestore firebase-security


    【解决方案1】:

    一种可能的解决方案: 每个组织都应包含一个字符串列表(userIds),并且只有在此列表中具有 userId 的用户才能访问组织集合和文档。

    数据库结构:

    organisation_1:
      userIds (field containing list of user ids - []<String>): 
      adminId (field containing admin id - String):
      admin (collection):
      users (collection):
      elements (collection):
      premium (collection):
    
    organisation_2:
    

    安全规则

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        function isLoggedIn() {
          // only true if user is logged in
          return request.auth != null;
        }
    
        match /organisation/{organisationId} {
          function prefix() {
            return /databases/$(database)/documents/organisation/$(organisationId);
          }
          function isAdmin() {
            // only true if admin
            return isLoggedIn() && request.auth.uid == get(/$(prefix())).data.adminId;
          }
          function isUser() {
            // only true if user
            return isLoggedIn() && request.auth.uid in get(/$(prefix())).data.usersId;
          }
          function isDataOwner(dataId) {
            // only true if user is admin or userId is the document id.
            // this rule should allow each user access to their own UID-
            // generated docs and collections only
            return isLoggedIn() && (isAdmin() || dataId == request.auth.uid);
          }
    
          // since userIds list is organisation data, we should prevent any 
          // user from editing it (or only allow admin to edit it).
          // if you are using cloud function to update userIds list, set this 
          // to false. Cloud function does not need access.
          allow write: if isAdmin();
          allow read: if true;
    
          match /Elements/{elementsId=**} {
            // allow access to the entire Elements collection and 
            // subcollections if isAdmin or isUser.
            allow read, write: if isAdmin() || isUser();
          }
          match /settings/{userId} {
            // allow access only if document id is your userId
            allow read, write: if isDataOwner(userId);
          } 
          match /adminDocs/{docId} {
            // only allow admin
            allow read, write: if isAdmin();
          }
        }
      }
    }
    

    然后您可以使用云功能来保持您的 userIds 列表是最新的。示例:

    const functions = require("firebase-functions");
    const admin = require("firebase-admin");
    const db = admin.firestore();
    
    exports.onCreate = functions.firestore
      .document("/organisation/{organisationId}/users/{userId}")
      .onCreate((_, context) => {
        const params = context.params;
        const organisationId = params.organisationId;
        const userId = params.userId;
    
        const data = {
          userIds: admin.firestore.FieldValue.arrayUnion(userId),
        };
        return db.doc(`/organisation/${organisationId}`)
          .set(data, { merge: true });
      });
    
    exports.onDelete = functions.firestore
      .document("/organisation/{organisationId}/users/{userId}")
      onDelete((_, context) => {
        const params = context.params;
        const organisationId = params.organisationId;
        const userId = params.userId;
    
        const data = {
          userIds: admin.firestore.FieldValue.arrayRemove(userId),
        };
        return db.doc(`/organisation/${organisationId}`)
          .set(data, { merge: true });
      });
    

    您可以通过在管理员创建新用户时简单地将用户 ID 添加到用户 ID 列表来避免此云功能。但是云功能更干净(使用它)。

    更新

    $(database) 是您的 Firestore 数据库的名称。

    {database}(我的安全规则中的第 3 行)告诉规则将数据库的实际名称保存到 database 变量中。

    prefix() 返回组织文档的路径。

    如果用户试图在organisation/12345/users/67890这个路径中访问他的文档,那么$(database)就是defaultprefix()返回/databases/default/documents/organisation/12345/

    你可以去firestore docs查看$(database)和路径(prefix())的使用情况。

    【讨论】:

    • 感谢@Peter O 的慷慨回复。我只是在理解某些语法时遇到了一些困难,例如$(database)。如果您能解释它的作用,并简要讨论您编写的函数(尤其是function prefix()),将不胜感激。
    • @Aron 不客气。我已经更新了我的答案来解释它们。
    • 谢谢@Peter。我现在对这些规则的运作方式有了更好的理解。我用模拟器尝试了一些实验,管理员访问工作正常,但是当我将请求 uid 更改为组织集合中名为“用户”的节点中存在的内容时出现错误。我在这里想念什么? 错误 Error: simulator.rules line [20], column [52]. Property usersId is undefined on object. 此行导致错误 return isLoggedIn() &amp;&amp; request.auth.uid in get(/$(prefix())).data.usersId;
    • 我解决了上面的问题!我没有注意到我必须创建一个数组字段来包含 id 字符串,我正在使用字符串字段哈哈。我注意到 userId 变量应该被传递给这个函数if isDataOwner();。如果你可以更新答案,也许有些人会注意这一点。我只有一个关于云功能的问题,我可以依靠管理员用户来解决这个问题吗?就像,管理员根据需要添加或删除列表中存在的 usersId!?我只是没有足够的后端经验来使用云功能!再次,非常感谢您的宝贵时间!
    • 完美。我已经更新了我的答案 (isDataOwner(userId))。是的,您可以依靠管理员用户来处理。它不太安全,但仍然可以。但是,如果您可以简单地让云功能示例 (firebase.google.com/docs/functions/get-started) 正常工作,那么您可以将我的代码复制并粘贴到那里。
    猜你喜欢
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-11
    • 2023-01-29
    • 2018-04-11
    相关资源
    最近更新 更多