Why isn't authzed picking up this recursive relationship?

311 Views Asked by At

I'm having an issue with authzed schema and relationships. I seem to be misunderstanding how things work.

I've got a scenario where users can be part of a group either by direct inclusion or by indirect location based criteria, location is hierarchical, with three levels -- Country, State/Province, and City.

That is to say, Anyone in Wichita, Topeka, or Dodge City is also in Kansas Anyone in Seattle, Tacoma, or Spokane is in Washington Anyone in Kansas or in Washington is in the United States Similarly, anyone who is in Pune is also in Maharashtra, and anyone in Maharashtra is in India

I've built a schema (https://play.authzed.com/s/cBfN1HhtcoVE) that supports detection of direct inclusions. I have a user_group called wichitans. It includes (naturally) users in the wichita org_unit, as well as user Samuel, who is in Seattle, but will be moving to wichita in the coming months.

I'm using the permission name "is_user_in_ex/implicits" just to understand I have grouping correct. I can see in the Expected_relations that Samuel is in Wichita explicits and Wally is in Wichita implicits which is what I expect, as wally is in the children of wichita.


Now I make a small change to line 22 of the test relationships (https://play.authzed.com/s/zeYxryGzYbaK), so that instead of assigning Wichita to the to implicits, I assign Kansas to implicits. Samual remains in Explicits, Wichita remains in Implicits (because it's a child of Kansas), but Wally is no longer in implicits. I was under the assumption that there would be a recursive evaluation, but that doesn't appear to be the case. Is there a different operator to say "I would like this relationship to be recursive" or do I need to change some schema definitions? I'd like to avoid splitting the org unit into three distinct levels if possible.

2

There are 2 best solutions below

0
On

In SpiceDB, you can take the permissions computations very literally. In the first schema, where the block looks like:

definition user_group {
    relation implicits : org_unit
    relation explicits : user

    permission is_user_in_implicits = implicits + implicits->children
    permission is_user_in_explicits = explicits
}

definition org_unit {
    relation parent: org_unit
    relation children: org_unit | user 
}

We are starting our permissions walk at the user_group object type. When calculating the is_user_in_implicits we are gathering up the relationships for implicits, which contains only the relationship:

user_group:wichitans#implicits@org_unit:kansas

Then, we union that with the objects (note: I don't say users) that are referenced by implicits->children. Pseudocode for what this does could be written as:

for relationship in implicits:
  for child in relationship.subject.children:
    yield child

With the given the relevant children relationships:

org_unit:kansas#children@org_unit:wichita

Will yield the subject org_unit:wichita.

There are no further instructions for the permissions system to follow or resolve.

As noted in the sibling answer, one way to resolve this is to point to a permission on the child. By putting is_user_in_implicits on both the org_unit and user_group, we can resolve through that permission regardless of what type the children relation points to. This is called "duck typing" and should be familiar from programming languages such as python and ruby.

Another way to accomplish this, would be to set the type of children to reference not the org unit itself, but the org unit's children, as follows:

definition user_group {
    relation implicits : org_unit#children
    relation explicits : user

    permission is_user_in_implicits = implicits + implicits->children
    permission is_user_in_explicits = explicits
}

definition org_unit {
    relation parent: org_unit
    relation children: org_unit#children | user 
}

This will require you to set the optional_subject on the relationships to children, but will allow you to hoist the decision about whether to recursively descent into the data layer.

I prefer to be explicit about when we're descending recursively when possible.

You can read more about how SpiceDB computes permissions in the following blog posts:

1
On

While exploring I found if I create a duplicate permission "is_user_in_implicits" on the org unit and selected the children of an org unit + the new "is_user_in_implicits" permission of the org unit's childern it appears that recursive relationships work as expected even up to the level of the united states (at this point it also picks up seattle, washington and Samuel, but that's how I would expect it to work". Is this the correct approach for getting a recursive relationship?

https://play.authzed.com/s/ZcjmA_7_1Xg3/schema