Skip to content

Object-Level Permissions/Custom Conditions

What about object-level permissions? You can easily check object-level access in a custom condition that's evaluated to determine whether the statement takes effect. This condition is passed the view instance, so you can get the model instance with a call to view.get_object(). You can even reference multiple conditions, to keep your access methods focused and testable, as well as parametrize these conditions with arguments.

class AccountAccessPolicy(AccessPolicy):
    statements = [
        ## ... other statements ...
        {
            "action": ["withdraw"],
            "principal": ["*"],
            "effect": "allow",
            "condition": ["balance_is_positive", "user_must_be:owner"]
        },
        {
            "action": ["upgrade_to_gold_status"],
            "principal": ["*"],
            "effect": "allow",
            "condition": ["user_must_be:account_advisor"]
        }
        ## ... other statements ...
    ]

    def balance_is_positive(self, request, view, action) -> bool:
        account = view.get_object()
        return account.balance > 0

    def user_must_be(self, request, view, action, field: str) -> bool:
        account = view.get_object()
        return getattr(account, field) == request.user

Notice how we're re-using the user_must_be method by parameterizing it with the model field that should be equal for the user of the request: the statement will only be effective if this condition passes.

If you have multiple custom methods defined on the policy, you can construct boolean expressions to combine them. The syntax is the same as Python's boolean expressions.

Note that the condition_expression element is used instead of condition.

class AccountAccessPolicy(AccessPolicy):
    statements = [
        {
            "action": ["freeze"],
            "principal": ["*"],
            "effect": "allow",
            "condition_expression": ["(is_request_from_account_owner or is_FBI_request)"]
        },
    ]

    def is_FBI_request(self, request, view, action) -> bool:
        return is_request_from_fbi(request)

    def is_request_from_account_owner(self, request, view, action) -> bool:
        return account.owner == request.user