Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Database Rules for groups

I have this Firebase DB which could be changed if necessary:

DB

The JSON of that DB is:

{
  "groups": {
    "1": {
      "name": "G1",

      "points": {
        "1": {
            "name": "p1"
        }
      },
      "visits": {
        "1": {
            "name": "v1"
        }
      },
      "areas": {
        "1": {
            "name": "a1"
        }
      },
      "waypoints": {
        "1": {
            "name": "w1"
        }
      },
      "interests": {
        "1": {
            "name": "i1"
        }
      }
    },
    "2": {
      "name": "G2",

      "points": {
        "2": {
            "name": "p2"
        }
      },
      "visits": {
        "2": {
            "name": "v2"
        }
      },
      "areas": {
        "2": {
            "name": "a2"
        }
      },
      "waypoints": {
        "2": {
            "name": "w2"
        }
      },
      "interests": {
        "2": {
            "name": "i2"
        }
      }
    }
  },
  "users": {
    "qdRw1khg1ZO1s52YioYCdM4WrD02": {
      "firstName": "AAAA",
      "lastName": "BBB",
      "email": "[email protected]"     
    },
    "h3KYDXkPQrY246w6Y6NXIanVoNS2": {
      "firstName": "FF",
      "lastName": "RR",
      "email": "[email protected]"
    }
  },
  "userGroups": {
    "qdRw1khg1ZO1s52YioYCdM4WrD02": {
      "1": "admin",
      "2": "readwrite"
    },
    "h3KYDXkPQrY246w6Y6NXIanVoNS2": {
      "1": "admin",
      "2": "readonly"     
    }
  }
}

I want to define rules to accomplish the following:

  • Everyone can create a new group
  • Only users of a group can read group data
  • Only "admin"s of a group can write data to the group itself, add users and change sub levels of group data but
    • "readwrite" group users can write to the sub levels "points" and "visits"
    • "readonly" group users can not write at all

I have:

"groups": {          
  "$groupId": {
    ".read": "root.child('userGroups').child(auth.uid).child($groupId).exists()",
    ".write": "! root.child('userGroups').child(auth.uid).child($groupId).exists() || 
                          (data.parent().val() === 'points' && root.child('userGroups').child(auth.uid).child($groupId).val() != 'readonly') ||
                        (data.parent().val() === 'visits' && root.child('userGroups').child(auth.uid).child($groupId).val() === 'readonly') ||
                        (data.parent().val() != 'points' && data.parent().val() != 'visits' && root.child('userGroups').child(auth.uid).child($groupId).val() === 'admin')"
  }
},
"users": {
  "$userId": {
    ".read": "auth != null",
    ".write": "auth != null && 
              $userId === auth.uid && 
              newData.val() != null"
  }
},
"userGroups": {
  "$userId": {
    ".read": "auth != null",
    ".write": "auth != null && 
               data.child(auth.uid).val() === 'admin' && 
               newData.val() != null"          
  }
}

But that does not work since

data.parent().val()

does not return the parent's name string. So I can't do

data.parent().val() != 'points'

How to solve this? The problem is writing data to groups according to the rules specified.

like image 245
juergen d Avatar asked Feb 04 '23 04:02

juergen d


1 Answers

Firebaser here. Expect this answer to be updated as I go along.

My first step is to move the rules for the specific child nodes into that specific child node. That removes the parent() problem you've been having. First iteration is:

  "groups": {          
    "$groupId": {
      ".read": "root.child('userGroups').child(auth.uid).child($groupId).exists()",
      "points": {
        ".write": "root.child('userGroups').child(auth.uid).child($groupId).val() !== 'readonly'"
      }
    }
  },

This allows user h3KYDXkPQrY246w6Y6NXIanVoNS2 to write to /groups/1/points (of which the user is an admin), but not to /groups/2/points (to which the user only has readonly access).

A next step is to make the rule more generic. To do this I introduce a $child variable, which matches any node under the group:

  "groups": {          
    "$groupId": {
      ".read": "root.child('userGroups').child(auth.uid).child($groupId).exists()",
      "$child": {
        ".write": "root.child('userGroups').child(auth.uid).child($groupId).val() !== 'readonly'
                   || ($child !== 'points' && $child !== 'visits')"
      }
    }

This allows user h3KYDXkPQrY246w6Y6NXIanVoNS2 to write to /groups/2/name (which is writeable by any group member), but not to /groups/2/points (to which the user only has readonly access).

Update: apparently I inverted your logic above, so here's my final take:

  "groups": {          
    "$groupId": {
      ".read": "root.child('userGroups').child(auth.uid).child($groupId).exists()",
      ".write": "root.child('userGroups').child(auth.uid).child($groupId).val() == 'admin'",
      "$child": {
        ".write": "root.child('userGroups').child(auth.uid).child($groupId).val() === 'readwrite'
                   && ($child !== 'points' || $child !== 'visits')"
      }
    }

With this user h3KYDXkPQrY246w6Y6NXIanVoNS2:

  • Can write to /groups/1/name because they're admin of group 1
  • Can write to /groups/2/points because they're admin of group 1
  • Can't write to /groups/2/name because they're not an admin of group 2
  • Can write to /groups/2/points because they're a readwrite member of group 2
like image 141
Frank van Puffelen Avatar answered Feb 06 '23 18:02

Frank van Puffelen