Firebase 数据库规则 - 控制对集合的访问
Firebase Database Rules - Controlling access to collections
我一直在尝试为 Firebase 数据库想出一个平面数据结构(如推荐的那样),然后是一组规则来正确控制访问。我的示例试图演示如何锁定 down/allow 对跨不同组织的多租户数据库的访问。
我的第一次尝试是这样的:
数据库结构:https://gist.github.com/peteski22/40b0a79a6854d7bb818919a5262f4a7e
{
"admins" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42" : true
},
"organizations": {
"-JiGh_31GA20JabpZBfc" : {
"name" : "Comp1 Ltd"
},
"-JiGh_31GA20JabpZBfd" : {
"name" : "company2 PLC"
}
},
"users" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42": {
"firstName" : "Peter",
"lastName" : "Piper",
"email" : "peter.piper@testtest.com",
"organization" : ""
},
"-JiGh_31GA20JabpZBfe" : {
"firstName" : "Joe",
"lastName" : "Blogs",
"email" : "joe.blogs@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : {
"firstName" : "test",
"lastName" : "user",
"email" : "test.user@google.com",
"organization" : "-JiGh_31GA20JabpZBfd"
}
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Johnny",
"lastName" : "Baggs",
"email" : "j.baggss@co1.com",
"employeeNumber" : "ASV123456"
},
"-JiGh_31GA20JabpZBec" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Maheswari",
"lastName" : "Sanjal",
"email" : "mahe.sanjal@co1.com",
"employeeNumber" : "ASV111111"
},
"-JiGh_31GA20JabpZBce" : {
"organization" : "-JiGh_31GA20JabpZBfd",
"firstName" : "Fedde",
"lastName" : "le Grande",
"email" : "fedde.grande@co2.com",
"employeeNumber" : "ASV111111"
}
}
}
数据库规则:https://gist.github.com/peteski22/b038d81641c1409cec734d187272eeba
{
"rules" : {
"admins" : {
".read" : "root.child('admins').hasChild(auth.uid)",
".write" : "root.child('admins').hasChild(auth.uid)"
},
"users" : {
"$user" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"organizations" : {
"$organization" : {
".read" : "$organization === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"employees" : {
"$employee" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()"
}
}
}
}
但是这里的问题似乎是我不能做类似的事情:
[GET] /employees
查看与登录用户属于同一组织的员工集合。
经过一番折腾,我在文档中读到:https://firebase.google.com/docs/database/security/securing-data#rules_are_not_filters,如果您想像我一样获取数据,我认为归结为“你做错了”。
回到绘图板,阅读后https://www.firebase.com/docs/web/guide/structuring-data.html / https://firebase.google.com/docs/database/web/structure-data
我对数据库结构和规则做了一些修改:
尝试 #2 结构:https://gist.github.com/peteski22/4593733bf54815393a443dfcd0f34c04
{
"admins" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42" : true
},
"organizations": {
"-JiGh_31GA20JabpZBfc" : {
"name" : "Comp1 Ltd",
"users" : {
"-JiGh_31GA20JabpZBfe" : true,
"-JiGh_31GA20JabpZBff" : true,
"-JiGh_31GA20JabpZBea" : true
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : true,
"-JiGh_31GA20JabpZBec" : true
}
},
"-JiGh_31GA20JabpZBfd" : {
"name" : "company2 PLC",
"users" :{
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : true
},
"employees" :{
"-JiGh_31GA20JabpZBce" : true
}
}
},
"users" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42": {
"firstName" : "Peter",
"lastName" : "Piper",
"email" : "peter.piper@testtest.com",
"organization" : ""
},
"-JiGh_31GA20JabpZBfe" : {
"firstName" : "Joe",
"lastName" : "Blogs",
"email" : "joe.blogs@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBff" : {
"firstName" : "Sally",
"lastName" : "McSwashle",
"email" : "sally.mcswashle@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBea" : {
"firstName" : "Eva",
"lastName" : "Rushtock",
"email" : "eva.rushtock@payrollings.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : {
"firstName" : "test",
"lastName" : "user",
"email" : "test.user@google.com",
"organization" : "-JiGh_31GA20JabpZBfd"
}
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Johnny",
"lastName" : "Baggs",
"email" : "j.baggss@financeco.com",
"employeeNumber" : "ASV123456"
},
"-JiGh_31GA20JabpZBec" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Maheswari",
"lastName" : "Sanjal",
"email" : "mahe.sanjal@financeco.com",
"employeeNumber" : "ASV111111"
},
"-JiGh_31GA20JabpZBce" : {
"organization" : "-JiGh_31GA20JabpZBfd",
"firstName" : "Fedde",
"lastName" : "le Grande",
"email" : "fedde.grande@payrollings.com",
"employeeNumber" : "ASV111111"
}
}
}
尝试 #2 规则:https://gist.github.com/peteski22/e1be434cd1ea8ec2e630bec6d8aa714f
{
"rules" : {
"admins" : {
".read" : "root.child('admins').hasChild(auth.uid)",
".write" : "root.child('admins').hasChild(auth.uid)"
},
"users" : {
".indexOn": [ "organization" ],
"$user" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"organizations" : {
".indexOn": [ "users", "employees" ],
"$organization" : {
".read" : "$organization === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"employees" : {
".indexOn": [ "organization" ],
"$employee" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()"
}
}
}
}
现在我可以在每个集合中很好地锁定数据,但获得任何东西的唯一方法是知道 组织 ID,获取该组织,然后获取每个和每个员工的 ID。尽管上面关于结构化数据的文档(部分:Joining Flattened Data)似乎表明这样做很好,但来自 OO 和 SQL 背景感觉很奇怪..这通常意味着..'我是做错了'。
如果有人对我是否走在正确的轨道上或尝试什么有任何建议,我们将不胜感激。
谢谢
彼得
阅读文档并与 firebase-community slack 中的人聊天后,我得出的结论是我的方向是正确的。
我发现使用名为 "Bolt" 的编译器(npm 中的 firebase-bolt)对生成规则也非常有用。
这是我的结构、螺栓规则和编译的 JSON 规则:
结构
{
"admins" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42" : true
},
"organizations": {
"-JiGh_31GA20JabpZBfc" : {
"name" : "Comp1 Ltd",
"users" : {
"-JiGh_31GA20JabpZBfe" : true,
"-JiGh_31GA20JabpZBff" : true,
"-JiGh_31GA20JabpZBea" : true
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : true,
"-JiGh_31GA20JabpZBec" : true
}
},
"-JiGh_31GA20JabpZBfd" : {
"name" : "company2 PLC",
"users" :{
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : true
},
"employees" :{
"-JiGh_31GA20JabpZBce" : true
}
}
},
"users" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42": {
"firstName" : "Peter",
"lastName" : "Piper",
"email" : "peter.piper@testtest.com",
"organization" : ""
},
"-JiGh_31GA20JabpZBfe" : {
"firstName" : "Joe",
"lastName" : "Blogs",
"email" : "joe.blogs@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBff" : {
"firstName" : "Sally",
"lastName" : "McSwashle",
"email" : "sally.mcswashle@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBea" : {
"firstName" : "Eva",
"lastName" : "Rushtock",
"email" : "eva.rushtock@payrollings.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : {
"firstName" : "test",
"lastName" : "user",
"email" : "test.user@google.com",
"organization" : "-JiGh_31GA20JabpZBfd"
}
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Johnny",
"lastName" : "Baggs",
"email" : "j.baggss@financeco.com",
"employeeNumber" : "ASV123456"
},
"-JiGh_31GA20JabpZBec" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Maheswari",
"lastName" : "Sanjal",
"email" : "mahe.sanjal@financeco.com",
"employeeNumber" : "ASV111111"
},
"-JiGh_31GA20JabpZBce" : {
"organization" : "-JiGh_31GA20JabpZBfd",
"firstName" : "Fedde",
"lastName" : "le Grande",
"email" : "fedde.grande@payrollings.com",
"employeeNumber" : "ASV111111"
}
}
}
螺栓规则
// **********
// FUNCTIONS
// **********
function isAdmin (auth) {
return root.admins[auth.uid] != null
}
function isInSameOrganization(auth, orgUid) {
return root.users[auth.uid].organization === orgUid
}
// **********
// PATHS
// **********
path /admins {
read() { isAdmin(auth) }
write() { isAdmin(auth) }
}
path /users {
index() { ["organization"] }
write() { isAdmin(auth) }
}
path /users/{id} is User {
read() { isInSameOrganization(auth, id) || isAdmin(auth) }
}
path /organizations {
write() { isAdmin(auth) }
}
path /organizations/{id} is Organization {
read() { isInSameOrganization(auth, id) }
}
path /employees {
index() { ["organization"] }
write() { isInSameOrganization(auth, this.organization) || isAdmin(auth) }
}
path /employees/{id} is Employee {
read() { isInSameOrganization(auth, id) || isAdmin(auth) }
}
// **********
// TYPES
// **********
type OrganizationID extends String {
validate() { root.organizations[this] != null }
}
type UserID extends String {
validate() { root.users[this] != null }
}
type EmployeeID extends String {
// Validate that the user ID exists in the employees node (read rule access should prevent us reading a employees that isn't in our org)
validate() { root.employees[this] != null }
}
type Email extends String {
validate() {
return this.matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i);
}
}
type User {
firstName: String,
lastName: String
email: Email,
organization: OrganizationID
}
type Employee {
organization: OrganizationID,
firstName: String,
lastName: String,
email: Email,
employeeNumber: String
}
type Organization {
name: String,
users: Map<UserID, Boolean> | Null,
employees: Map<EmployeeID, Boolean> | Null
}
JSON 规则(由 Bolt 生成)
{
"rules": {
"admins": {
".read": "root.child('admins').child(auth.uid).val() != null",
".write": "newData.parent().child('admins').child(auth.uid).val() != null"
},
"users": {
".write": "newData.parent().child('admins').child(auth.uid).val() != null",
".indexOn": [
"organization"
],
"$id": {
".validate": "newData.hasChildren(['firstName', 'lastName', 'email', 'organization'])",
"firstName": {
".validate": "newData.isString()"
},
"lastName": {
".validate": "newData.isString()"
},
"email": {
".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
},
"organization": {
".validate": "newData.isString() && newData.parent().parent().parent().child('organizations').child(newData.val()).val() != null"
},
"$other": {
".validate": "false"
},
".read": "root.child('users').child(auth.uid).child('organization').val() == $id || root.child('admins').child(auth.uid).val() != null"
}
},
"organizations": {
".write": "newData.parent().child('admins').child(auth.uid).val() != null",
"$id": {
".validate": "newData.hasChildren(['name'])",
"name": {
".validate": "newData.isString()"
},
"users": {
"$key1": {
".validate": "newData.parent().parent().parent().parent().child('users').child($key1).val() != null && newData.isBoolean()"
},
".validate": "newData.hasChildren()"
},
"employees": {
"$key2": {
".validate": "newData.parent().parent().parent().parent().child('employees').child($key2).val() != null && newData.isBoolean()"
},
".validate": "newData.hasChildren()"
},
"$other": {
".validate": "false"
},
".read": "root.child('users').child(auth.uid).child('organization').val() == $id"
}
},
"employees": {
".write": "newData.parent().child('users').child(auth.uid).child('organization').val() == newData.child('organization').val() || newData.parent().child('admins').child(auth.uid).val() != null",
".indexOn": [
"organization"
],
"$id": {
".validate": "newData.hasChildren(['organization', 'firstName', 'lastName', 'email', 'employeeNumber'])",
"organization": {
".validate": "newData.isString() && newData.parent().parent().parent().child('organizations').child(newData.val()).val() != null"
},
"firstName": {
".validate": "newData.isString()"
},
"lastName": {
".validate": "newData.isString()"
},
"email": {
".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
},
"employeeNumber": {
".validate": "newData.isString()"
},
"$other": {
".validate": "false"
},
".read": "root.child('users').child(auth.uid).child('organization').val() == $id || root.child('admins').child(auth.uid).val() != null"
}
}
}
}
我仍在寻找其中的错误,但我认为它显示出进步。请注意,Youtube 上有一些不错的官方和非官方 Firebase 视频,大部分文档都相当不错,而且 firebase 社区似乎很友好。所以任何像我这样的新来者,你知道从哪里开始。
我一直在尝试为 Firebase 数据库想出一个平面数据结构(如推荐的那样),然后是一组规则来正确控制访问。我的示例试图演示如何锁定 down/allow 对跨不同组织的多租户数据库的访问。
我的第一次尝试是这样的:
数据库结构:https://gist.github.com/peteski22/40b0a79a6854d7bb818919a5262f4a7e
{
"admins" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42" : true
},
"organizations": {
"-JiGh_31GA20JabpZBfc" : {
"name" : "Comp1 Ltd"
},
"-JiGh_31GA20JabpZBfd" : {
"name" : "company2 PLC"
}
},
"users" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42": {
"firstName" : "Peter",
"lastName" : "Piper",
"email" : "peter.piper@testtest.com",
"organization" : ""
},
"-JiGh_31GA20JabpZBfe" : {
"firstName" : "Joe",
"lastName" : "Blogs",
"email" : "joe.blogs@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : {
"firstName" : "test",
"lastName" : "user",
"email" : "test.user@google.com",
"organization" : "-JiGh_31GA20JabpZBfd"
}
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Johnny",
"lastName" : "Baggs",
"email" : "j.baggss@co1.com",
"employeeNumber" : "ASV123456"
},
"-JiGh_31GA20JabpZBec" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Maheswari",
"lastName" : "Sanjal",
"email" : "mahe.sanjal@co1.com",
"employeeNumber" : "ASV111111"
},
"-JiGh_31GA20JabpZBce" : {
"organization" : "-JiGh_31GA20JabpZBfd",
"firstName" : "Fedde",
"lastName" : "le Grande",
"email" : "fedde.grande@co2.com",
"employeeNumber" : "ASV111111"
}
}
}
数据库规则:https://gist.github.com/peteski22/b038d81641c1409cec734d187272eeba
{
"rules" : {
"admins" : {
".read" : "root.child('admins').hasChild(auth.uid)",
".write" : "root.child('admins').hasChild(auth.uid)"
},
"users" : {
"$user" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"organizations" : {
"$organization" : {
".read" : "$organization === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"employees" : {
"$employee" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()"
}
}
}
}
但是这里的问题似乎是我不能做类似的事情:
[GET] /employees
查看与登录用户属于同一组织的员工集合。
经过一番折腾,我在文档中读到:https://firebase.google.com/docs/database/security/securing-data#rules_are_not_filters,如果您想像我一样获取数据,我认为归结为“你做错了”。
回到绘图板,阅读后https://www.firebase.com/docs/web/guide/structuring-data.html / https://firebase.google.com/docs/database/web/structure-data
我对数据库结构和规则做了一些修改:
尝试 #2 结构:https://gist.github.com/peteski22/4593733bf54815393a443dfcd0f34c04
{
"admins" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42" : true
},
"organizations": {
"-JiGh_31GA20JabpZBfc" : {
"name" : "Comp1 Ltd",
"users" : {
"-JiGh_31GA20JabpZBfe" : true,
"-JiGh_31GA20JabpZBff" : true,
"-JiGh_31GA20JabpZBea" : true
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : true,
"-JiGh_31GA20JabpZBec" : true
}
},
"-JiGh_31GA20JabpZBfd" : {
"name" : "company2 PLC",
"users" :{
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : true
},
"employees" :{
"-JiGh_31GA20JabpZBce" : true
}
}
},
"users" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42": {
"firstName" : "Peter",
"lastName" : "Piper",
"email" : "peter.piper@testtest.com",
"organization" : ""
},
"-JiGh_31GA20JabpZBfe" : {
"firstName" : "Joe",
"lastName" : "Blogs",
"email" : "joe.blogs@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBff" : {
"firstName" : "Sally",
"lastName" : "McSwashle",
"email" : "sally.mcswashle@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBea" : {
"firstName" : "Eva",
"lastName" : "Rushtock",
"email" : "eva.rushtock@payrollings.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : {
"firstName" : "test",
"lastName" : "user",
"email" : "test.user@google.com",
"organization" : "-JiGh_31GA20JabpZBfd"
}
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Johnny",
"lastName" : "Baggs",
"email" : "j.baggss@financeco.com",
"employeeNumber" : "ASV123456"
},
"-JiGh_31GA20JabpZBec" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Maheswari",
"lastName" : "Sanjal",
"email" : "mahe.sanjal@financeco.com",
"employeeNumber" : "ASV111111"
},
"-JiGh_31GA20JabpZBce" : {
"organization" : "-JiGh_31GA20JabpZBfd",
"firstName" : "Fedde",
"lastName" : "le Grande",
"email" : "fedde.grande@payrollings.com",
"employeeNumber" : "ASV111111"
}
}
}
尝试 #2 规则:https://gist.github.com/peteski22/e1be434cd1ea8ec2e630bec6d8aa714f
{
"rules" : {
"admins" : {
".read" : "root.child('admins').hasChild(auth.uid)",
".write" : "root.child('admins').hasChild(auth.uid)"
},
"users" : {
".indexOn": [ "organization" ],
"$user" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"organizations" : {
".indexOn": [ "users", "employees" ],
"$organization" : {
".read" : "$organization === root.child('users').child(auth.uid).child('organization').val()",
".write" : "root.child('admins').hasChild(auth.uid)"
}
},
"employees" : {
".indexOn": [ "organization" ],
"$employee" : {
".read" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()",
".write" : "data.child('organization').val() === root.child('users').child(auth.uid).child('organization').val()"
}
}
}
}
现在我可以在每个集合中很好地锁定数据,但获得任何东西的唯一方法是知道 组织 ID,获取该组织,然后获取每个和每个员工的 ID。尽管上面关于结构化数据的文档(部分:Joining Flattened Data)似乎表明这样做很好,但来自 OO 和 SQL 背景感觉很奇怪..这通常意味着..'我是做错了'。
如果有人对我是否走在正确的轨道上或尝试什么有任何建议,我们将不胜感激。
谢谢 彼得
阅读文档并与 firebase-community slack 中的人聊天后,我得出的结论是我的方向是正确的。
我发现使用名为 "Bolt" 的编译器(npm 中的 firebase-bolt)对生成规则也非常有用。
这是我的结构、螺栓规则和编译的 JSON 规则:
结构
{
"admins" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42" : true
},
"organizations": {
"-JiGh_31GA20JabpZBfc" : {
"name" : "Comp1 Ltd",
"users" : {
"-JiGh_31GA20JabpZBfe" : true,
"-JiGh_31GA20JabpZBff" : true,
"-JiGh_31GA20JabpZBea" : true
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : true,
"-JiGh_31GA20JabpZBec" : true
}
},
"-JiGh_31GA20JabpZBfd" : {
"name" : "company2 PLC",
"users" :{
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : true
},
"employees" :{
"-JiGh_31GA20JabpZBce" : true
}
}
},
"users" : {
"8UnM6LIiZJYAHVdty6gzdD8oVI42": {
"firstName" : "Peter",
"lastName" : "Piper",
"email" : "peter.piper@testtest.com",
"organization" : ""
},
"-JiGh_31GA20JabpZBfe" : {
"firstName" : "Joe",
"lastName" : "Blogs",
"email" : "joe.blogs@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBff" : {
"firstName" : "Sally",
"lastName" : "McSwashle",
"email" : "sally.mcswashle@co1.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"-JiGh_31GA20JabpZBea" : {
"firstName" : "Eva",
"lastName" : "Rushtock",
"email" : "eva.rushtock@payrollings.com",
"organization" : "-JiGh_31GA20JabpZBfc"
},
"WgnHjk5D8xbuYeA7VDM3ngKwCYV2" : {
"firstName" : "test",
"lastName" : "user",
"email" : "test.user@google.com",
"organization" : "-JiGh_31GA20JabpZBfd"
}
},
"employees" : {
"-JiGh_31GA20JabpZBeb" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Johnny",
"lastName" : "Baggs",
"email" : "j.baggss@financeco.com",
"employeeNumber" : "ASV123456"
},
"-JiGh_31GA20JabpZBec" : {
"organization" : "-JiGh_31GA20JabpZBfc",
"firstName" : "Maheswari",
"lastName" : "Sanjal",
"email" : "mahe.sanjal@financeco.com",
"employeeNumber" : "ASV111111"
},
"-JiGh_31GA20JabpZBce" : {
"organization" : "-JiGh_31GA20JabpZBfd",
"firstName" : "Fedde",
"lastName" : "le Grande",
"email" : "fedde.grande@payrollings.com",
"employeeNumber" : "ASV111111"
}
}
}
螺栓规则
// **********
// FUNCTIONS
// **********
function isAdmin (auth) {
return root.admins[auth.uid] != null
}
function isInSameOrganization(auth, orgUid) {
return root.users[auth.uid].organization === orgUid
}
// **********
// PATHS
// **********
path /admins {
read() { isAdmin(auth) }
write() { isAdmin(auth) }
}
path /users {
index() { ["organization"] }
write() { isAdmin(auth) }
}
path /users/{id} is User {
read() { isInSameOrganization(auth, id) || isAdmin(auth) }
}
path /organizations {
write() { isAdmin(auth) }
}
path /organizations/{id} is Organization {
read() { isInSameOrganization(auth, id) }
}
path /employees {
index() { ["organization"] }
write() { isInSameOrganization(auth, this.organization) || isAdmin(auth) }
}
path /employees/{id} is Employee {
read() { isInSameOrganization(auth, id) || isAdmin(auth) }
}
// **********
// TYPES
// **********
type OrganizationID extends String {
validate() { root.organizations[this] != null }
}
type UserID extends String {
validate() { root.users[this] != null }
}
type EmployeeID extends String {
// Validate that the user ID exists in the employees node (read rule access should prevent us reading a employees that isn't in our org)
validate() { root.employees[this] != null }
}
type Email extends String {
validate() {
return this.matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i);
}
}
type User {
firstName: String,
lastName: String
email: Email,
organization: OrganizationID
}
type Employee {
organization: OrganizationID,
firstName: String,
lastName: String,
email: Email,
employeeNumber: String
}
type Organization {
name: String,
users: Map<UserID, Boolean> | Null,
employees: Map<EmployeeID, Boolean> | Null
}
JSON 规则(由 Bolt 生成)
{
"rules": {
"admins": {
".read": "root.child('admins').child(auth.uid).val() != null",
".write": "newData.parent().child('admins').child(auth.uid).val() != null"
},
"users": {
".write": "newData.parent().child('admins').child(auth.uid).val() != null",
".indexOn": [
"organization"
],
"$id": {
".validate": "newData.hasChildren(['firstName', 'lastName', 'email', 'organization'])",
"firstName": {
".validate": "newData.isString()"
},
"lastName": {
".validate": "newData.isString()"
},
"email": {
".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
},
"organization": {
".validate": "newData.isString() && newData.parent().parent().parent().child('organizations').child(newData.val()).val() != null"
},
"$other": {
".validate": "false"
},
".read": "root.child('users').child(auth.uid).child('organization').val() == $id || root.child('admins').child(auth.uid).val() != null"
}
},
"organizations": {
".write": "newData.parent().child('admins').child(auth.uid).val() != null",
"$id": {
".validate": "newData.hasChildren(['name'])",
"name": {
".validate": "newData.isString()"
},
"users": {
"$key1": {
".validate": "newData.parent().parent().parent().parent().child('users').child($key1).val() != null && newData.isBoolean()"
},
".validate": "newData.hasChildren()"
},
"employees": {
"$key2": {
".validate": "newData.parent().parent().parent().parent().child('employees').child($key2).val() != null && newData.isBoolean()"
},
".validate": "newData.hasChildren()"
},
"$other": {
".validate": "false"
},
".read": "root.child('users').child(auth.uid).child('organization').val() == $id"
}
},
"employees": {
".write": "newData.parent().child('users').child(auth.uid).child('organization').val() == newData.child('organization').val() || newData.parent().child('admins').child(auth.uid).val() != null",
".indexOn": [
"organization"
],
"$id": {
".validate": "newData.hasChildren(['organization', 'firstName', 'lastName', 'email', 'employeeNumber'])",
"organization": {
".validate": "newData.isString() && newData.parent().parent().parent().child('organizations').child(newData.val()).val() != null"
},
"firstName": {
".validate": "newData.isString()"
},
"lastName": {
".validate": "newData.isString()"
},
"email": {
".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
},
"employeeNumber": {
".validate": "newData.isString()"
},
"$other": {
".validate": "false"
},
".read": "root.child('users').child(auth.uid).child('organization').val() == $id || root.child('admins').child(auth.uid).val() != null"
}
}
}
}
我仍在寻找其中的错误,但我认为它显示出进步。请注意,Youtube 上有一些不错的官方和非官方 Firebase 视频,大部分文档都相当不错,而且 firebase 社区似乎很友好。所以任何像我这样的新来者,你知道从哪里开始。