Search code examples
iosswiftfirebase-realtime-databasefirebase-security

Different rule behaviours for the same scheme on Realtime Database


I have added new security rules for my Firebase database, here they are:

{
  "rules": {
    "Users" : {
      "$uid" : {
        ".read" : "auth != null && auth.uid == $uid",
        "numberOfConnections" : {
         ".write" : "(auth != null && auth.uid == $uid) && (newData.val() - data.val() == 1 || newData.val() == 0)",
        },
        "mail" : {
          ".write" : "auth != null && auth.uid == $uid",
        },
        "numberOfFeedBack" : {
          ".write" : "auth != null && auth.uid == $uid",
        },
        "username" : {
          ".write" : "auth != null && auth.uid == $uid",
        }
      }
    },
    "Feedback" : {
      "$uid" : {
        ".read" : "auth != null && auth.uid == $uid",
        ".write" : "auth != null && auth.uid == $uid",
      }
    },
  }
}

When I write with updateChildValues in FeedBack it works, but when I write in Users I get a denied permission.

Here is the code:

// This don't work -> Permission Denied
static func createNewUsersDb(userId : String, username : String, mail : String) {
        let update : [String : Any] = [
            "/Users/\(userId)/username" : username,
            "/Users/\(userId)/mail" : mail,
            "/Users/\(userId)/numberOfConnections" : 0,
            "/Users/\(userId)/numberOfFeedBack" : 0
        ]
        dbRef.updateChildValues(update)
    }

//This work
private static func createNewFeedBackDb(_ informationForFeedback : String){
        let now = DateFormatter()
        now.dateStyle = .medium
        now.timeStyle = .medium
        now.locale = Locale(identifier: "EN-en")
        
        let update : [String : Any] = [
            "/Users/\(userId!)/numberOfFeedBack" : ServerValue.increment(NSNumber(value : 1)),
            "/FeedBack/\(userId!)" : [
                "date" : now.string(from: Date()),
                "information" : informationForFeedback
            ]
        ]
        
        dbRef.updateChildValues(update)
    }

It seems that it's because I didn't write a ".write" rule for Users.uid but I don't understand why it's blocking since I don't write when in the child nodes for which I did set a ".write" rule. Also, if I write a ".write" rule in Users.uid, Firebase's rule handling would cause my sub-rule for numberOfConnections to be ignored.

That's why I specified the same ".write" rule for all child nodes of Users.uid except numberOfConnections, it seems a bit overkill but it's the only way I found.


Solution

  • I'm not exactly sure why your write rules don't work, since you're only writing to allowed paths/values as far as I can see. However, I'd typically implement the case differently, which may solve your problem anyway:

    "Users" : {
      "$uid" : {
        ".read" : "auth != null && auth.uid == $uid",
        ".write" : "auth != null && auth.uid == $uid",
        "numberOfConnections" : {
          ".validate" : "(newData.val() - data.val() == 1 || newData.val() == 0)",
        },
        "mail" : {
          ".validate" : true
        },
        "numberOfFeedBack" : {
          ".validate" : true
        },
        "username" : {
          ".validate" : true
        },
        "$other" : {
          ".validate" : false
        }
      }
    

    So now you only check for authorization once (on /Users/$uid) and then use validation checks to:

    • Allow mail, numberOfFeedBack, and username.
    • Allow numberOfConnections based on the value written.
    • Reject any other child nodes - the $other rule matches all child nodes that are not matched by another/named rules already.