56

私は、他のユーザーに表示される前にモデレートされた推薦をユーザーが送信できるようにするアプリを作成しています。これには、これまでセキュリティ ルールでの実装に失敗してきた多くの制限が必要です。

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

子ルール (例: $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_changedonを使用する予定nominations/statusでしたが、確実に動作させることができませんでした。

4

3 に答える 3

52

加藤 そうです。セキュリティ ルールによってデータがフィルター処理されることは決してないことを理解することが重要です。どの場所でも、すべてのデータ (その子を含む) を読み取ることができるか、まったく読み取れないかのいずれかです。したがって、ルールの場合、「指名」の下に「.read」: true があると、他のすべてのルールが無効になります。

したがって、ここでお勧めするアプローチは、3 つのリストを持つことです。1 つは推薦データを含み、もう 1 つは承認された推薦のリストを含み、もう 1 つは保留中の推薦のリストを含みます。

ルールは次のようになります。

{
  "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 を使用し、すべての実際の指名データを指名リストに保存します (誰も直接列挙することはできません)。

于 2013-01-12T22:19:15.820 に答える
5

セキュリティ ルールの仕組みを完全に理解している場合 (自分で学習しているだけです)、いずれかのルールがアクセスを許可すると、アクセスが許可されます。したがって、それらは次のように読み取られます。

  • 指名 ".read": true、アクセス許可
  • その他のルール: 読まない

さらに、そのルールが削除された場合、$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

アップデート

これをさらに考えてみるとapproved/、レコードをリストできるようにする公開と、非公開にすることでまだ問題が発生すると思いますapproved/restricted/。このユースケースでは、制限されたデータにも独自のパスが必要になる場合があります。

于 2013-01-12T19:25:34.507 に答える
-1

このスレッドは少し時代遅れで、ルールによる解決策があるかもしれませんが、ビデオが言うように、それは巧妙なトリックです: https://youtu.be/5hYMDfDoHpI?t=8m50s

Firebase のドキュメントでは、ルールはフィルターではないと述べているため、これは適切な方法ではない可能性があります: https://firebase.google.com/docs/database/security/securing-data

私はセキュリティの専門家ではありませんが、このトリックをテストしたところ、問題なく動作しました。:)

したがって、この実装に関するセキュリティの問題をよりよく理解できることを願っています。

于 2016-08-03T21:49:12.317 に答える