Authenticaton and Authorization
+++++++++++++++++++++++++++++++

:author: James Gardner
:date: 2008-08-30

.. warning:: First draft, more to come, use at your own risk!

Any comments, suggestions or improvements would be gratefully received on the
`Pylons mailing list <http://groups.google.com/group/pylons-discuss>`_.

The Basics
==========

When writing a web application you frequently come across the problem of
needing to restrict which visitors can see which pages. This problem can be
split into two parts. 

Authentication 
    The process of proving who the visitor is. Usually they will
    enter a username and password to prove their identity and the web application
    will set a cookie so that when they visit other pages on the site the
    application will be able to remember who the visitor is without needing to ask
    for their username and password again.

    Authentication is typically performed by a middleware component.

Authorization 
    The process of checking that an authenticated vistor has
    permission to see a particular piece of informtaion on the site.

    Authorization is typically performed in controller code.

These two parts are quite separate but often become confused becuase
information about who can be authenticated and what authorization permissions
each user has is often stored in the same place, for example a database or LDAP
repository.

This document will take you through some different ways of restricting access
to your data starting simply and getting more sophisticated.

Getting Started
===============

First install Pylons and follow the Getting Started Guide so that you have a
running HelloWorld application with a hello controller available at
http://localhost:5000/hello served with the --reload option.

Private Data
============

If you want to create a controller method that is for private use by other
methods and controller actions and cannot be accessed by any visiting a URL the
method name should start with an ``_`` underscore character.

Consider this example:

.. code-block:: Python

    from helloworld.lib.base import *

    class HelloController(BaseController):

        def hello(self):
            return Response(self._result())

        def _result(self):
            return 'Hello World'

If a visitor entered the URL http://localhost:5000/hello/hello they would see
``Hello World`` but if they accessed http://localhost:5000/hello/_result they
would be shown a 404 Not Found error document.

Using Pylons' __before__ method and session functionality
=========================================================

A very simple and powerful way of adding authentication and authorisation
functionality to your controller is by making use of the ``__before__()`` method.

Add the following ``__before__()`` method to your controller. It will be
executed before the action is called on each request. 

.. code-block:: Python

    from helloworld.lib.base import *

    template = """\
       <html>
         <head><title>Please Login!</title></head>
         <body>
           <h1>Please Login</h1>
           <form action="" method="post">
             <dl>
               <dt>Username:</dt>
               <dd><input type="text" name="username"></dd>
               <dt>Password:</dt>
               <dd><input type="password" name="password"></dd>
             </dl>
             <input type="hidden" name="r" value="%s" />
             <input type="submit" name="authform" />
             <hr />
           </form>
         </body>
       </html>
    """

    class HelloController(BaseController):

        def __before__(self, action, **params):  
            user = session.get('user', None)
            method = getattr(self, action, None)
            if method and getattr(method, 'private', False):
                if not user:
                    # No user signed in
                    return self._redirect_to_sign_in()
                elif user != 'james':
                    # Access denied
                    return self._redirect_to_sign_in()
            # Otherwise continue and execute the method:
            return

        def _redirect_to_sign_in(self):
            url = request.path_info
            if request.params:
                url += "?" + request.environ['QUERY_STRING']
            return redirect_to(action='sign_in' ,r=url)

        def sign_in(self):
            if len(request.params) > 1 and request.params['password'] == request.params['username']:
                session['user'] = request.params['username']
                session.save()
                h.redirect_to(request.params['r'])
            else:
                return Response(template % request.params['r']) 

        def public(self):
            return Response('This is public')
        public.private = False

        def private(self):
            return Response('This is private')
        private.private = True

.. Note:: Incidentally you could also define a corresponding  ``__after__()``
    object to be executed after each action is called.


Restricting Access to a Whole Site using Middleware
===================================================

Using HTTP Basic Authorisation
------------------------------

If you wish to restrict access to an entire site you can add some simple
middleware to your ``config/middleware.py`` file. Immediately after the lines:

.. code-block:: Python

    # YOUR MIDDLEWARE
    # Put your own middleware here, so that any problems are caught by the error
    # handling middleware underneath
    
add the following:

.. code-block:: Python

    from paste.auth.basic import AuthBasicHandler
    
    def authfunc(environ, username, password):
        if username:
            return username == password
        return False
    
    app = AuthBasicHandler(app, 'Test Realm', authfunc)

By customising the code in ``authfunc`` you can restrict who can sign in. If
you visit http://localhost:5000/hello/hello you will have to sign in before
seeing the message. If you press Cancel you will be shown the middleware's
Unauthorized message. Once the user has signed in they will remain signed in
until they close the browser window. 

You can find out in your code which user has signed in by looking at the
``request.environ['REMOTE_USER']`` varibale which contains the username:

.. code-block:: Python

    # in your Hello controller

        def hello(self):
            return Response(
                self._result() + ' ' + request.environ['REMOTE_USER']
            )

If you visit the page again you should see a greeting customised to your username.

See the documentation at http://pythonpaste.org/module-paste.auth.basic.html
for more information about the ``AuthBasicHandler`` middlware.

.. warning:: Do not use this example in production sites unless you are using
    SSL or need to work with very out-dated clients because the password entered is
    transmitted in plain text, instead use 
    `digest authentication <http://pythonpaste.org/module-paste.auth.digest.html>`_
    middleware.

Form and Cookie-Based Authentication
------------------------------------

Of course often users expect to see a sign in form as part of an HTML page
instead of a dialog box and their authenticated status can be saved with a
cookie so that they don't have to sign in each time.

Remove the middleware code from the last example and this time after the lines:

.. code-block:: Python

    # @@@ Change HTTPExceptions to HTTP responses @@@
    app = httpexceptions.make_middleware(app, global_conf)

Add this:

.. code-block:: Python

    from paste.auth.cookie import AuthCookieHandler
    from paste.auth.form import AuthFormHandler

    def authfunc(environ, username, password):
        if username:
            return username == password
        return False

    template = """\
    <html>
      <head><title>Please Login!</title></head>
      <body>
        <h1>Please Login</h1>
        <form action="%s" method="post">
          <dl>
            <dt>Username:</dt>
            <dd><input type="text" name="username"></dd>
            <dt>Password:</dt>
            <dd><input type="password" name="password"></dd>
          </dl>
          <input type="submit" name="authform" />
          <hr />
        </form>
      </body>
    </html>
    """

    app = AuthFormHandler(app, authfunc, template)
    app = AuthCookieHandler(app)

If you visit http://localhost:5000/hello/hello this time you will see the sign
in form. One clever trick of this setup is that if the sign in is successful
the request proceeds as a GET without the page needing to be refreshed so the
user immediately gains access to the content they were looking for.

To sign out you will need to write an action that removes the cookie
information. Something like this should work fine::

.. code-block:: Python

    # in your Hello controller

        def sign_out(self):
            del request.environ['REMOTE_USER']
            response = Response('You have been signed out.')
            response.headers['Set-cookie'] = 'PASTE_AUTH_COOKIE=; Path=/;'
            return response

If you visit http://localhost:5000/hello/sign_out you should be signed out and
if you visit http://localhost:5000/hello/hello again you should see the sign in
screen.

See http://pythonpaste.org/module-paste.auth.form.html for more information
about this code.

Accessing the authfunc from Within Controllers
----------------------------------------------

When using this type of middleware setup it is occasionally necessary to access
the ``authfunc()`` you have defined from within a controller. One way of doing
this is to make the authfunc a method of the ``Globals`` object ``g``. 

Add a new authfunc method to ``lib/app_globals.py``::

     class Globals(object):
        # ...

        def authfunc(self, environ, username, password):
            if username:
                return username == password
            return False

        # ...

In ``config/middleware.py`` you can now specify the authfunc as ``g.authfunc``:

.. code-block:: Python
    
        app = AuthFormHandler(app, g.authfunc, template)
 
The ``g.authfunc()`` method is also available in your controllers:

.. code-block:: Python

    # in your Hello controller

        def test(self):
            if g.authfunc(request.environ, 'james', 'james'):
                return Response(
                    "The username 'james' and password 'james' is a valid combination!"
                )
            return Response('Try again.')

If you visit http://localhost:5000/hello/test you will now see the message
``The username 'james' and password 'james' is a valid combination!``.

If your authfunc needed a database connection you could initialise it in the
``__init__()`` method and close it in the ``__del__()`` method.

AuthKit: Best of Both Worlds
============================

AuthKit from http://authkit.org combines the use of middleware and per action
authorisation. The middleware only catches 401 or 403 status codes and presents
a sign in screen. Every other response is allowed through. This means your
application simply has to use ``abort(401)`` when a user is not signed in or
``abort(403)`` when they don't have permissions to access a resource. 

Once you have setup AuthKit you can write code that looks like this:

.. code-block:: Python

    # in your Hello controller

        def hello(self):
            if not (request.environ.has_key('REMOTE_USER') and \
                request.environ['REMOTE_USER']):
                # No user signed in
                abort(401)
            # username are typically case insensitive
            elif request.environ['REMOTE_USER'].lower() != 'james':
                # Access denied
                abort(403)
            else:
                return Response(
                    self._result() + ' ' + request.environ['REMOTE_USER']
                )

In this example if a user is not signed in the request is aborted with a 401
code. If the user is signed in but is not authorised to access the particular
resource the request is aborted with a 403 code.

This system is really handy because it means your authentication system is
automatically compaitble with any other WSGI applications you might choose to
use in your controllers.

Of course AuthKit also has a sophisticated permissions system so ordinarily you
wouldn't need to write your authentication and authorisation checks manually as
described above. Instead you would derive your controller from one of AuthKit's
``SecureController`` s and specify some permissions:

.. code-block:: Python

    class HelloController(SecureController):

        model = model

        def hello(self):
            return Response('Hello world!')
        hello.permissions = And(ValidSession(), UserIn(['james','ben']))

In this case in order for someone to view the ``hello()`` action they would
need to be signed in with a session that hadn't expired and also have a
username that was ``james`` or ``ben``. There are a few permissions objects
already but you can also easily create your own.

For real world web applications you typically need a registration system and
authorisation system where different users have different roles for different
applications for different groups. AuthKit provides all this functionality too
making use of SQLAlchemy and can be extended to suit your needs. 

If your application needs this level of sophistication you might like to have a
look at the `AuthKit manual <http://authkit.org/manual.html>`_ which is not
quite complete in its current state. 


Other Authentication and Authorization Systems
==============================================

By now you should see the common pattern that a piece of middleware is used to
authenticate the user and then the user is authorised in a controller. There
are lots of diffent ways to handle authentication and authorization so not all
will follow this approach.

Other places to look include:

*   `authkit <http://authkit.org>`_ which is a complete auth system.

*   `barrel <http://lukearno.com/projects/barrel/>`_ which is a simple and 
    powerful system for authentication and authoristation.

*   `paste.auth <http://pythonpaste.org/module-paste.auth.html>`_ which has
    middleware supporting many different authentication schemes.





