bouncer

Simple Declarative Authentication DSL inspired by Ryan Bates’ excellent cancan library

travis-badge

Introduction

Meet bouncer.

bouncer is your faithful servant. Big, burly, trustworthy – smarter than he looks, but very effective in what he does. You just have to talk simply to him. For example:

from bouncer import authorization_method
from bouncer.constants import *

@authorization_method
def authorize(user, they):

    if user.is_admin:
        they.can(MANAGE, ALL)
    else:
        they.can(READ, ALL)
        they.cannot(READ, 'TopSecretDocs')

        def if_author(article):
            return article.author == user

        they.can(EDIT, 'Article', if_author)

And once you have that setup, you can ask questions like:

jonathan = User(name='jonathan',admin=False)
marc = User(name='marc',admin=False)

article = Article(author=jonathan)

print can(jonathan, EDIT, article)   # True
print can(marc, EDIT, article)       # False

# Can Marc view articles in general?
print can(marc, VIEW, Article)       # True

Installation

pip install bouncer

Defining Abilities

@authorization_method

User permissions are defined in a method decorated with @authorize_method

A simple setup looks like so …

@authorization_method
def authorize(user, they):

    if user.is_admin:
        they.can(MANAGE, ALL)
    else:
        they.can(READ, ALL)

        def if_author(article):
            return article.author == user

        they.can(EDIT, Article, if_author)

If you do not think the “they.can” is pythonic enough you can use the append syntax

@authorization_method
def authorize(user, abilities):

    if user.is_admin:
        abilities.append(MANAGE, ALL)
    else:
        abilities.append(READ, ALL)

        # See I am using a string here
        abilities.append(EDIT, 'Article', author=user)

Alternative syntax

dict syntax

You can also use an alternative dict syntax. The following is equivalent to above:

@authorization_method
def authorize(user, they):

    if user.is_admin:
        they.can(MANAGE, ALL)
    else:
        they.can(READ, ALL)
        they.can(EDIT, Article, author=user)

You can add multiple conditions to the dict:

they.can(READ, Article, published=True, active=True)

Strings instead of classes

Use can use Strings instead of classes so you do not need to import a bunch of files you are not using in initialization

@authorization_method
def authorize(user, they):

    if user.is_admin:
        they.can(MANAGE, ALL)
    else:
        they.can(READ, ALL)

        # Notice that I am using a string here
        they.can(EDIT, 'Article', author=user)

Combining Rules

You can (are encouraged to) combine similar rules on a single line:

they.can((EDIT,READ,DELETE),(Article,Photo))

Combining Abilities

It is possible to define multiple abilites for the same resource. This is particularly useful in combination with the cannot method

they.can(MANAGE, ALL)
then.cannot(DELETE, ('USER', 'ACCOUNT')

Checking Abilities

There are two main ways for checking for authorization. can (and its brother cannot) and ensure

  • can returns a boolean
  • ensure will raise an AccessDenied Exception
from bouncer import can, ensure
from bouncer.constants import *

jonathan = User(name='jonathan',admin=False)

# can jonathan edit articles in general
can(jonathan, EDIT, Article)

# ensure jonathan edit articles in general -- otherwise we are going to throw an exception
ensure(jonathan, EDIT, Article)

article = Article(author=jonathan)

# can jonathan delete this specific article
can(jonathan, EDIT, article)

Decorating your User Model

Optionally, you can add helper methods into your User model by using @authorization_target

For example:

from bouncer import authorization_target

@authorization_target
class User(object):

def __init__(self, **kwargs):
    self.id = kwargs.get('id', 1)
    self.name = kwargs.get('name', '')
    self.admin = kwargs.get('name', False)
    pass

@property
def is_admin(self):
    return self.admin

jonathan = User(name='jonathan',admin=False)
marc = User(name='marc',admin=False)

article = Article(author=jonathan)

print jonathan.can(EDIT,article)   # True
print marc.can(EDIT,article)       # False

Flask

If you use Flask, I am currently working on a Flask extension – follow its progress here: flask-bouncer.

Questions / Issues

Feel free to ping me on twitter: @tushman or add issues or PRs at https://github.com/jtushman/bouncer

Fork me on GitHub