mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										43
									
								
								django/contrib/auth/handlers/modwsgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								django/contrib/auth/handlers/modwsgi.py
									
									
									
									
									
										Normal 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() | ||||||
| @@ -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 * | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								django/contrib/auth/tests/handlers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								django/contrib/auth/tests/handlers.py
									
									
									
									
									
										Normal 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'), []) | ||||||
| @@ -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 |  | ||||||
							
								
								
									
										122
									
								
								docs/howto/deployment/wsgi/apache-auth.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								docs/howto/deployment/wsgi/apache-auth.txt
									
									
									
									
									
										Normal 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. | ||||||
| @@ -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 | ||||||
| ===================================== | ===================================== | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user