Skip to main content
Version: 2.x

Working With Relations

In the previous chapters, we've learned how to write basic policy rules involving model fields and the current user. However, real-world applications usually have authorization requirements that need to access relations. Here're some examples:

  • A Todo is readable if its parent List is not private.
  • A Post is readable if the current user is a member of the Organization that the Post belongs to.

In this chapter, we'll learn how to write policy rules that involve relations.

*-to-one Relations

Accessing *-to-one relation is straightforward. You simply reference the field directly. You can further visit the relation's fields using the dot notation.

model List {
id Int
private Boolean
}

model Todo {
id Int
list List @relation(...)

// `list` references a to-one relation
@@allow('update', !list.private)
}

You can chain the dot notation to make deep traversal in the relation graph. There's no limit on how deep you can go.

*-to-many Relations

To-many relations are a bit more complicated because you're dealing with a list of objects instead of one. The authorization conditions written with to-many relations can be one of the following three forms:

  • Does any of the objects in the list satisfy the condition?
  • Does all of the objects in the list satisfy the condition?
  • Does none of the objects in the list satisfy the condition?

ZenStack provides a succinct syntax called "Collection Predicate Expression" for writing such rules:

Any:  relation?[condition]
All: relation![condition]
None: relation^[condition]

The relation part refers to a to-many relation field. The condition part is a boolean expression scoped to the type of relation. I.e., it can reference the member fields of relation without any qualification.

info

A collection predicate expression must start with a to-many relation field. You can't use a simple array field (like String[]) with it. In the next chapter you'll learn about helper functions that help you work with simple arrays.

🛠️ Adding Relation-Based Access Control

Let's continue working on the schema of our Todo app and add policies that control data access for users within the same space.

schema.zmodel
model Space {
...

// require login
@@deny('all', auth() == null)

// everyone can create a space
@@allow('create', true)

// users in the space can read the space
@@allow('read', members?[user == auth()])

// space admin can update and delete
@@allow('update,delete', members?[user == auth() && role == 'ADMIN'])
}

model SpaceUser {
...

// require login
@@deny('all', auth() == null)

// space owner and admins have full access
@@allow('all', space.owner == auth() || space.members?[user == auth() && role == 'ADMIN'])

// user can read members of spaces that he's a member of
@@allow('read', space.members?[user == auth()])
}

model User {
...

// everyone can sign up
@@allow('create', true)

// full access by oneself
@@allow('all', auth() == this)

// can be read by users sharing any space
@@allow('read', spaces?[space.members?[user == auth()]])
}

model List {
...

// require login
@@deny('all', auth() == null)

// can be read by space members if not private
@@allow('read', owner == auth() || (space.members?[user == auth()] && !private))

// when create, owner must be set to current user, and user must be in the space
@@allow('create,update', owner == auth() && space.members?[user == auth()])

// can be deleted by owner
@@allow('delete', owner == auth())
}

model Todo {
...

// require login
@@deny('all', auth() == null)

// owner has full access
@@allow('all', list.owner == auth())

// space members have full access if the parent List is not private
@@allow('all', list.space.members?[user == auth()] && !list.private)
}

Let's test it out. Rerun generation and start REPL:

npx zenstack generate
npx zenstack repl

Switch to user Joey and fetch Todo Lists:

.auth { id: 1 }
db.list.findMany()
[
{
id: 1,
createdAt: 2023-11-08T04:38:53.385Z,
updatedAt: 2023-11-08T04:38:53.385Z,
spaceId: 1,
ownerId: 2,
title: 'Grocery',
private: false
}
]

We queried with user Joey and now can get the List created by Rachel because Joey is a member of the same space.

Comments
Feel free to ask questions, give feedback, or report issues.

Don't Spam


You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link below