Basics

Policies

Policies provide a way to group your authorization logic around a particular resource.

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,
# }

Copyright © 2025