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.
This commit is contained in:
Preston Holmes 2012-09-23 22:48:13 -07:00
parent 01362745ba
commit 373932fa6b
9 changed files with 222 additions and 46 deletions

View File

@ -0,0 +1,43 @@
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()

View File

@ -7,6 +7,7 @@ from django.contrib.auth.tests.forms import *
from django.contrib.auth.tests.remote_user import * from django.contrib.auth.tests.remote_user import *
from django.contrib.auth.tests.management import * from django.contrib.auth.tests.management import *
from django.contrib.auth.tests.models 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.hashers import *
from django.contrib.auth.tests.signals import * from django.contrib.auth.tests.signals import *
from django.contrib.auth.tests.tokens import * from django.contrib.auth.tests.tokens import *

View File

@ -0,0 +1,45 @@
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'), [])

View File

@ -1,45 +0,0 @@
=========================================================
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

View File

@ -0,0 +1,122 @@
=========================================================
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.

View File

@ -16,6 +16,7 @@ documentation for the following WSGI servers:
:maxdepth: 1 :maxdepth: 1
modwsgi modwsgi
apache-auth
gunicorn gunicorn
uwsgi uwsgi

View File

@ -177,6 +177,13 @@ other approaches:
3. Copy the admin static files so that they live within your Apache 3. Copy the admin static files so that they live within your Apache
document root. document root.
Authenticating against Django's user database from Apache
=========================================================
Django provides a handler to allow Apache to authenticate users directly
against Django's authentication backends. See the :doc:`mod_wsgi authentication
documentation </howto/deployment/wsgi/apache-auth>`.
If you get a UnicodeEncodeError If you get a UnicodeEncodeError
=============================== ===============================

View File

@ -9,7 +9,6 @@ you quickly accomplish common tasks.
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
apache-auth
auth-remote-user auth-remote-user
custom-management-commands custom-management-commands
custom-model-fields custom-model-fields

View File

@ -146,6 +146,9 @@ Django 1.5 also includes several smaller improvements worth noting:
configuration duplication. More information can be found in the configuration duplication. More information can be found in the
:func:`~django.contrib.auth.decorators.login_required` documentation. :func:`~django.contrib.auth.decorators.login_required` documentation.
* Django now provides a mod_wsgi :doc:`auth handler
</howto/deployment/wsgi/apache-auth>`
Backwards incompatible changes in 1.5 Backwards incompatible changes in 1.5
===================================== =====================================