mirror of https://github.com/django/django.git
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:
parent
01362745ba
commit
373932fa6b
|
@ -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()
|
|
@ -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 *
|
||||||
|
|
|
@ -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'), [])
|
|
@ -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
|
|
|
@ -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.
|
|
@ -16,6 +16,7 @@ documentation for the following WSGI servers:
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
modwsgi
|
modwsgi
|
||||||
|
apache-auth
|
||||||
gunicorn
|
gunicorn
|
||||||
uwsgi
|
uwsgi
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue