Commit 373932fa authored by Preston Holmes's avatar Preston Holmes
Browse files

fixed #10809 -- add a mod_wsgi authentication handler

Thanks to baumer1122 for the suggestion and initial 
patch and David Fischer for the contributions and
long term patch maintenance and docs.
parent 01362745
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
from django.contrib.auth.models import User
from django import db
from django.utils.encoding import force_bytes


def check_password(environ, username, password):
    """
    Authenticates against Django's auth database

    mod_wsgi docs specify None, True, False as return value depending
    on whether the user exists and authenticates.
    """

    # db connection state is managed similarly to the wsgi handler
    # as mod_wsgi may call these functions outside of a request/response cycle
    db.reset_queries()

    try:
        try:
            user = User.objects.get(username=username, is_active=True)
        except User.DoesNotExist:
            return None
        return user.check_password(password)
    finally:
        db.close_connection()


def groups_for_user(environ, username):
    """
    Authorizes a user based on groups
    """

    db.reset_queries()

    try:
        try:
            user = User.objects.get(username=username, is_active=True)
        except User.DoesNotExist:
            return []

        return [force_bytes(group.name) for group in user.groups.all()]
    finally:
        db.close_connection()
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ from django.contrib.auth.tests.forms import *
from django.contrib.auth.tests.remote_user import *
from django.contrib.auth.tests.management import *
from django.contrib.auth.tests.models import *
from django.contrib.auth.tests.handlers import *
from django.contrib.auth.tests.hashers import *
from django.contrib.auth.tests.signals import *
from django.contrib.auth.tests.tokens import *
+45 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
from django.contrib.auth.models import User, Group
from django.test import TestCase


class ModWsgiHandlerTestCase(TestCase):
    """
    Tests for the mod_wsgi authentication handler
    """

    def setUp(self):
        user1 = User.objects.create_user('test', 'test@example.com', 'test')
        User.objects.create_user('test1', 'test1@example.com', 'test1')

        group = Group.objects.create(name='test_group')
        user1.groups.add(group)

    def test_check_password(self):
        """
        Verify that check_password returns the correct values as per
        http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
        """

        # User not in database
        self.assertTrue(check_password({}, 'unknown', '') is None)

        # Valid user with correct password
        self.assertTrue(check_password({}, 'test', 'test'))

        # Valid user with incorrect password
        self.assertFalse(check_password({}, 'test', 'incorrect'))

    def test_groups_for_user(self):
        """
        Check that groups_for_user returns correct values as per
        http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Group_Authorisation
        """

        # User not in database
        self.assertEqual(groups_for_user({}, 'unknown'), [])

        self.assertEqual(groups_for_user({}, 'test'), [b'test_group'])
        self.assertEqual(groups_for_user({}, 'test1'), [])

docs/howto/apache-auth.txt

deleted100644 → 0
+0 −45
Original line number Diff line number Diff line
=========================================================
Authenticating against Django's user database from Apache
=========================================================

Since keeping multiple authentication databases in sync is a common problem when
dealing with Apache, you can configuring Apache to authenticate against Django's
:doc:`authentication system </topics/auth>` directly. This requires Apache
version >= 2.2 and mod_wsgi >= 2.0. For example, you could:

* Serve static/media files directly from Apache only to authenticated users.

* Authenticate access to a Subversion_ repository against Django users with
  a certain permission.

* Allow certain users to connect to a WebDAV share created with mod_dav_.

.. _Subversion: http://subversion.tigris.org/
.. _mod_dav: http://httpd.apache.org/docs/2.2/mod/mod_dav.html

Configuring Apache
==================

To check against Django's authorization database from a Apache configuration
file, you'll need to set 'wsgi' as the value of ``AuthBasicProvider`` or
``AuthDigestProvider`` directive and then use the ``WSGIAuthUserScript``
directive to set the path to your authentification script:

.. code-block:: apache

    <Location /example/>
        AuthType Basic
        AuthName "example.com"
        AuthBasicProvider wsgi
        WSGIAuthUserScript /usr/local/wsgi/scripts/auth.wsgi
        Require valid-user
    </Location>

Your auth.wsgi script will have to implement either a
``check_password(environ, user, password)`` function (for ``AuthBasicProvider``)
or a ``get_realm_hash(environ, user, realm)`` function (for ``AuthDigestProvider``).

See the `mod_wsgi documentation`_ for more details about the implementation
of such a solution.

.. _mod_wsgi documentation: http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
+122 −0
Original line number Diff line number Diff line
=========================================================
Authenticating against Django's user database from Apache
=========================================================

Since keeping multiple authentication databases in sync is a common problem when
dealing with Apache, you can configure Apache to authenticate against Django's
:doc:`authentication system </topics/auth>` directly. This requires Apache
version >= 2.2 and mod_wsgi >= 2.0. For example, you could:

* Serve static/media files directly from Apache only to authenticated users.

* Authenticate access to a Subversion_ repository against Django users with
  a certain permission.

* Allow certain users to connect to a WebDAV share created with mod_dav_.

.. _Subversion: http://subversion.tigris.org/
.. _mod_dav: http://httpd.apache.org/docs/2.2/mod/mod_dav.html

Authentication with mod_wsgi
============================

Make sure that mod_wsgi is installed and activated and that you have
followed the steps to setup
:doc:`Apache with mod_wsgi </howto/deployment/wsgi/modwsgi>`

Next, edit your Apache configuration to add a location that you want
only authenticated users to be able to view:

.. code-block:: apache

    WSGIScriptAlias / /path/to/mysite/config/mysite.wsgi

    WSGIProcessGroup %{GLOBAL}
    WSGIApplicationGroup django

    <Location "/secret">
        AuthType Basic
        AuthName "Top Secret"
        Require valid-user
        AuthBasicProvider wsgi
        WSGIAuthUserScript /path/to/mysite/config/mysite.wsgi
    </Location>

The ``WSGIAuthUserScript`` directive tells mod_wsgi to execute the
``check_password`` function in specified wsgi script, passing the user name and
password that it receives from the prompt. In this example, the
``WSGIAuthUserScript`` is the same as the ``WSGIScriptAlias`` that defines your
application :doc:`that is created by django-admin.py startproject
</howto/deployment/wsgi/index>`.

.. admonition:: Using Apache 2.2 with authentication

    Make sure that ``mod_auth_basic`` and ``mod_authz_user`` are loaded.

    These might be compiled statically into Apache, or you might need to use
    LoadModule to load them dynamically in your ``httpd.conf``:

    .. code-block:: apache

        LoadModule auth_basic_module modules/mod_auth_basic.so
        LoadModule authz_user_module modules/mod_authz_user.so

Finally, edit your WSGI script ``mysite.wsgi`` to tie Apache's
authentication to your site's authentication mechanisms by importing the
check_user function:

.. code-block:: python

    import os
    import sys

    os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

    from django.contrib.auth.handlers.modwsgi import check_user

    from django.core.handlers.wsgi import WSGIHandler
    application = WSGIHandler()


Requests beginning with ``/secret/`` will now require a user to authenticate.

The mod_wsgi `access control mechanisms documentation`_ provides additional
details and information about alternative methods of authentication.

.. _access control mechanisms documentation: http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms

Authorization with mod_wsgi and Django groups
---------------------------------------------

mod_wsgi also provides functionality to restrict a particular location to
members of a group.

In this case, the Apache configuration should look like this:

.. code-block:: apache

    WSGIScriptAlias / /path/to/mysite/config/mysite.wsgi

    WSGIProcessGroup %{GLOBAL}
    WSGIApplicationGroup django

    <Location "/secret">
        AuthType Basic
        AuthName "Top Secret"
        AuthBasicProvider wsgi
        WSGIAuthUserScript /path/to/mysite/config/mysite.wsgi
        WSGIAuthGroupScript /path/to/mysite/config/mysite.wsgi
        Require group secret-agents
        Require valid-user
    </Location>

To support the ``WSGIAuthGroupScript`` directive, the same WSGI script
``mysite.wsgi`` must also import the ``groups_for_user`` function which
returns a list groups the given user belongs to.

.. code-block:: python

    from django.contrib.auth.handlers.modwsgi import check_user, groups_for_user

Requests for ``/secret/`` will now also require user to be a member of the
"secret-agents" group.
Loading