https://authzed.com logo
Title
a

Andrii

03/06/2023, 10:45 AM
Hi all! I'm considering using SpiceDB for our org. I have checked the examples and the documentation, and I would like an advice for experienced users or SpiceDB experts here on how to implement the following scenario. There's a collection of users, and a collection of educational courses. A user can be added to a course with role teacher or student. A course also has an associated license which has startDate and endDate, and also an attribute called protocol which has a string value. With this setup, how to implement a permission "invite_students" that would allow to add new users to course only if : * current user is a teacher in a course * the license startDate is at most 7 back days from now and the endDate is in the future * course's protocol attribute has value "HttpProtocol" I have started to work it out, but I'm a bit confused about what of these conditions to put as a caveat and which should be a relation, so I couldn't work out the full schema, but here's what I have so far: https://play.authzed.com/s/jl0sBWtc1mHu/schema. All the advise is much appreciated. Thanks!
creating a thread for focused discussion 🙂
v

vroldanbet

03/06/2023, 2:46 PM
hey! 👋🏻 I think you are going in the right direction here 💯 Here is one potential way to model it, by smushing all the restrictions into one single caveat. I've made teachers without
valid_license
also possible, for illustration purposes, but I assume it would be fine to enforce only
with valid_license
. Playground at: https://play.authzed.com/s/iQqJloIJEolt/schema
definition user {}

definition course {
    relation student: user
    relation teacher: user | user with valid_license

    permission view = student + teacher
    permission invite_students = teacher
}

caveat valid_license(startDate timestamp, endDate timestamp, now timestamp, receivedProtocol string, expectedProtocol string) {
    (startDate > now && now < endDate) && (receivedProtocol == expectedProtocol)
}
Another option, if you want to favour composable caveats instead, is to model it with one relation per caveat referencing the teacher, as you did in your example. Playground link: https://play.authzed.com/s/kaCcGRtStCAS/schema
definition user {}

definition course {
    relation student: user
    relation teacher: user

    relation teacher_with_license: course#teacher with valid_license
    relation teacher_with_protocol: course#teacher with valid_protocol

    permission view = student + teacher
    permission invite_students = teacher & teacher_with_license & teacher_with_protocol
}

caveat valid_license(startDate timestamp, endDate timestamp, now timestamp) {
    startDate > now && now < endDate
}

caveat valid_protocol(receivedProtocol string, expectedProtocol string) {
    receivedProtocol == expectedProtocol
}
a

Andrii

03/06/2023, 3:38 PM
Thanks @vroldanbet , I'm checking it out! Applying caveats to relations feels counter-intuitive to me, and I wonder what's the logic behind it. I would rather have it on permission level as you would probably like to verify different things for the same entity in different places (like in the composable caveat example). Do I get it right that in this case I'd need to create 2 relationships for a single user-course pair? like one with valid_license and another with valid_protocol?
v

vroldanbet

03/06/2023, 3:51 PM
It's a good question! And one likely @Joey can better answer. If I'm not mistaken, the rationale behind this design is that it follows the same principles in which Zanzibar is based on (although please note this is really an extension to the paper we made). Zanzibar relies on server-side state to aggresively optimize database access in order to achieve global scale, and server-side relation state is core to its design. Having caveat context be part of the tuple state allows us to do clever optimizations that we probably couldn't do otherwise.
You are correct that in this case, in order to achieve composable caveats, you need multiple relations. You could model this as part of
teacher
relation using the
|
operator, but that implicily gives us union (
+
) semantics, so it's not exactly what you want. We could potentially model it if we supported something similar to intersection arrow semantics at the relation level: https://github.com/authzed/spicedb/issues/597
a

Andrii

03/06/2023, 3:57 PM
got it. Also found this thread regarding the relationship caveats: https://github.com/authzed/spicedb/issues/386
v

vroldanbet

03/06/2023, 3:58 PM
also we could support composition natively into caveat definition (e.g a caveat that is composed of other caveats)
yeah, that is the tracking issue for the caveats initiative
a

Andrii

03/06/2023, 4:02 PM
Btw, I'm struggling to make an assertion pass. Could I ask for you help one more time please? The relationships are:
course:c1#teacher@user:u1[valid_license]
course:c1#teacher@user:u1[valid_protocol:{"expectedProtocol":"test"}]
Assertion:
assertTrue:
  - 'course:c1#invite_students@user:u1 with {"status": "active", "expectedProtocol":"test"}'
And I have simplified valid_license caveat for now:
caveat valid_license(status string) {
    status == 'active'
}
So the message I get is
Expected relation or permission course:c1#invite_students@user:u1 with {"status": "active", "expectedProtocol":"test"} to exist
Please advice what is wrong here? Full example: https://play.authzed.com/s/CULLu446bbql/assertions
v

vroldanbet

03/06/2023, 4:49 PM
those relations do not seem correct, because you are setting the context on the
teacher
relation, but the caveat is only valid on the
teacher_with_license
and
teacher_with_protocol
relations
course:c1#teacher@user:u1[valid_license]
course:c1#teacher@user:u1[valid_protocol:{"expectedProtocol":"test"}]
btw I made a mistaken on the modelling of my proposed solution, you should use type
user
instead of
course#teacher
because otherwise you reference all teachers. you can see it working here https://play.authzed.com/s/cFkuHsGVkCTM/schema
a

Andrii

03/07/2023, 11:05 AM
I see, thank you!