我正在编写一个应用程序,允许用户在向其他用户显示之前提交经过审核的提名。这需要一些限制,到目前为止,我没有成功实施安全规则:
我目前的规则如下:
{
"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
}
}
}
}
}
子规则(例如$nomination
)不会阻止从父级读取整个子级。如果我在https://my.firebaseio.com/nominations上收听child_added
,即使遵守上述安全规则,它也会愉快地返回所有孩子及其所有数据。
我目前的解决方法是保留一个名为approved
的单独节点,只要有人批准或拒绝提名,就可以在列表之间移动数据,但这似乎是一种非常糟糕的方法。
更新
根据Michael Lehenbauer的优秀评论,我以最小的努力重新实现了最初的想法。
新数据结构如下:
my-firebase
|
`- nominations
|
`- entries
| |
| `- private
| `- public
|
`- status
|
`- pending
`- approved
`- rejected
每个提名都存储在entries
下,其中包含private
下的电话号码,电子邮件等私人数据以及public
下的公开可见数据。
更新的规则如下:
{
"rules": {
"nominations": {
"entries": {
"$id": {
".write": "!data.exists()",
"public": {
".read": true,
},
"private": {
".read": "auth != null"
}
}
},
"status": {
"pending": {
".read": "auth != null",
"$id": {
".write": "root.child('nominations/entries').child($id).exists() && (auth != null || newData.val() == true)"
}
},
"approved": {
".read": true,
"$id": {
".write": "root.child('nominations/entries').child($id).exists() && auth != null"
}
},
"rejected": {
".read": "auth != null",
"$id": {
".write": "root.child('nominations/entries').child($id).exists() && auth != null"
}
}
}
}
}
}
JavaScript实现:
var db = new Firebase('https://my.firebaseio.com')
var nominations = db.child('nominations')
var entries = nominations.child('entries')
var status = nominations.child('status')
var pending = status.child('pending')
var approved = status.child('approved')
var rejected = status.child('rejected')
// Create nomination via form input (not shown)
var createNomination = function() {
var data = {
public: {
name: 'Foo',
age: 20
},
private: {
createdAt: new Date().getTime(),
phone: 123456
}
}
var nomination = entries.push()
nomination.setWithPriority(data, data.private.createdAt)
pending.child(nomination.name()).set(true)
}
// Retrieve current nomination status
var getStatus = function(id, callback) {
approved.child(id).once('value', function(snapshot) {
if (snapshot.val()) {
callback(id, 'approved')
} else {
rejected.child(id).once('value', function(snapshot) {
callback(id, snapshot.val() ? 'rejected' : 'pending')
})
}
})
}
// Change status of nomination
var changeStatus = function(id, from, to) {
status.child(from).child(id).remove()
status.child(to).child(id).set(true)
}
我正在努力实现的唯一部分是处理状态变化,我当前的方法肯定可以改进:
_.each([pending, approved, rejected], function(status) {
status.on('child_added', function(snapshot) {
$('#' + snapshot.name()).removeClass('pending approved rejected').addClass(status.name())
})
})
我打算在child_changed
上使用nominations/status
,但我无法让它可靠地运行。
答案 0 :(得分:46)
加藤是对的。了解安全规则永远不会过滤数据非常重要。对于任何位置,您将能够读取所有数据(包括其子项)或不读取所有数据。因此,对于您的规则,在“提名”下使用“.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用作可以枚举的轻量级列表(分别由未经过身份验证和验证的用户),并将所有实际提名数据存储在提名列表中(没有人可以直接枚举)。
答案 1 :(得分:4)
如果我完全了解安全规则的工作方式(我只是自己学习),那么当任何一条规则允许访问时,就会授予访问权限。因此,它们的内容如下:
此外,如果删除该规则,$nominationId
“。read”会在获得批准后授予访问权限;因此,只要获得批准,.read
和phone
中的state
就会变得多余。
最简单的方法是将其分解为public/
和private/
个孩子,如下所示:
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
<强>更新强>
更多地考虑这一点,我认为你仍会遇到使approved/
公开的问题,这将允许你列出记录,并approved/restricted/
私有。在这个用例中,受限制的数据可能也需要它自己的路径。
答案 2 :(得分:0)
这个帖子有点过时,它可能通过规则有解决方案,但正如视频所说,它是一个巧妙的技巧: https://youtu.be/5hYMDfDoHpI?t=8m50s
这可能不是一个好习惯,因为firebase文档说规则不是过滤器: https://firebase.google.com/docs/database/security/securing-data
我没有安全方面的专家,但我测试了这个技巧,它对我来说很好。 :)
所以我希望更好地了解这个实现的安全问题。