1
votes

I'm using the Firebase Bolt Compiler to build my security rules and I've come across the following scenario:

  • I have some nested data that I want to post to Firebase in the following form:

    data: {
      a: //Number, must always be present
      b: //Number, conditionally present (condition B)
      c: //Number, conditionally present (condition C)
    }
    
  • Conditions B and C are based on a piece of data, elsewhere is Firebase, and if met, I want the values included, but if not met, I want to values to be null

    root.condition >= 5  //condition B
    root.condition >= 10 //condition C
    

The following is my attempt, using Bolt, to construct these rules:

type ConditionB extends Number {
  validate() = root.condition >= 5 ? this != null : this == null;
} 

type ConditionC extends Number {
  validate() = root.condition >= 10 ? this != null : this == null;
}

type Data {
  a: Number,
  b: ConditionB,
  c: ContitionC
}

path /data is Data {
  read()  = true,
  write() = true
}

Here is the following rules.json:

"rules": {
  "data": {
    ".validate": "newData.hasChildren(['a', 'b', 'c']),
    ...
  }
}

It can be seen that the .validate rule enforces all children to be present in data. So, how can I ensure that, based on my conditions, the .validate rules for both data and each of its children are correct?

1

1 Answers

3
votes

Since your type can be Null, you must extend Number | Null like so:

type ConditionB extends Number | Null {
  validate() = root.condition >= 5 ? this != null : this == null;
}

type ConditionC extends Number | Null {
  validate() = root.condition >= 10 ? this != null : this == null;
}

type Data {
  a: Number,
  b: ConditionB,
  c: ConditionC
}

path /data is Data {
  read() = true;
  write() = true;
}

Which results in the following JSON rules file:

{
  "rules": {
    "data": {
      ".validate": "newData.hasChildren(['a'])",
      "a": {
        ".validate": "newData.isNumber()"
      },
      "b": {
        ".validate": "(newData.isNumber() || newData.val() == null) && (newData.parent().parent().child('condition').val() >= 5 ? newData.val() != null : newData.val() == null)"
      },
      "c": {
        ".validate": "(newData.isNumber() || newData.val() == null) && (newData.parent().parent().child('condition').val() >= 10 ? newData.val() != null : newData.val() == null)"
      },
      "$other": {
        ".validate": "false"
      },
      ".read": "true",
      ".write": "true"
    }
  }
}