Policies
Policies provide a way to group your authorization logic around a particular resource.
A policy is essentially a registry of rules relating to the same resource type.
Declaring a Policy
The policy constructor does not take any parameter, but take a type argument that will define which resource this Policy refers to.
That's it! Once you have a policy created, you may start registering rules.
post_policy = Policy[Post]()
Adding Rules to your Policy
To add a rule to your policy, you have two options:
You may first take a previously defined Rule
and call the register
method on your policy. This method calls for
a name and a Rule object:
async def can_edit(actor: User, post: Post):
return post.owner == actor
post_policy = Policy[Post]()
post_policy.register("update", can_edit)
The other, and arguably more practical option, is to used the rule
decorator provided by your new policy:
post_policy = Policy[Post]()
@policy.rule
async def update(actor: User, post: Post):
return post.owner == actor
This will automatically take your function, create a new rule from it, then register it on your policy.
A warning
Be aware that registering two rules with the same name or action name will simply overwrite the former rule defined for this name:
async def can_edit(actor: User, post: Post):
return post.owner == actor
async def can_read(actor: User, post: Post):
return post.owner == actor || actor in post.guests
post_policy.register("update", can_edit)
post_policy.register("update", can_read) # Overwrites the previously defined rule
Using a policy
The Policy
class exposes the same evaluation functions as the Rule
class does, but requires that you pass in an action name as well. Here are few examples:
if post_policy.allows('update', actor, post):
# The action is authorized...
else:
# The action is forbidden
response = post_policy.inspect("update", actor, post)
match response:
case Ok(_):
# The action is authorized
case Err(msg):
# The action is forbidden
post_policy.authorize("update", actor, post)
# The action is authorized
On top of these calls, a policy also exposes a grants
call. This one is a bit different as it does not take an action name as parameter, but instead will evaluate all rules in the policy for the given set of parameters provided to the function and return the result as a dictionary.
For example:
policy = Policy[Post]()
@policy.rule
async def create(actor: User):
return True
@policy.rule
async def view(actor: User, post: Post):
return post.owner == user | user in post.access_list
@policy.rule
async def update(actor: User, post: Post):
return post.owner == actor
print(policy.grants(post_owner, post))
# {
# "create": True,
# "view": True,
# "update": True,
# }
print(policy.grants(post_reader, post))
# {
# "create": True,
# "view": True,
# "update": False,
# }