****************************************************************
:mod:`repoze.what` -- Authorization in TurboGears 2 applications
****************************************************************

:Status: Official
:Website: `<http://static.repoze.org/whatdocs/>`_

.. module:: repoze.what
    :synopsis: Setup authorization in WSGI applications

.. topic:: Overview

    This document describes how :mod:`repoze.what` is integrated into TurboGears
    and how you may get started with it. For more information, you may want
    to check :mod:`repoze.what`'s website.


:mod:`repoze.what` is a highly extensible and fully documented ``authorization`` 
framework which is well integrated into TurboGears (in fact, it started as a TG 
subproject).


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.

Your users' table is used by :mod:`repoze.who` to authenticate them and also
by :mod:`repoze.what` (along with the groups table) to find the groups to which 
the user belongs. Then your permissions table is only used by 
:mod:`repoze.what` to find the permissions granted to the groups to which the
current user belongs.

This is all configured through :mod:`repoze.what` and you are free to replace
these settings whenever you want. If you want to keep the three tables above
but you need more flexibility, you will get it by adjusting your
``{yourproject}.config.app_cfg`` module accordingly. If you don't want to keep
the three tables above for authentication or authorization (e.g., you want to
use `OpenID` authentication and store your groups and permissions in XML
files), then you should check the :mod:`repoze.what` manual to learn how 
to configure your TG application. (See also :ref:`openid` which describes
the process of setting up `OpenID` authentication in detail).

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 -- if so, check the :mod:`repoze.what` 
manual to learn more.


Restricting access with :mod:`repoze.what.predicates`
=====================================================

.. module:: repoze.what.predicates
    :synopsis: repoze.what built-in predicate checkers.

:mod:`repoze.what` allows you to define access rules based on so-called
"predicate checkers". 

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
:mod:`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 repoze.what.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 repoze.what.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 
:func:`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 repoze.what.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 = authorize.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()


.. 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 :mod:`repoze.what`,
although the list below may not always be up-to-date:


Single predicate checkers
~~~~~~~~~~~~~~~~~~~~~~~~~

.. class:: not_anonymous()

    Check that the current user has been authenticated.

.. class:: is_user(user_name)
    
    Check that the authenticated user's user name is the specified one.
    
    :param user_name: The required user name.
    :type user_name: str

.. class:: in_group(group_name)

    Check that the user belongs to the specified group.
    
    :param group_name: The name of the group to which the user must belong.
    :type group_name: str

.. class:: in_all_groups(group1_name, group2_name[, group3_name ...])

    Check that the user belongs to all of the specified groups.
    
    :param group1_name: The name of the first group the user must belong to.
    :param group2_name: The name of the second group the user must belong to.
    :param group3_name ...: The name of the other groups the user must belong to.

.. class:: in_any_group(group1_name, [group2_name ...])

    Check that the user belongs to at least one of the specified groups.
    
    :param group1_name: The name of the one of the groups the user may belong to.
    :param group2_name ...: The name of other groups the user may belong to.

.. class:: has_permission(permission_name)

    Check that the current user has the specified permission.
    
    :param permission_name: The name of the permission that must be granted to 
        the user.

.. class:: has_all_permissions(permission1_name, permission2_name[, permission3_name...])

    Check that the current user has been granted all of the specified 
    permissions.
    
    :param permission1_name: The name of the first permission that must be
        granted to the user.
    :param permission2_name: The name of the second permission that must be
        granted to the user.
    :param permission3_name ...: The name of the other permissions that must be
        granted to the user.

.. class:: has_any_permission(permission1_name[, permission2_name ...])

    Check that the user has at least one of the specified permissions.
    
    :param permission1_name: The name of one of the permissions that may be
        granted to the user.
    :param permission2_name ...: The name of the other permissions that may be
        granted to the user.

.. class:: Not(predicate)

    Negate the specified predicate.
    
    :param 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 :class:`repoze.what.predicate.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 repoze.what.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')
        @authorize.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:: All(predicate1, predicate2[, predicate3 ...])

    Check that all of the specified predicates are met.
    
    :param predicate1: The first predicate that must be met.
    :param predicate2: The second predicate that must be met.
    :param predicate3 ...: The other predicates that must be met.

.. class:: Any(predicate1[, predicate2 ...])

    Check that at least one of the specified predicates is met.
    
    :param predicate1: One of the predicates that may be met.
    :param 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)),
                                     authorize.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".


How TurboGears deals with :mod:`repoze.what` internally
=======================================================

.. note::

    TurboGears will configure :mod:`repoze.what` for you, if and only if you 
    have enabled authentication and authorization in 
    ``{yourproject}.config.app_cfg``.

TurboGears will take your auth settings defined in 
``{yourproject}.config.app_cfg`` and then it will configure :mod:`repoze.what` 
with such settings using its SQL plugin.

Also, it provides you will the functionality described above:
The ``@require`` decorator and the ability to define controller-wide predicates.

That's it -- TurboGears doesn't deal with :mod:`repoze.what` in any other way,
so it's absolutely safe for you to stop TurboGears from configuring
:mod:`repoze.what` with its SQL plugin so that you can set it up on your own,
while still using the @require decorator and the ability to control access
at the controller level.

