1
,"people" : {
  ".read": "auth != null",
  "$uid":  {
  },
  "e2" : {     
    ".read": "auth.uid != null",
    ".write": "$uid == auth.uid"  
  },
  "l1" : {     
    ".read": "auth.uid != null",
    ".write": "auth.uid != null"  
  }

So e2 is the @ of an email. For example @gmail or @aol or @yahoo. For the l1 child, I want to make the write rule: Write if auth.uid != null && e2 has the same value as the e2 of the person you are writing l1 to. The furthest I could get, is something like this:

"data.parent().child(people).hasChildren(['auth.uid', 'l1'])"    

JSON

"people": {
    "02PdiNpmW3MMyJt3qPuRyTpHLaw2": {
        "e2": "aol.com",
        "l1": 4,
        "X": {
            "e2": "aol.com",
            "l1": 0,
            "P": {
                "e2": "gmail.com",
                "l1": 0,

Basically l1 = like, so a user writes to another users's l1 in the form of adding 1 more to the count.

Operation that should succeed:

A user X wants to like the user with uid 02PdiNpmW3MMyJt3qPuRyTpHLaw2. User X has a e2 child of @aol.com. This is the same as the e2 child of the user he wants to like. User x is also an authorized user, therefore he meets the 2 requirements to write to the l1 of the user he wants to like.

Operation that should not succeed:

User P wants to like the user with uid 02PdiNpmW3MMyJt3qPuRyTpHLaw2. User P has a e2 child of @gmail.com. This is not the same as the e2 child of the user he wants to like. User P therefore does not meet the requirement to write to the l1 of the user he wants to like

  • I'm having a hard time parsing the requirements here, with the names `e2` and `l1` definitely not helping. Can you edit your question to include: 1) the JSON in the database that you're trying to write (as text, no screenshots please)? 2) a write operation that should succeed? 3) a write operation that should fail. – Frank van Puffelen Aug 16 '20 at 23:04
  • @FrankvanPuffelen Thanks for the comment. A added the stuff you suggested. Hope it is clear now. – frank timmon Aug 17 '20 at 00:10
  • Instead of describing the operations in words, can you write them as code? Aside from that, at least one problem is that `auth.uid` should not be in quotes in `data.parent().child(people).hasChildren([auth.uid, 'l1'])"` – Frank van Puffelen Aug 17 '20 at 02:25
  • @FrankvanPuffelen Thanks. I don't know how to write that in code. That is basically what I am looking to solve. The closest I got is the '"data.parent().child(people).hasChildren(['auth.uid', 'l1'])"', which granted is not close. Is a rule like this possible? – frank timmon Aug 17 '20 at 13:36
  • I'm still having a hard time understanding the use-case, which makes it impossible to model the rules for it. If I understand the JSON snippet correctly, it means that: 1) user `X` has liked user `02PdiNpmW3MMyJt3qPuRyTpHLaw2`. 2) this is allowed because the values of their `e2` child nodes are the same. Is that correct? If so, how does `l1` relate to this all? – Frank van Puffelen Aug 17 '20 at 15:14
  • @FrankvanPuffelen Yes you understand that (your point 1 and 2) perfectly correct. Once `X` likes `02PdiNpmW3MMyJt3qPuRyTpHLaw2`, `l1` of `02PdiNpmW3MMyJt3qPuRyTpHLaw2` becomes one more. In other words it was 3 before the like, now it is 4. That is the same for any like. But if P tries to like either of the other users, he cannot because his `e2` node is not the same. – frank timmon Aug 17 '20 at 22:43

1 Answers1

4

This is a really involved scenario, so I'm going to walk through it step-by-step. There is a good chance you'll need to make changes to make these rules work for your complete use-case, so I'm hoping that having each step will make it possible for you to tune them yourself.


The first step is to deobfuscate your data structure. I'll instead use this structure to get started:

{
  "people" : {
    "user1" : {
      "domain" : "aol.com",
      "likeCount" : 2,
      "likers" : {
        "user2" : {
          "comain" : "aol.com"
        },
        "user3" : {
          "domain" : "aol.com"
        }
      }
    }
  }
}

So user1 has two likes, by user2 and user3 both from the same domain.

I highly recommend using meaningful names like this in your database in general, but definitely in questions that you post about it. If people can't easily understand your data model, chances of them helping go down rapidly.


In the above data model, we can ensure that only users from the same domain can like this user with:

  "people": {
    "$uid": {
      "likers": {
        "$likerid": {
          ".write": "data.parent().parent().child('domain').val() == newData.child('domain').val()"
        }
      }
    }
  }

With these rules, I've tried two write operations to people/user1/likers/user4. The first operation succeeds:

{
  "domain": "aol.com"
}

The second operation fails:

{
  "domain": "gmail.com"
}

We should probably also ensure that a user can only write their own likes, and not for other users. We can do that with:

  "people": {
    "$uid": {
      "likers": {
        "$likerid": {
          ".write": "$likerid == auth.uid && 
            data.parent().parent().child('domain').val() == newData.child('domain').val()"
        }
      }
    }
  }

Next up, we'll add a rule that allows a user to like someone, only if they haven't liked them before. We'll do that on people/$uid as we'll need to look at data under likers and under likesCount soon.

The rules for the first step are:

  "people": {
    "$uid": {
      ".write": "
        !data.child('likers').child(auth.uid).exists() && newData.child('likers').child(auth.uid).exists()
      ",
      "likers": {
        "$likerid": {
          ".write": "$likerid == auth.uid && 
            data.parent().parent().child('domain').val() == newData.child('domain').val()"
        }
      }
    }

So these rules allow us to write to a user, if we are adding a like that doesn't exist yet. You may need to do additional checks here to allow updates of other child nodes, but for here we'll keep things as simple as possible (as it's already pretty involved).


Finally you want to ensure that the write must also increment the likeCount, which should be something like this:

  "people": {
    "$uid": {
      ".write": "
        !data.child('likers').child(auth.uid).exists() && newData.child('likers').child(auth.uid).exists()
        && newData.child('likeCount').val() == data.child('likeCount').val() + 1
      ",
      "likers": {
        "$likerid": {
          ".write": "$likerid == auth.uid && 
            data.parent().parent().child('domain').val() == newData.child('domain').val()"
        }
      }
    }

So the new line now checks if thew new data at likeCount is one higher than its previous value.


I've taken each of the steps above in a test database of my own, and tested in the playground with both positive and negative cases. So while there may be some issues, the basic approach of each step works.

As said this is pretty involved, and it's quite likely that you'll need to make significant changes before it fully works for all your use-cases.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Only if not before: if the rules don't do that yet, it'd be a check on `newData` vs `data` for the relevant node. Per day: also sounds possible for example by expanding your data model to include the date in the path. – Frank van Puffelen Aug 18 '20 at 23:39
  • Say you did the first check at likers and not at `$likerid`, you would just use one .parent() less, right? I already had a node called `peopleWhoLike` that are have the child form [double]: `childByAutoID: ikerid`(///the uid of user who likes, not called likedid in mine of course). I tried to make it work at `$peopleWhoLike`, but I suspect it didn't succeed cause it is a double. I am editing the question to add that structure, but marking clearly that it comes after your answer. – frank timmon Aug 19 '20 at 00:22
  • Probably better to put it in the comment, because it would be confusing otherwise for someone just reading the answer. This is what I tried: So the likers in the first answer had something similar called peopleWhoLike, in the json format: `"peopleWhoLike" : { "Optional(\"-MDWpTyQjA6a1VzTWvEV\")" :"QqQpDheHC4cC9bs4KtreArFiS8g2",` That double is: `childBYAutoID: uid` – frank timmon Aug 19 '20 at 00:35
  • I am wondering how the first rule in the answer might work with peopleWhoike, AKA likes, and not $likes. I tried to make it work with likes with the below, but it did not succeed: `"peopleWhoLike" : {"$peopleWhoLike": { ".read": "auth.uid != null", ".write": "$peopleWhoLike == auth.uid" } }` – frank timmon Aug 19 '20 at 00:36
  • **Deleted Comment of Asker**: Sorry I (Asker) deleted the first comment before I saw that it was already answered. My comment, to which the now first comment answers, was : Is it possible to alter the rule that makes sure a user cannot relike another user - in that relike is possible in the next day. – frank timmon Aug 19 '20 at 00:47