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

Sim*_*ken 49 firebase firebase-realtime-database

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

  1. 隐藏尚未批准的任何提名
  2. 隐藏提交中的私人字段(电话,审批状态,创建日期等)

我目前的规则如下:

{
    "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
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

子规则(例如$nomination)不会阻止从父母那里读取整个孩子.如果我child_addedhttps://my.firebaseio.com/nominations上倾听,即使遵守上述安全规则,它也会愉快地返回所有孩子及其所有数据.

我目前的解决方法是保持一个单独的节点命名approved,只要有人批准或拒绝提名,就可以在列表之间移动数据,但这似乎是一种非常糟糕的方法.

更新

根据Michael Lehenbauer的出色评论,我以最小的努力重新实现了最初的想法.

新的数据结构如下:

my-firebase
    |
    `- nominations
        |
        `- entries
        |   |
        |   `- private
        |   `- public
        |
        `- status
            |
            `- pending
            `- approved
            `- rejected
Run Code Online (Sandbox Code Playgroud)

每个提名都存储在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"
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和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)
}
Run Code Online (Sandbox Code Playgroud)

我正在努力实现的唯一部分是处理状态变化,我当前的方法肯定可以改进:

_.each([pending, approved, rejected], function(status) {
    status.on('child_added', function(snapshot) {
        $('#' + snapshot.name()).removeClass('pending approved rejected').addClass(status.name())
    })
})
Run Code Online (Sandbox Code Playgroud)

我打算继续使用child_changed,nominations/status但我无法让它可靠地工作.

Mic*_*uer 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)"
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

未经身份验证的用户可以添加新的提名:

var id = ref.child('nominations').push({ public_data: "whatever", phone: "555-1234" });
ref.child('pending_list').child(id).set(true);
Run Code Online (Sandbox Code Playgroud)

经过身份验证的用户可以批准以下消息:

ref.child('pending_list').child(id).remove();
ref.child('approved_list').child(id).set(true);
Run Code Online (Sandbox Code Playgroud)

要呈现已批准和待定列表,您需要使用以下代码:

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());
  });
});
Run Code Online (Sandbox Code Playgroud)

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

  • 在我看来,我会遇到这样的问题,任何需要过滤或隐藏最终用户信息的应用程序,坦率地说,你所概述的方法只能证明我应该重新考虑使用Firebase来处理具有这些要求的项目. (9认同)
  • 很公平.虽然我会记住几件事.首先,这样的非规范化数据一开始可能会感觉很尴尬,但这是一种完全有效的方法.例如,当您查看推特供稿时,推特不会进行任何"过滤"操作.这都是提前计算好的.这会占用额外空间并可能导致临时不一致的数据,但它会比加入/过滤数据更好地扩展.其次,您正在寻找的基本上是每行访问控制,我认为在任何数据库中都很难找到.如果我能以任何方式提供帮助,请随时联系(firebase dot com的michael)! (5认同)
  • 举一个@SimenBrekken的例子:一个聊天室希望允许用户查看在joinDate和leaveDate之间发送的消息。我们可以在查询中进行过滤以用于显示目的,但是最终用户仍然可以访问不应看到的消息。由于所有日期都是动态的,因此我们无法分为“ approved_list”,“ pending_list”等。@ michael-lehenbauer,是否可以使用Firebase函数来保护数据访问控制?我没有从FB文档中看到`onRead()` (2认同)

Kat*_*ato 5

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

  • 提名“.read”:正确,已授予访问权限
  • 其他规则:未读

此外,如果删除该规则,则$nominationId在记录获得批准的情况下“.read”将授予访问权限;因此,只要它被批准,它就.read变得多余了phonestate

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
Run Code Online (Sandbox Code Playgroud)

更新

再仔细考虑一下,我认为您仍然会遇到公开(approved/允许您列出记录)和approved/restricted/私有的问题。在此用例中,受限数据可能也需要自己的路径。