Firebase 使用安全规则限制子/字段访问

Firebase 使用安全规则限制子/字段访问,firebase,firebase-realtime-database,Firebase,Firebase Realtime Database,我正在编写一个应用程序,允许用户提交提名,提名在显示给其他用户之前经过审核。这需要一些限制,我迄今为止在安全规则方面没有成功实施: 隐藏任何尚未批准的提名 从提交电话、批准状态、创建日期等隐藏私人字段。 我目前的规则如下: { "rules": { "nominations": { ".read": true, "$nominationId": { ".read": "data.child(

我正在编写一个应用程序,允许用户提交提名,提名在显示给其他用户之前经过审核。这需要一些限制,我迄今为止在安全规则方面没有成功实施:

隐藏任何尚未批准的提名 从提交电话、批准状态、创建日期等隐藏私人字段。 我目前的规则如下:

{
    "rules": {
        "nominations": {
            ".read": true,

            "$nominationId": {
                ".read": "data.child('state').val() == 'approved' || auth != null", // Only read approved nominations if not authenticated
                ".write": "!data.exists()", // Only allow new nominations to be created

                "phone": {
                    ".read": "auth != null" // Only allow authenticated users to read phone number
                },

                "state": {
                    ".read": "auth != null", // Only allow authenticated users to read approval state
                    ".write": "auth != null" // Only allow authenticated users to change state
                }
            }
        }
    }
}
my-firebase
    |
    `- nominations
        |
        `- entries
        |   |
        |   `- private
        |   `- public
        |
        `- status
            |
            `- pending
            `- approved
            `- rejected
儿童规则,例如$提名,不会阻止家长阅读整个儿童。如果我听了child_的话,它会愉快地返回所有的孩子和他们的所有数据,即使有上述安全规则

我目前的解决方案是保留一个名为approved的单独节点,并在有人批准或拒绝提名时在列表之间移动数据,但这似乎是一种非常糟糕的方法

更新

下面是我的精彩评论,我用最少的努力重新实现了最初的想法

新的数据结构如下:

{
    "rules": {
        "nominations": {
            ".read": true,

            "$nominationId": {
                ".read": "data.child('state').val() == 'approved' || auth != null", // Only read approved nominations if not authenticated
                ".write": "!data.exists()", // Only allow new nominations to be created

                "phone": {
                    ".read": "auth != null" // Only allow authenticated users to read phone number
                },

                "state": {
                    ".read": "auth != null", // Only allow authenticated users to read approval state
                    ".write": "auth != null" // Only allow authenticated users to change state
                }
            }
        }
    }
}
my-firebase
    |
    `- nominations
        |
        `- entries
        |   |
        |   `- private
        |   `- public
        |
        `- status
            |
            `- pending
            `- approved
            `- rejected
每个提名都存储在带有私人数据(如电话号码、电子邮件等)的条目下。在“公共”下的“私人”和“公共”下的“可公开查看的数据”下

更新后的规则如下:

{
    "rules": {
        "nominations": {
            ".read": true,

            "$nominationId": {
                ".read": "data.child('state').val() == 'approved' || auth != null", // Only read approved nominations if not authenticated
                ".write": "!data.exists()", // Only allow new nominations to be created

                "phone": {
                    ".read": "auth != null" // Only allow authenticated users to read phone number
                },

                "state": {
                    ".read": "auth != null", // Only allow authenticated users to read approval state
                    ".write": "auth != null" // Only allow authenticated users to change state
                }
            }
        }
    }
}
my-firebase
    |
    `- nominations
        |
        `- entries
        |   |
        |   `- private
        |   `- public
        |
        `- status
            |
            `- pending
            `- approved
            `- rejected
{ 规则:{ 提名:{ 参赛作品:{ $id:{ .write:!data.exists, 公众:{ 读:是的, }, 私人:{ .read:auth!=null } } }, 地位:{ 待定:{ .read:auth!=null, $id:{ .write:root.child“提名/条目”.child$id.exists&&auth!=null | | newData.val==true } }, 核准:{ 读:是的, $id:{ .write:root.child“提名/条目”。child$id.exists&&auth!=null } }, 拒绝:{ .read:auth!=null, $id:{ .write:root.child“提名/条目”。child$id.exists&&auth!=null } } } } } } 以及JavaScript实现:

var db=新Firebase'https://my.firebaseio.com' var Naminations=db.child'Naminations' 变量条目=提名。子项“条目” 变量状态=提名。子“状态” var pending=status.child'pending' var已批准=状态。子级“已批准” var rejected=状态。子级“rejected” //通过未显示的表单输入创建提名 var=函数{ 风险值数据={ 公众:{ 名称:“Foo”, 年龄:20 }, 私人:{ createdAt:new Date.getTime, 电话:123456 } } var提名=条目。推送 提名.setWithPrioritydata,data.private.createdAt pending.childnamification.name.settrue } //检索当前提名状态 var getStatus=functionid,回调{ approved.childid.once'value',functionsnapshot{ 如果snapshot.val{ callbackid,“已批准” }否则{ 拒绝。childid。一旦“值”,functionsnapshot{ callbackid,snapshot.val?“已拒绝”:“挂起” } } } } //更改提名状态 var changeStatus=函数ID,从,到{ status.childfrom.childid.remove status.childto.childid.settrue } 我正在努力解决的唯一一个实现部分是处理状态更改,我当前的方法肯定可以改进:

_.每个[待定、批准、拒绝]功能状态{ 状态。在'child_added'上,函数快照{ $+snapshot.name.removeClass“待定已批准已拒绝”。addClassstatus.name } }
我曾计划使用child_changed on Namination/status,但我无法让它可靠地工作。

如果我完全摸索安全规则的工作方式,我只是自己学习它们,那么当任何一条规则允许访问时,访问都被授予。因此,其内容如下:

{
    "rules": {
        "nominations": {
            ".read": true,

            "$nominationId": {
                ".read": "data.child('state').val() == 'approved' || auth != null", // Only read approved nominations if not authenticated
                ".write": "!data.exists()", // Only allow new nominations to be created

                "phone": {
                    ".read": "auth != null" // Only allow authenticated users to read phone number
                },

                "state": {
                    ".read": "auth != null", // Only allow authenticated users to read approval state
                    ".write": "auth != null" // Only allow authenticated users to change state
                }
            }
        }
    }
}
my-firebase
    |
    `- nominations
        |
        `- entries
        |   |
        |   `- private
        |   `- public
        |
        `- status
            |
            `- pending
            `- approved
            `- rejected
提名。改为:正确,授予访问权限 其他规则:不要阅读 此外,如果删除了该规则,$namignionId.read将在记录被批准时授予访问权限;因此,只要得到批准,.read-in-phone和state就变得多余了

将其分解为公共/和私人/儿童可能是最简单的,如下所示:

nominations/unapproved/          # only visible to logged in users
nominations/approved/            # visible to anyone (move record here after approval)
nominations/approved/public/     # things everyone can see
nominations/approved/restricted/ # things like phone number, which are restricted
更新


再想一想,我认为您仍然会遇到一个问题,即将批准/公开,这将允许您列出记录,以及批准/限制/私有。在这个用例中,受限制的数据可能也需要它自己的路径。

加藤是对的。重要的是要了解安全规则从不过滤数据。对于任何位置,您都可以阅读所有 包含或不包含其子项的数据。因此,在你的规则中,在提名项下有一个.read:true否定了你所有的其他规则

所以我推荐的方法是有3个列表。一个包含提名数据,一个包含核准提名列表,一个包含待提名列表

你的规则可以是这样的:

{
  "rules": {
    // The actual nominations.  Each will be stored with a unique ID.
    "nominations": {
      "$id": {
        ".write": "!data.exists()", // anybody can create new nominations, but not overwrite existing ones.
        "public_data": {
          ".read": true // everybody can read the public data.
        },
        "phone": {
          ".read": "auth != null", // only authenticated users can read the phone number.
        }
      }
    },
    "approved_list": {
      ".read": true, // everybody can read the approved nominations list.
      "$id": {
        // Authenticated users can add the id of a nomination to the approved list 
        // by creating a child with the nomination id as the name and true as the value.
        ".write": "auth != null && root.child('nominations').child($id).exists() && newData.val() == true"
      }
    },
    "pending_list": {
      ".read": "auth != null", // Only authenticated users can read the pending list.
      "$id": {
        // Any user can add a nomination to the pending list, to be moderated by
        // an authenticated user (who can then delete it from this list).
        ".write": "root.child('nominations').child($id).exists() && (newData.val() == true || auth != null)"
      }
    }
  }
}
未经身份验证的用户可以添加具有以下内容的新提名:

var id = ref.child('nominations').push({ public_data: "whatever", phone: "555-1234" });
ref.child('pending_list').child(id).set(true);
经过身份验证的用户可以通过以下方式批准邮件:

ref.child('pending_list').child(id).remove();
ref.child('approved_list').child(id).set(true);
要呈现已批准和待处理的列表,您可以使用如下代码:

ref.child('approved_list').on('child_added', function(childSnapshot) {
  var nominationId = childSnapshot.name();
  ref.child('nominations').child(nominationId).child('public_data').on('value', function(nominationDataSnap) {
    console.log(nominationDataSnap.val());
  });
});

通过这种方式,您可以使用approved_list和pending_list作为轻量级列表,未经身份验证和身份验证的用户可以分别枚举这些列表,并将所有实际提名数据存储在提名列表中,没有人可以直接枚举。

此线程有点过时,它可能有一个通过规则的解决方案,但正如视频所说,这是一个巧妙的技巧:

这可能不是一个好的做法,因为firebase文档说规则不是过滤器:

我不是安全方面的专家,但我测试了这个技巧,它对我很有效


因此,我希望能够更好地理解此实现中的安全问题。

在我看来,对于任何需要过滤或隐藏最终用户信息的应用程序,我都会遇到类似的问题,坦率地说,您概述的方法只能证明我可能应该重新考虑在有需求的项目中使用Firebase像这样,很公平。虽然我会记住几件事。首先,像这样对数据进行非规范化一开始可能会感到尴尬,但这是一种完全有效的方法。例如,当您查看twitter提要时,twitter不会进行任何过滤。这都是预先计算好的。这会占用额外的空间,并可能导致暂时不一致的数据,但它的伸缩性比加入/过滤数据要好得多。第二,您需要的基本上是每行访问控制,我认为这在任何数据库中都很难找到。如果我能提供任何帮助,请随时联系firebase.com的michael!举一个@SimenBrekken point的例子:聊天室希望允许用户查看在joinDate和leaveDate之间发送的消息。我们可以在查询中进行过滤以显示,但最终用户仍然可以访问他们不应该看到的消息。由于所有日期都是动态的,因此我们无法进入已批准的_列表、待定的_列表等@michael lehenbauer,是否可以使用Firebase功能来保护数据访问控制?我看不到来自FB的onReaddoc@MichaelLehenbauer第二,您需要的基本上是每行访问控制,我认为这在任何数据库中都很难找到。具有讽刺意味的是,这在许多数据库中通常很难实现或不完整,因为用户登录并不总是将1:1映射到数据库登录。用firebase解决了这个问题。简单的一点应该是只显示./user\u id字段与登录用户匹配的记录,或者只显示登录用户的组有权访问该记录的记录。最好将答案放在这里,而不是一个可能会死掉的链接…上面的视频不违反规则不过滤语句-即,这不是一个试图绕过这一点的把戏。这只是构建数据的一种方法,可以有效地与规则系统配合使用。