basically we're trying to build a schema that uses...
# spicedb
y
basically we're trying to build a schema that uses the model from the cloud IAM blog post. one of our requirements is that we want to have a concept of "personas," which are owned by users and can be used to make authz calls as if they were users (there's a conversation to be had about whether this is really an authz concern, but this was the decision we made :P). we can't use the iamish model without either treating personas as a special case of user or duplicating a decent amount of our schema, because
relation can_do_thing: user:*
on a
role
can't just become
relation can_do_thing: user:* | persona:*
. (as an aside: apparently that's syntactically correct, but spicedb doesn't evaluate it.) one thing i want to explore is whether caveats can cover the hole, especially for the purposes of
LookupSubjects
requests where we only want to return a list of users and not personas - essentially creating a
caveat is_persona () {}
that allows for requests to specify that they want to exclude personas. I'm not quite sure what it would mean in practice, though - would it be something like:
Copy code
caveat persona (personas_desired bool, is_persona bool) {
  (personas_desired && is_persona) || (!personas_desired && !is_persona)
}
be needed because some part of the context has to come from the request and some part has to come from the relation? my other question is about what "optional" means - if I had:
Copy code
relation user: user | user with persona
in my schema, one relationship with
is_persona
in its context, and one relationship with no context attached, and I issued a
LookupSubjects
request with
personas_desired: false
, which of those relations would be returned?
i just went and mucked around in my local and found that if you say
personas_desired: false
, it will exclude the personas, but if you say
personas_desired: true
, it doesn't restrict it to the personas, and non-persona users are returned. why is that?
v
>be needed because some part of the context has to come from the request and some part has to come from the relation? not necessarily, but typically yes. You can store any caveat parameter in the context, which would mean the caveat would be "self-fulfilled", in that you wouldn't need to provide any value in the request. But you could if you wanted, and it that case the context stored will take precedence over anything sent in the request. >in my schema, one relationship with is_persona in its context, and one relationship with no context attached, and I issued a LookupSubjects request with personas_desired: false, which of those relations would be returned? it that case it will only return the user that has no caveat
persona
. >basically we're trying to build a schema that uses the model from the cloud IAM blog post. one of our requirements is that we want to have a concept of "personas," which are owned by users and can be used to make authz calls as if they were users (there's a conversation to be had about whether this is really an authz concern, but this was the decision we made :P). I would like to hear more about this usecase, could you provide a real life example of what this looks like?
y
sure - let me pull a sandbox together. or did you mean like a user story?
also was curious - why is it that
relation can_do_thing: user:* | persona:*
is invalid? is there a reason it must be, or could it theoretically be implemented?
this is kind of what i'd be wanting? https://play.authzed.com/s/LAl0GFcGUJjB/schema
the idea is that personas are interchangeable with users for the purposes of checks + lookups, but they're not the same, because we want to enforce that a persona is owned by a user and can never have more permissions than the user. we enforce the latter in our intermediate service when we do
&&
or set intersections on what comes back for the persona vs the user.
the sandbox is confusing me because it seems like it should work, but I can't figure out why it doesn't.
we also came up with an example that has a different structure but also seems to misbehave: https://play.authzed.com/s/RrOHSbm1Vyht/schema
oh :thonk: i screwed up my test relationships in that first one. it should be
persona:my_persona
and then it seems to work!
thanks for 🦆 ing and i'm happy to discuss further on the use case
and here's the final version in case y'all are curious: https://play.authzed.com/s/BvZVwIYukW0g/schema the key ended up being to create
role:my_role#can_do_thing@user:*
and
role:my_role#can_do_thing@persona:*
, and then doing the
(user + persona) & role->can_do_thing
in the permission
v
>also was curious - why is it that relation can_do_thing: user:* | persona:* is invalid? is there a reason it must be, or could it theoretically be implemented? it's totally possible, why wouldn't it?
>sure - let me pull a sandbox together. or did you mean like a user story? yeah was thinking of a user story
perhaps I'm missing something, did https://play.authzed.com/s/LAl0GFcGUJjB/schema not work? perhaps SpiceDB won't allow missing various subjects in the same permission/relation?
I made it work because by doing this:
Copy code
assertTrue:
  - document:my_document#can_do_thing@user:my_user
  - document:my_document#can_do_thing@persona:my_persona
assertFalse:
  - document:my_other_document#can_do_thing@persona:my_persona
  - document:my_other_document#can_do_thing@user:my_user
`
you had the type of persona as
user
, but that's not its type. It's
persona
the playground couldn't have helped here because it was doing exactly what you asked it to. You asked for a
user
named
my_persona
and that
user
does not exist.
>the idea is that personas are interchangeable with users for the purposes of checks + lookups, but they're not the same, because we want to enforce that a persona is owned by a user and can never have more permissions than the user. we enforce the latter in our intermediate service when we do && or set intersections on what comes back for the persona vs the user. an alternative that I'm thinking is that you could have the
persona
object, and then it will have a relation to the owning user:
Copy code
definition persona {
  relation owner: user
}
then you can grant permissions to it like this:
Copy code
definition user {}

definition persona {
    relation owner: user
}

definition role_binding {
    relation user: user | persona#owner
    relation role: role

    permission can_do_thing = user & role->can_do_thing
}

definition role {
    relation can_do_thing: user:* | persona:*
}

/** document represents a document protected by Authzed. */
definition document {
    relation granted: role_binding
    permission can_do_thing = granted->can_do_thing
}
>oh :thonk: i screwed up my test relationships in that first one. it should be persona:my_persona and then it seems to work! thanks for 🦆 ing and i'm happy to discuss further on the use case lol I should have read everything first 😅
still think that the solution I proposed above seems better
(id probably be faster to evaluate and less stuff to cache)
y
right, but wouldn't the schema above be answer the question of whether the user behind the persona has the permission, not the persona itself? the thing we're trying to model is that personas are a user-like entity, and to be able to issue a permission check with a persona ID and figure out whether that persona has a permission on an entity
I do agree about the runtime complexity and performance
and that's one of my concerns about my current approach, especially because our system really hasn't seen real traffic yet 😓
v
Hey! I was off for easter, sorry for not following up. I guess I need more information on your use-case. You described personas as an additional entity that acts with the permissions of the user, but it wasn't clear if that persona could have more or less permissions than the owning user. I assumed that if the
user
is allowed, the persona is allowed. So you really want to be able to authorize a persona by its ID, not by its owning user ID. I'm in a rush now but I'll have to think a bit about it. You are basically describing two mostly unrelated subjects in the system that should have some sort of permission inheritance across them, but I'm not sure that's supported because spicedb treats them as independent subjects, only related through the edges of the graph and graph traversal semantics.
A strategy that occurs to me is not defining a persona type. Instead personas would be users, but you'd use a special ID format that allows you differentiating them from users. We would then follow the same strategy: a user would be owned by another user, making them a persona. You can then inherit permissions, or attenuate them if needed. It sounds like, for all intent and purposes, personas are users with temporal attenuation, and this seems like something that can be modeled without an actual persona type.
y
the persona can have less permissions but shouldn't have more. we're enforcing that outside of SpiceDB - AND/intersecting together two permission calls when a user wants to issue a call involving a persona.
we also looked into modeling personas as users, potentially using caveats for situations where they need to be differentiated (such as when we want to ask "what is the set of users, exclusive of personas, that has a permission on this object")
but we decided that it was conceptually simpler and therefore probably less error-prone to model them separately
it means we've got a bit of duplication, but figuring out that a permission can be written as
permission do_something = (user + persona) & role->do_something
made things seem nicer
now the only real duplication is out at the leaves of the role, where we need to define the relation as
user:* | persona:*
and make sure that we write both of those relations on the role
we also know that there might be performance implications between the two, but we aren't in a position where we actually have meaningful traffic on our cluster to be able to test it in the real world :/