使用安全规则限制子/域访问

时间:2013-01-12 18:34:36

标签: firebase firebase-realtime-database

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

  1. 隐藏尚未批准的任何提名
  2. 隐藏提交中的私人字段(电话,审批状态,创建日期等)
  3. 我目前的规则如下:

    {
        "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,但我无法让它可靠地运行。

3 个答案:

答案 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)

如果我完全了解安全规则的工作方式(我只是自己学习),那么当任何一条规则允许访问时,就会授予访问权限。因此,它们的内容如下:

  • 提名“.read”:true,ACCESS GRANTED
  • 其他规则:未阅读

此外,如果删除该规则,$nominationId“。read”会在获得批准后授予访问权限;因此,只要获得批准,.readphone中的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

我没有安全方面的专家,但我测试了这个技巧,它对我来说很好。 :)

所以我希望更好地了解这个实现的安全问题。