Access Control and Authorization

This document describes how authentication is integrated into TurboGears and how you may get started with it.

How authentication and authorization is set up by default

If you enabled authentication and authorization in your project when it was generated by TurboGears, then it’s been set up to store your users, groups and permissions in SQLAlchemy-managed tables or Ming collections.

Your users’ table is used by repoze.who to authenticate them. When the authentication has success TurboGears uses the TGAuthMetadata instance declared in config.base_config.sa_auth.authmetadata to retrieve the informations about your user which are required for authorization.

You are free to change the authmetadata object as you wish, usually if your authentication model changes, your authmetadata object will change accordingly.

You can even get rid of authorization based on groups and permissions and use other authorization patterns (e.g., roles, based on network components) or simply use a mix of patterns. To do this you can set authmetadata to None and register your own metadata providers for repoze.who.

Restricting access with tg.predicates

tg.predicates allows you to define access rules based on so-called “predicate checkers”. It is a customized version of the repoze.what module which has been merged into TurboGears itself to make easier to support different authentication backends.

A predicate is the condition that must be met for the user to be able to access the requested source. Such a predicate, or condition, may be made up of more predicates – those are called compound predicates. Action controllers, or controllers, may have only one predicate, be it single or compound.

A predicate checker is a class that checks whether a predicate or condition is met.

If a user is not logged in, or does not have the proper permissions, the predicate checker throws a 401 (HTTP Unauthorized) which is caught by the repoze.who middleware to display the login page allowing the user to login, and redirecting the user back to the proper page when they are done.

For example, if you have a predicate which is “grant access to any authenticated user”, then you can use the following built-in predicate checker:

from tg.predicates import not_anonymous

p = not_anonymous(msg='Only logged in users can read this post')

Or if you have a predicate which is “allow access to root or anyone with the ‘manage’ permission”, then you may use the following built-in predicate checker:

from tg.predicates import Any, is_user, has_permission

p = Any(is_user('root'), has_permission('manage'),
        msg='Only administrators can remove blog posts')

As you may have noticed, predicates receive the msg keyword argument to use its value as the error message if the predicate is not met. It’s optional and if you don’t define it, the built-in predicates will use the default English message; you may take advantage of this functionality to make such messages translatable.

Note

Good predicate messages don’t explain what went wrong; instead, they describe the predicate in the current context (regardless of whether the condition is met or not!). This is because such messages may be used in places other than in a user-visible message (e.g., in the log file).

  • Really bad: “Please login to access this area”.
  • Bad: “You cannot delete an user account because you are not an administrator”.
  • OK: “You have to be an administrator to delete user accounts”.
  • Perfect: “Only administrators can delete user accounts”.

Below are described the convenient utilities TurboGears provides to deal with predicates in your applications.

Action-level authorization

You can control access on a per action basis by using the tg.decorators.require() decorator on the actions in question. All you have to do is pass the predicate to that decorator. For example:

# ...
from tg import require
from tg.predicates import Any, is_user, has_permission
# ...
class MyCoolController(BaseController):
    # ...
    @expose('yourproject.templates.start_vacations')
    @require(Any(is_user('root'), has_permission('manage'),
                 msg='Only administrators can remove blog posts'))
    def only_for_admins():
        flash('Hello admin!')
        dict()
    # ...

Controller-level authorization

If you want that all the actions from a given controller meet a common authorization criteria, then you may define the allow_only attribute of your controller class:

from yourproject.lib.base import BaseController

class Admin(BaseController):
    allow_only = predicates.has_permission('manage')

    @expose('yourproject.templates.index')
    def index(self):
        flash(_("Secure controller here"))
        return dict(page='index')

    @expose('yourproject.templates.index')
    def some_where(self):
        """This is protected too.

        Only those with "manage" permissions may access.

        """
        return dict()

If you need to specify additional options for the requirement you can assign a require instance instead of a plain predicate to the allow_only attribute:

from yourproject.lib.base import BaseController
from tg import require

class AdminAPI(BaseController):
    allow_only = require(predicates.has_permission('manage'),
                         smart_denial=True)

    @expose('json')
    def create_user(self, user_name):
        pass

Warning

Do not use this feature if the login URL would be mapped to that controller, as that would result in a cyclic redirect.

Built-in predicate checkers

These are the predicate checkers that are included with tg.predicates, although the list below may not always be up-to-date:

Single predicate checkers

class tg.predicates.not_anonymous

Check that the current user has been authenticated.

class tg.predicates.is_user(user_name)

Check that the authenticated user’s user name is the specified one.

Parameters:user_name (str) – The required user name.
class tg.predicates.in_group(group_name)

Check that the user belongs to the specified group.

Parameters:group_name (str) – The name of the group to which the user must belong.
class tg.predicates.in_all_groups(group1_name, group2_name[, group3_name ...])

Check that the user belongs to all of the specified groups.

Parameters:
  • group1_name – The name of the first group the user must belong to.
  • group2_name – The name of the second group the user must belong to.
  • .. (group3_name) – The name of the other groups the user must belong to.
class tg.predicates.in_any_group(group1_name[, group2_name ...])

Check that the user belongs to at least one of the specified groups.

Parameters:
  • group1_name – The name of the one of the groups the user may belong to.
  • .. (group2_name) – The name of other groups the user may belong to.
class tg.predicates.has_permission(permission_name)

Check that the current user has the specified permission.

Parameters:permission_name – The name of the permission that must be granted to the user.
class tg.predicates.has_all_permissions(permission1_name, permission2_name[, permission3_name...])

Check that the current user has been granted all of the specified permissions.

Parameters:
  • permission1_name – The name of the first permission that must be granted to the user.
  • permission2_name – The name of the second permission that must be granted to the user.
  • .. (permission3_name) – The name of the other permissions that must be granted to the user.
class tg.predicates.has_any_permission(permission1_name[, permission2_name ...])

Check that the user has at least one of the specified permissions.

Parameters:
  • permission1_name – The name of one of the permissions that may be granted to the user.
  • .. (permission2_name) – The name of the other permissions that may be granted to the user.
class tg.predicates.Not(predicate)

Negate the specified predicate.

Parameters:predicate – The predicate to be negated.

Custom single predicate checkers

You may create your own predicate checkers if the built-in ones are not enough to achieve a given task.

To do so, you should extend the tg.predicates.Predicate class. For example, if your predicate is “Check that the current month is the specified one”, your predicate checker may look like this:

from datetime import date
from tg.predicates import Predicate

class is_month(Predicate):
    message = 'The current month must be %(right_month)s'

    def __init__(self, right_month, **kwargs):
        self.right_month = right_month
        super(is_month, self).__init__(**kwargs)

    def evaluate(self, environ, credentials):
        if date.today().month != self.right_month:
            self.unmet()

Warning

When you create a predicate, don’t try to guess/assume the context in which the predicate is evaluated when you write the predicate message because such a predicate may be used in a different context.

  • Bad: “The software can be released if it’s %(right_month)s”.
  • Good: “The current month must be %(right_month)s”.

If you defined that class in, say, {yourproject}.lib.auth, you may use it as in this example:

# ...
from spain_travels.lib.auth import is_month
# ...
class SummerVacations(BaseController):
    # ...
    @expose('spain_travels.templates.start_vacations')
    @require(is_month(7))
    def start_vacations():
        flash('Have fun!')
        dict()
    # ...

Built-in compound predicate checkers

You may create a compound predicate by aggregating single (or even compound) predicate checkers with the functions below:

class tg.predicates.All(predicate1, predicate2[, predicate3 ...])

Check that all of the specified predicates are met.

Parameters:
  • predicate1 – The first predicate that must be met.
  • predicate2 – The second predicate that must be met.
  • .. (predicate3) – The other predicates that must be met.
class tg.predicates.Any(predicate1[, predicate2 ...])

Check that at least one of the specified predicates is met.

Parameters:
  • predicate1 – One of the predicates that may be met.
  • .. (predicate2) – Other predicates that may be met.

But you can also nest compound predicates:

# ...
from yourproject.lib.auth import is_month
# ...
@authorize.require(authorize.All(
                                 Any(is_month(4), is_month(10)),
                                 predicates.has_permission('release')
                                 ))
def release_ubuntu(self, **kwargs):
    return dict()
# ...

Which translates as “Anyone granted the ‘release’ permission may release a version of Ubuntu, if and only if it’s April or October”.