使用安全规则限制儿童/现场访问 [英] Restricting child/field access with security rules

查看:18
本文介绍了使用安全规则限制儿童/现场访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

我目前的规则如下:

<代码>{规则":{提名":{.read":对,$nominationId":{".read": "data.child('state').val() == 'approved' || auth != null",//如果未通过身份验证,则仅读取已批准的提名".write": "!data.exists()",//只允许创建新的提名电话": {".read": "auth != null"//只允许经过身份验证的用户读取电话号码},状态": {".read": "auth != null",//只允许认证用户读取审批状态".write": "auth != null"//只允许经过身份验证的用户改变状态}}}}}

子规则(例如 $nomination)不会阻止从父项读取整个子项.如果我在 https://my.firebaseio.com/nominationschild_ added> 即使在上述安全规则到位的情况下,它也会很高兴地返回所有孩子和他们的所有数据.

我目前的解决方法是保留一个名为 approved 的单独节点,并在有人批准或拒绝提名时简单地在列表之间移动数据,但这似乎是一种非常糟糕的方法.

更新

遵循 Michael Lehenbauer 的精彩评论我以最小的努力重新实现了最初的想法.

新的数据结构如下:

my-firebase|`- 提名|`- 条目|||`- 私人|`- 公共|`- 状态|`- 待定`- 批准`- 拒绝

每个提名都存储在 entries 下,在 private 下带有私人数据,例如电话号码、电子邮件等,在 public 下可公开查看数据>.

更新规则如下:

{规则":{提名":{条目":{$ id":{".write": "!data.exists()",民众": {.read":对,},私人的": {".read": "auth != null"}}},地位": {待办的": {".read": "auth != null",$ id":{".write": "root.child('nominations/entries').child($id).exists() && (auth != null || newData.val() == true)"}},得到正式认可的": {.read":对,$ id":{".write": "root.child('nominations/entries').child($id).exists() && auth != null"}},拒绝了": {".read": "auth != null",$ id":{".write": "root.child('nominations/entries').child($id).exists() && auth != null"}}}}}}

以及 JavaScript 实现:

var db = new Firebase('https://my.firebaseio.com')var 提名 = db.child('提名')var entry = Nominations.child('entries')var status = Nominations.child('status')var pending = status.child('pending')var批准 = status.child('批准')var denied = status.child('rejected')//通过表单输入创建提名(未显示)var createNomination = function() {变量数据 = {民众: {name: '福',年龄:20},私人的: {createdAt: 新日期().getTime(),电话:123456}}var提名 = entry.push()提名.setWithPriority(数据,data.private.createdAt)pending.child(nomination.name()).set(true)}//检索当前提名状态var getStatus = function(id, callback) {批准.child(id).once('value', function(snapshot) {如果(快照.val()){回调(ID,'批准')} 别的 {拒绝.child(id).once('value', function(snapshot) {回调(id,snapshot.val()?'拒绝':'待定')})}})}//改变提名状态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', 函数(快照){$('#' + snapshot.name()).removeClass('待批准被拒绝').addClass(status.name())})})

我打算在 nominations/status 上使用 child_changed,但我一直无法让它可靠地工作.

解决方案

Kato 是对的.了解安全规则从不过滤数据很重要.对于任何位置,您要么能够读取所有数据(包括其子数据),要么无法读取.因此,就您的规则而言,在提名"下设置.read":true 会否定您的所有其他规则.

所以我在这里推荐的方法是有 3 个列表.一份包含提名数据,一份包含已批准的提名列表,一份包含待定提名列表.

你的规则可能是这样的:

<代码>{规则":{//实际提名.每个都将存储一个唯一的 ID.提名":{$ id":{".write": "!data.exists()",//任何人都可以创建新的提名,但不能覆盖现有的提名.公共数据":{".read": true//每个人都可以读取公共数据.},电话": {".read": "auth != null",//只有经过身份验证的用户才能读取电话号码.}}},approved_list":{".read": true,//每个人都可以阅读批准的提名名单.$ id":{//通过认证的用户可以将提名的id添加到批准列表中//通过创建一个以提名 id 作为名称和 true 作为值的孩子.".write": "auth != null && root.child('nominations').child($id).exists() && newData.val() == true"}},待处理清单": {".read": "auth != null",//只有经过身份验证的用户才能读取待处理列表.$ id":{//任何用户都可以将提名添加到待处理列表中,由//一个经过身份验证的用户(然后可以从这个列表中删除它).".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提名Id = childSnapshot.name();ref.child('nominations').child(nominationId).child('public_data').on('value', function(nominationDataSnap) {console.log(nominationDataSnap.val());});});

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

I'm writing an app that allows users to submit nominations which are moderated before being displayed to other users. This requires a number of restrictions I've so far been unsuccessful in implementing with security rules:

  1. Hide any nominations that haven't been approved yet
  2. Hide private fields from submission (phone, approval status, creation date etc.)

My current rules are as follows:

{
    "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
                }
            }
        }
    }
}

Child rules (e.g. $nomination) don't prevent the entire child from being read from the parent. If I listen for child_added on https://my.firebaseio.com/nominations it happily returns all children and all their data even with the above security rules in place.

My current workaround idea for this is to keep a separate node named approved and simply move the data between lists whenever someone approves or rejects a nomination, but it seems like a horribly broken approach.

Update

Following Michael Lehenbauer's excellent comment I've reimplemented the initial idea with minimal effort.

The new data structure is as follows:

my-firebase
    |
    `- nominations
        |
        `- entries
        |   |
        |   `- private
        |   `- public
        |
        `- status
            |
            `- pending
            `- approved
            `- rejected

Each nomination is stored under entries with private data such as phone number, e-mail etc. under private and publicly viewable data under public.

The updated rules are as follows:

{
    "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"
                    }
                }
            }
        }
    }
}

And the JavaScript implementation:

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)
}

The only part of the implementation I'm struggling with is handling status changes, my current approach can surely be improved upon:

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

I was planning on using child_changed on nominations/status but I haven't been able to get it working reliably.

解决方案

Kato's right. It's important to understand that security rules never filter data. For any location, you'll either be able to read all of the data (including its children) or none of it. So in the case of your rules, having a ".read": true under "nominations" negates all of your other rules.

So the approach I'd recommend here is to have 3 lists. One containing nomination data, one to contain the list of approved nominations, and one to contain the list of pending nominations.

Your rules could be like so:

{
  "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)"
      }
    }
  }
}

An unauthenticated user could add a new nomination with:

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

An authenticated user could approve a message with:

ref.child('pending_list').child(id).remove();
ref.child('approved_list').child(id).set(true);

And to render the approved and pending lists you'd use code something like:

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());
  });
});

In this way, you use approved_list and pending_list as lightweight lists that can be enumerated (by unauthenticated and authenticated users respectively) and store all of the actual nomination data in the nominations list (which nobody can enumerate directly).

这篇关于使用安全规则限制儿童/现场访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆