Keeping the Policies DRY
ZenStack's access policies provide you with a powerful tool to model authorization in a intuitive and declarative way. However, as your application grows, you may find yourself repeating the same access policy patterns across multiple models. In this section, we'll review a typical type of duplication and show you how to avoid it by delegating to related models.
Parent-Child Duplication
If your application has a hierarchy of models, you'll quite often notice that the child models repeat the access policies of their parent. This can be found in our Todo schema, where List
and Todo
models have a parent-child relationship:
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()])
...
}
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)
}
Although not a 100% match, the rules of Todo
closely resemble those of List
. How can we avoid such duplication and stay DRY?
Delegating Access Control to a Relation
If we carefully inspect the policies, we'll find the rules of Todo
can summarized into a single statement:
One has full access to a
Todo
entity if he can read its parentList
entity.
ZenStack provides a check()
attribute function for you to delegate access control of the current model to a relation. Let's refactor the Todo
model to use this feature 🛠️:
model Todo {
...
// full access if the parent list is readable
@@allow('all', check(list, 'read'))
}
When authorizing an operation on a Todo
(e.g., "update"), ZenStack will make the decision based on if the related List
entity is readable. If you adjust the rules on List
in the future, the Todo
model automatically follows without needing any changes.
The check()
API can be used in both model-level and field-level policies. See the API Reference for more details.