Rules

Rule library that forms the core of Bridgekeeper.

This module defines the Rule base class, as well as a number of built-in rules.

The Rule API

class bridgekeeper.rules.Rule

Base class for rules.

All rules are instances of this class, but not directly; use (or write!) a subclass instead, as this class will raise NotImplementedError if you try to actually do anything with it.

check(user, instance=None)

Check if a user satisfies this rule.

Given a user, return a boolean indicating if that user satisfies this rule for a given instance, or if none is provided, every instance.

filter(user, queryset)

Filter a queryset to instances that satisfy this rule.

Given a queryset and a user, this method will return a filtered queryset that contains only instances from the original queryset for which the user satisfies this rule.

Parameters:
Returns:

A filtered queryset

Return type:

django.db.models.QuerySet

Warning

If you are subclassing this class, don’t override this method; override query() instead.

is_possible_for(user)

Check if it is possible for a user to satisfy this rule.

Returns True if it is possible for an instance to exist for which the given user satisfies this rule, False otherwise.

For example, in a multi-tenanted app, you might have a rule that allows access to model instances if a user is a staff user, or if the instance’s tenant matches the user’s tenant.

In that case, check(), when called without an instance, would return True only for staff users (since only they can see every instance). This method would return True for all users, because every user could possibly see an instance (whether it’s one that exists currently in the database, or a hypothetical one that might in the future).

Cases where this method would return False include where a user doesn’t have the right role or subscription plan to use a feature at all; this method is the single-permission equivalent of has-module-perms.

Built-in Blanket Rules

bridgekeeper.rules.always_allow

Rule that always allows access to everything.

bridgekeeper.rules.always_deny

Rule that never allows access to anything.

bridgekeeper.rules.is_authenticated

Rule that allows access to users for whom is_authenticated is True.

bridgekeeper.rules.is_superuser

Rule that allows access to users for whom is_superuser is True.

bridgekeeper.rules.is_staff

Rule that allows access to users for whom is_staff is True.

bridgekeeper.rules.is_active

Rule that allows access to users for whom is_active is True.

Rule Classes

class bridgekeeper.rules.Attribute(attr, matches)

Rule class that checks the value of an instance attribute.

This rule is satisfied by model instances where the attribute given in attr matches the value given in matches.

Parameters:
  • attr (str) – An attribute name to match against on the model instance.
  • value – The value to match against, or a callable that takes a user and returns a value to match against.

For instance, if you had a model class Widget with an attribute colour that was either 'red', 'green' or 'blue', you could limit access to blue widgets with the following:

blue_widgets_only = Attribute('colour', matches='blue')

Restricting access in a multi-tenanted application by matching a model’s tenant to the user’s might look like this:

applications_by_tenant = Attribute('tenant',
                                   lambda user: user.tenant)

Warning

This rule uses Python equality (==) when checking a retrieved Python object, but performs an equality check on the database when filtering a QuerySet. Avoid using it with imprecise types (e.g. floats), and ensure that you are using the correct Python type (e.g. decimal.Decimal for decimals rather than floats or strings), to prevent inconsistencies.

class bridgekeeper.rules.Relation(attr, model, rule)

Check that a rule applies to a ForeignKey.

Parameters:
  • attr (str) – Name of a foreign key attribute to check.
  • model (type) – Model class on the other side of the foreign key.
  • rule (Rule) – Rule to check the foreign key against.

For example, given Applicant and Application models, to allow access to all applications to anyone who has permission to access the related applicant:

perms['foo.view_application'] = Relation(
    'applicant', Applicant, perms['foo.view_applicant'])
class bridgekeeper.rules.ManyRelation(attr, query_attr, model, rule)

Check that a rule applies to a many-object relationship.

This can be used in a similar fashion to Relation, but across a ManyToManyField, or the remote end of a ForeignKey.

Parameters:
  • attr (str) – Name of a many-object relationship to check. This is the accessor name; the name that you access on a model instance to get a manager for the other side of the relationship. If you are on the reverse side of the relationship (the side where the field is not defined), this is typically mymodel_set, where mymodel is the lowercased model name, unless you’ve set related_name.
  • query_attr (str) – Query name to use; that is, the name that you use when filtering this relationship using .filter(). If you are on the side of the relationship where the field is defined, this is typically the lowercased model name (e.g. mymodel on its own, not mymodel_set), unless you’ve set related_name or related_query_name.
  • model (type) – Model class on the other side of the relationship.
  • rule (Rule) – Rule to check the foreign object against.

For example, given Agency and Customer models, to allow agency users access only to customers that have a relationship with their agency:

perms['foo.view_customer'] = ManyRelation(
    'agencies', Agency, Is(lambda user: user.agency))
class bridgekeeper.rules.Is(instance)

Rule class that checks the identity of the instance.

This rule is satisfied only by a the provided model instance.

Parameters:instance – The instance to match against, or a callable that takes a user and returns a value to match against.

For instance, if you only wanted a user to be able to update their own profile:

own_profile = Is(lambda user: user.profile)
class bridgekeeper.rules.In(collection)

Rule class that checks the instance is a member of a collection.

This rule is satisfied only by model instances that are members of the provided collection.

Parameters:collection – The collection to match against, or a callable that takes a user and returns a value to match against.

For instance, if you only wanted to match groups a user is in:

own_profile = Is(lambda user: user.profile)

Built-in rule instances

bridgekeeper.rules.current_user = Is(<function <lambda>>)

Rule class that checks the identity of the instance.

This rule is satisfied only by a the provided model instance.

Parameters:instance – The instance to match against, or a callable that takes a user and returns a value to match against.

For instance, if you only wanted a user to be able to update their own profile:

own_profile = Is(lambda user: user.profile)
bridgekeeper.rules.in_current_groups = In(<function <lambda>>)

Rule class that checks the instance is a member of a collection.

This rule is satisfied only by model instances that are members of the provided collection.

Parameters:collection – The collection to match against, or a callable that takes a user and returns a value to match against.

For instance, if you only wanted to match groups a user is in:

own_profile = Is(lambda user: user.profile)

Extension Points (For Writing Your Own Rule Subclasses)

class bridgekeeper.rules.Rule

If you want to create your own rule class, these are the methods you need to override.

query(user)

Generate a Q object.

Note

This method is used internally by filter(); subclasses will need to override it but you should never need to call it directly.

Given a user, return a Q object which will filter a queryset down to only instances for which the given user satisfies this rule.

Alternatively, return UNIVERSAL if this user satisfies this rule for every possible object, or EMPTY if this user cannot satisfy this rule for any possible object. (These two values are usually only returned in “blanket rules” which depend only on some property of the user, e.g. the built-in is_staff, but these are usually best created with the blanket_rule decorator.)

Parameters:user (django.contrib.auth.models.User) – The user to match against.
Returns:A query that will filter a queryset to match this rule.
Return type:django.db.models.Q
check(user, instance=None)

Check if a user satisfies this rule.

Given a user, return a boolean indicating if that user satisfies this rule for a given instance, or if none is provided, every instance.

bridgekeeper.rules.UNIVERSAL = UNIVERSAL
bridgekeeper.rules.EMPTY = EMPTY