Firebase Realtime Database Security Rules #6 - Rules for Your Next Branch

OK let’s keep going with designing our rules for Level2

To reach Level2 you must be logged in, have a user profile, have completed Level1 and additionally completed Level1 with a value higher than 15. 

Let’s write rules for this criteria!

We’re going to use the same LevelModel for this example.

Here’s our code:

     LevelModel levelModel = new LevelModel();
       levelModel.setScore(20);

       DatabaseReference dbRef1 = FirebaseDatabase.getInstance().getReference()
               .child("Levels")
               .child("Level2")
               .child(mAuth.getUid().toString());

       dbRef1.setValue(levelModel).addOnSuccessListener(new OnSuccessListener<Void>() {
           @Override
           public void onSuccess(Void aVoid) {
               Log.i(TAG, "Write to database is successful!");
           }
       }).addOnFailureListener(new OnFailureListener() {
           @Override
           public void onFailure(@NonNull Exception e) {
               Log.e(TAG, "Write to database failed.");
               Log.e(TAG, e.getMessage());
           }
       });
   }
});

Next, we’ll create a rule for Level2 as a peer rule to Level1. For the most part we can copy the Level1 rule contents and instead of enforcing existence of a user profile under /MyUsers we want to check the score value for this user in the /Levels/Level1 tree and ensure it is greater than 15.

We can do that using the root variable and child snapshot method again:

root.child('Levels').child('Level1').child(auth.uid).child('score').val() > 15

So our Level2 rule looks like:

"Level2": {
   "$userUID": {
        ".read": "auth.uid != null && auth.uid == $userUID",
        ".write": "auth.uid != null && auth.uid == $userUID && root.child('Levels').child('Level1').child(auth.uid).child('score').val() > 15",
        ".validate" : "newData.hasChildren(['score'])",
        "score": {
           ".validate" : "newData.isNumber() && newData.val() >=0"
         }
    }
}

Let’s try our Simulator. Remember in our last example that we set the user’s Level1 score to 10. So with our new rule the user should not be able to add a Level2 score to the database.

Click Run on the Simulator and you’ll see the user is disallowed from writing their Level2 score. Great!

But we still need to test that our rule allows writes when the Level1 score is greater than 15. At this point you can go to your Firebase console via the browser and simply update the Level1 score to something above 15.

Run the Simulator again and our rule allows the write. Perfect!

Note that our rule validating the Level1 score also protects against the user having no Level1 score at all. 

At this point our full rule set is:

{
  "rules": { 
      "MyUsers": {
          "$userUID": {
          ".read": "auth.uid != null && auth.uid == data.child('userUID').val() == auth.uid",
          ".write": "auth.uid != null && auth.uid == $userUID && newData.child('userUID').val() == auth.uid",  
          ".validate": "newData.hasChildren(['firstName', 'lastName', 'userUID'])",
          "firstName" : {
              ".validate": "newData.isString() && newData.val().length <= 20"
          },
          "lastName" : {
              ".validate": "newData.isString() && newData.val().length <= 20"
          },
          "userUID" : {
              ".validate": "newData.isString() && newData.val().length === 28 && newData.val().matches(/^[A-z0-9]*/)"
          }
        }
    },
      "Levels": {
          "Level1": {
              "$userUID": {
                  ".read": "auth.uid != null && auth.uid == $userUID",
                  ".write": "auth.uid != null && auth.uid == $userUID && root.child('MyUsers').child(auth.uid).exists()",
              ".validate" : "newData.hasChildren(['score'])",
              "score": {
                ".validate" : "newData.isNumber() && newData.val() >=0"
              }
            }
          },
          "Level2": {
              "$userUID": {
                  ".read": "auth.uid != null && auth.uid == $userUID",
                  ".write": "auth.uid != null && auth.uid == $userUID && root.child('Levels').child('Level1').child(auth.uid).child('score').val() > 15",
              ".validate" : "newData.hasChildren(['score'])",
              "score": {
                ".validate" : "newData.isNumber() && newData.val() >=0"
              }
            }
          }
      }
  }
}

We are on a roll!