【问题标题】:Firebase Database Rules - Controlling access to collectionsFirebase 数据库规则 - 控制对集合的访问
【发布时间】:2017-12-08 18:51:30
【问题描述】:

我一直在尝试为 Firebase 数据库提供一个平面数据结构(如推荐的那样),然后是一组规则来正确控制访问。我的示例试图演示如何锁定/允许访问跨不同组织的多租户数据库。

我的第一次尝试是这样的:

数据库结构: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

我对db结构和规则做了一些修改:

尝试 #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 获取每个员工.虽然上面用于结构化数据的文档(部分:加入扁平数据)似乎表明这样做很好,但来自 OO 和 SQL 背景感觉很奇怪..这通常意味着..“我做错了” .

如果有人对我是否走在正确的轨道上或尝试什么有任何建议,将不胜感激。

谢谢 彼得

【问题讨论】:

    标签: firebase firebase-realtime-database multi-tenant firebase-security nosql


    【解决方案1】:

    在阅读文档并与 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 社区似乎很友好。所以像我这样的新人,你知道从哪里开始。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-19
      • 2018-12-15
      • 1970-01-01
      • 1970-01-01
      • 2023-02-01
      • 2017-06-08
      • 2017-02-21
      • 1970-01-01
      相关资源
      最近更新 更多