Ticket #689: 689.4.diff
File 689.4.diff, 20.6 KB (added by , 16 years ago) |
---|
-
django/contrib/auth/backends.py
=== modified file 'django/contrib/auth/backends.py'
78 78 return User.objects.get(pk=user_id) 79 79 except User.DoesNotExist: 80 80 return None 81 82 83 class RemoteUserBackend(ModelBackend): 84 """ 85 This backend is to be used in conjunction with the ``RemoteUserMiddleware`` 86 found in the middleware module of this package, and is used when the server 87 is handling authentication outside of Django. 88 89 By default, the ``authenticate`` method creates ``User`` objects for 90 usernames that don't already exist in the database. Subclasses can disable 91 this behavior by setting the ``create_unknown_user`` attribute to 92 ``False``. 93 """ 94 95 # Create a User object if not already in the database? 96 create_unknown_user = True 97 98 def authenticate(self, remote_user): 99 """ 100 The username passed as ``remote_user`` is considered trusted. This 101 method simply returns the ``User`` object with the given username, 102 creating a new ``User`` object if ``create_unknown_user`` is ``True``. 103 104 Returns None if ``create_unknown_user`` is ``False`` and a ``User`` 105 object with the given username is not found in the database. 106 """ 107 if not remote_user: 108 return 109 user = None 110 username = self.clean_username(remote_user) 111 112 # Note that this could be accomplished in one try-except clause, but 113 # instead we use get_or_create when creating unknown users since it has 114 # built-in safeguards for multiple threads. 115 if self.create_unknown_user: 116 user, created = User.objects.get_or_create(username=username) 117 if created: 118 user = self.configure_user(user) 119 else: 120 try: 121 user = User.objects.get(username=username) 122 except User.DoesNotExist: 123 pass 124 return user 125 126 def clean_username(self, username): 127 """ 128 Cleans the passed username to remove any extraneous text, and returns 129 the cleaned username. 130 131 By default, this method returns the passed username unmodified. 132 Override this method if you need to do special things with the 133 username, like stripping @realm or cleaning something like 134 cn=user,dc=domain. 135 """ 136 return username 137 138 def configure_user(self, user): 139 """ 140 Configures a user after creation and returns the updated user. 141 142 By default, this method returns the passed user unmodified. Override 143 this method if you would like to do additional setup on the user that 144 was just created, such as set groups based on attributes in an LDAP 145 directory. 146 """ 147 return user -
django/contrib/auth/middleware.py
=== modified file 'django/contrib/auth/middleware.py'
1 from django.contrib import auth 2 from django.core.exceptions import ImproperlyConfigured 3 4 1 5 class LazyUser(object): 2 6 def __get__(self, request, obj_type=None): 3 7 if not hasattr(request, '_cached_user'): … … 5 9 request._cached_user = get_user(request) 6 10 return request._cached_user 7 11 12 8 13 class AuthenticationMiddleware(object): 9 14 def process_request(self, request): 10 15 assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." 11 16 request.__class__.user = LazyUser() 12 17 return None 18 19 20 class RemoteUserMiddleware(object): 21 """ 22 Middleware for utilizing web-server-provided authentication. 23 24 If request.user is not authenticated, then this middleware attempts to 25 authenticate the username passed in the ``REMOTE_USER`` request header. 26 If authentication is successful, the user is automatically logged in to 27 persist the user in the session. 28 29 The header used is configurable and defaults to ``REMOTE_USER``. Subclass 30 this class and change the ``header`` attribute if you need to use a 31 different header. 32 """ 33 34 # Name of request header to grab username from. This will be the key as 35 # used in the request.META dictionary, i.e. the normalization of headers to 36 # all uppercase and the addition of "HTTP_" prefix apply. 37 header = "REMOTE_USER" 38 39 def process_request(self, request): 40 # AuthenticationMiddleware is required so that request.user exists. 41 if not hasattr(request, 'user'): 42 raise ImproperlyConfigured( 43 "The Django remote user auth middleware requires the" 44 " authentication middleware to be installed. Edit your" 45 " MIDDLEWARE_CLASSES setting to insert" 46 " 'django.contrib.auth.middleware.AuthenticationMiddleware'" 47 " before the RemoteUserMiddleware class.") 48 try: 49 username = request.META[self.header] 50 except KeyError: 51 # If specified header doesn't exist then return (leaving 52 # request.user set to AnonymousUser by the 53 # AuthenticationMiddleware). 54 return 55 # If the user is already authenticated and that user is the user we are 56 # getting passed in the headers, then the correct user is already 57 # persisted in the session and we don't need to continue. 58 if request.user.is_authenticated(): 59 if request.user.username == self.clean_username(username, request): 60 return 61 # We are seeing this user for the first time in this session, attempt 62 # to authenticate the user. 63 user = auth.authenticate(remote_user=username) 64 if user: 65 # User is valid. Set request.user and persist user in the session 66 # by logging the user in. 67 request.user = user 68 auth.login(request, user) 69 70 def clean_username(self, username, request): 71 """ 72 Allows the backend to clean the username, if the backend defines a 73 clean_username method. 74 """ 75 backend_str = request.session[auth.BACKEND_SESSION_KEY] 76 backend = auth.load_backend(backend_str) 77 try: 78 username = backend.clean_username(username) 79 except AttributeError: # Backend has no clean_username method. 80 pass 81 return username -
django/contrib/auth/tests/__init__.py
=== modified file 'django/contrib/auth/tests/__init__.py'
1 1 from django.contrib.auth.tests.basic import BASIC_TESTS 2 from django.contrib.auth.tests.views import PasswordResetTest, ChangePasswordTest 2 from django.contrib.auth.tests.views \ 3 import PasswordResetTest, ChangePasswordTest 3 4 from django.contrib.auth.tests.forms import FORM_TESTS 5 from django.contrib.auth.tests.remote_user \ 6 import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest 4 7 from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS 5 8 6 9 # The password for the fixture data users is 'password' -
django/contrib/auth/tests/remote_user.py
=== added file 'django/contrib/auth/tests/remote_user.py'
1 from datetime import datetime 2 3 from django.conf import settings 4 from django.contrib.auth.backends import RemoteUserBackend 5 from django.contrib.auth.models import AnonymousUser, User 6 from django.test import TestCase 7 8 9 class RemoteUserTest(TestCase): 10 11 middleware = 'django.contrib.auth.middleware.RemoteUserMiddleware' 12 backend = 'django.contrib.auth.backends.RemoteUserBackend' 13 known_user = 'knownuser' 14 15 def setUp(self): 16 self.curr_middleware = settings.MIDDLEWARE_CLASSES 17 self.curr_auth = settings.AUTHENTICATION_BACKENDS 18 settings.MIDDLEWARE_CLASSES += (self.middleware,) 19 settings.AUTHENTICATION_BACKENDS = (self.backend,) 20 21 def test_no_remote_user(self): 22 """ 23 Tests requests where no remote user is specified and insures that no 24 users get created. 25 """ 26 num_users = User.objects.count() 27 28 response = self.client.get('/') 29 self.assert_(isinstance(response.context['user'], AnonymousUser)) 30 self.assertEqual(User.objects.count(), num_users) 31 32 response = self.client.get('/', REMOTE_USER=None) 33 self.assert_(isinstance(response.context['user'], AnonymousUser)) 34 self.assertEqual(User.objects.count(), num_users) 35 36 response = self.client.get('/', REMOTE_USER='') 37 self.assert_(isinstance(response.context['user'], AnonymousUser)) 38 self.assertEqual(User.objects.count(), num_users) 39 40 def test_unknown_user(self): 41 """ 42 Tests the case where the username passed in the header does not exist 43 as a User. 44 """ 45 num_users = User.objects.count() 46 response = self.client.get('/', REMOTE_USER='newuser') 47 self.assertEqual(response.context['user'].username, 'newuser') 48 self.assertEqual(User.objects.count(), num_users + 1) 49 User.objects.get(username='newuser') 50 51 # Another request with same user should not create any new users. 52 response = self.client.get('/', REMOTE_USER='newuser') 53 self.assertEqual(User.objects.count(), num_users + 1) 54 55 def test_known_user(self): 56 """ 57 Tests the case where the username passed in the header is a valid User. 58 """ 59 User.objects.create(username='knownuser') 60 User.objects.create(username='knownuser2') 61 num_users = User.objects.count() 62 response = self.client.get('/', REMOTE_USER='knownuser') 63 self.assertEqual(response.context['user'].username, 'knownuser') 64 self.assertEqual(User.objects.count(), num_users) 65 # Test that a different user passed in the headers causes the new user 66 # to be logged in. 67 response = self.client.get('/', REMOTE_USER='knownuser2') 68 self.assertEqual(response.context['user'].username, 'knownuser2') 69 self.assertEqual(User.objects.count(), num_users) 70 71 def test_last_login(self): 72 """ 73 Tests that a user's last_login is set the first time they make a 74 request but not updated in subsequent requests with the same session. 75 """ 76 user = User.objects.create(username='knownuser') 77 # Set last_login to something so we can determine if it changes. 78 default_login = datetime(2000, 1, 1) 79 user.last_login = default_login 80 user.save() 81 82 response = self.client.get('/', REMOTE_USER=self.known_user) 83 self.assertNotEqual(default_login, response.context['user'].last_login) 84 85 user = User.objects.get(username='knownuser') 86 user.last_login = default_login 87 user.save() 88 response = self.client.get('/', REMOTE_USER=self.known_user) 89 self.assertEqual(default_login, response.context['user'].last_login) 90 91 def tearDown(self): 92 """Restores settings to avoid breaking other tests.""" 93 settings.MIDDLEWARE_CLASSES = self.curr_middleware 94 settings.AUTHENTICATION_BACKENDS = self.curr_auth 95 96 97 class RemoteUserNoCreateBackend(RemoteUserBackend): 98 """Backend that doesn't create unknown users.""" 99 create_unknown_user = False 100 101 102 class RemoteUserNoCreateTest(RemoteUserTest): 103 """ 104 Contains the same tests as RemoteUserTest, but using a custom auth backend 105 class that doesn't create unknown users. 106 """ 107 108 backend =\ 109 'django.contrib.auth.tests.remote_user.RemoteUserNoCreateBackend' 110 111 def test_unknown_user(self): 112 num_users = User.objects.count() 113 response = self.client.get('/', REMOTE_USER='newuser') 114 self.assert_(isinstance(response.context['user'], AnonymousUser)) 115 self.assertEqual(User.objects.count(), num_users) 116 117 118 class CustomRemoteUserBackend(RemoteUserBackend): 119 """ 120 Backend that overrides RemoteUserBackend methods. 121 """ 122 123 def clean_username(self, username): 124 """ 125 Grabs username before the @ character. 126 """ 127 return username.split('@')[0] 128 129 def configure_user(self, user): 130 """ 131 Sets user's email address. 132 """ 133 user.email = 'user@example.com' 134 user.save() 135 return user 136 137 138 class RemoteUserCustomTest(RemoteUserTest): 139 140 backend =\ 141 'django.contrib.auth.tests.remote_user.CustomRemoteUserBackend' 142 known_user = 'knownuser@example.com' 143 144 def test_clean_username(self): 145 """ 146 The strings passed in REMOTE_USER should be cleaned and the known users 147 should not have been configured with an email address. 148 """ 149 User.objects.create(username='knownuser') 150 User.objects.create(username='knownuser2') 151 num_users = User.objects.count() 152 153 response = self.client.get('/', REMOTE_USER='knownuser@abc.com') 154 self.assertEqual(response.context['user'].username, 'knownuser') 155 self.assertEqual(response.context['user'].email, '') 156 self.assertEqual(User.objects.count(), num_users) 157 158 response = self.client.get('/', REMOTE_USER='knownuser2@abc.com') 159 self.assertEqual(response.context['user'].username, 'knownuser2') 160 self.assertEqual(response.context['user'].email, '') 161 self.assertEqual(User.objects.count(), num_users) 162 163 def test_unknown_user(self): 164 """ 165 The unknown user created should be configured with an email address. 166 """ 167 super(RemoteUserCustomTest, self).test_unknown_user() 168 newuser = User.objects.get(username='newuser') 169 self.assertEqual(newuser.email, 'user@example.com') -
docs/ref/authbackends.txt
=== added file 'docs/ref/authbackends.txt'
1 .. _ref-authentication-backends: 2 3 ========================================== 4 Built-in authentication backends reference 5 ========================================== 6 7 .. module:: django.contrib.auth.backends 8 :synopsis: Django's built-in authentication backend classes. 9 10 This document details the authentication backends that come with Django. For 11 information on how how to use them and how to write your own authentication 12 backends, see the :ref:`Other authentication sources section 13 <authentication-backends>` of the :ref:`User authentication guide 14 <topics-auth>`. 15 16 17 Available authentication backends 18 ================================= 19 20 The following backends are available in :mod:`django.contrib.auth.backends`: 21 22 .. class:: ModelBackend 23 24 This is the default authentication backend used by Django. It 25 authenticates using usernames and passwords stored in the the 26 :class:`~django.contrib.auth.models.User` model. 27 28 29 .. class:: RemoteUserBackend 30 31 .. versionadded:: 1.1 32 33 Use this backend to take advantage of external-to-Django-handled 34 authentication. It authenticates using usernames passed in 35 :attr:`request.META['REMOTE_USER'] <django.http.HttpRequest.META>`. See 36 the :ref:`Authenticating against REMOTE_USER <topics-auth-remote-user>` 37 documentation. -
docs/ref/index.txt
=== modified file 'docs/ref/index.txt'
5 5 6 6 .. toctree:: 7 7 :maxdepth: 1 8 8 9 authbackends 9 10 contrib/index 10 11 databases 11 12 django-admin … … 19 20 signals 20 21 templates/index 21 22 unicode 22 23 No newline at end of file -
docs/topics/auth-remote-user.txt
=== added file 'docs/topics/auth-remote-user.txt'
1 .. _topics-auth-remote-user: 2 3 ==================================== 4 Authentication using ``REMOTE_USER`` 5 ==================================== 6 7 This document describes how to make use of external authentication sources 8 (where the Web server sets the ``REMOTE_USER`` environment variable) in your 9 Django applications. This type of authentication solution is typically seen on 10 intranet sites, with single sign-on solutions such as IIS and Integrated 11 Windows Authentication or Apache and `mod_authnz_ldap`_, `CAS`_, `Cosign`_, 12 `WebAuth`_, `mod_auth_sspi`_, etc. 13 14 .. _mod_authnz_ldap: http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html 15 .. _CAS: http://www.ja-sig.org/products/cas/ 16 .. _Cosign: http://weblogin.org 17 .. _WebAuth: http://www.stanford.edu/services/webauth/ 18 .. _mod_auth_sspi: http://sourceforge.net/projects/mod-auth-sspi 19 20 When the Web server takes care of authentication it typically sets the 21 ``REMOTE_USER`` environment variable for use in the underlying application. In 22 Django, ``REMOTE_USER`` is made available in the :attr:`request.META 23 <django.http.HttpRequest.META>` attribute. Django can be configured to make 24 use of the ``REMOTE_USER`` value using the ``RemoteUserMiddleware`` and 25 ``RemoteUserBackend`` classes found in :mod:`django.contirb.auth`. 26 27 Configuration 28 ============= 29 30 First, you must add the 31 :class:`django.contrib.auth.middleware.RemoteUserMiddleware` to the 32 :setting:`MIDDLEWARE_CLASSES` setting **after** the 33 :class:`django.contrib.auth.middleware.AuthenticationMiddleware`:: 34 35 MIDDLEWARE_CLASSES = ( 36 ... 37 'django.contrib.auth.middleware.AuthenticationMiddleware', 38 'django.contrib.auth.middleware.RemoteUserMiddleware', 39 ... 40 ) 41 42 Next, you must replace the :class:`~django.contrib.auth.backends.ModelBackend` 43 with ``RemoteUserBackend`` in the :setting:`AUTHENTICATION_BACKENDS` setting:: 44 45 AUTHENTICATION_BACKENDS = ( 46 'django.contrib.auth.backends.RemoteUserBackend', 47 ) 48 49 With this setup, ``RemoteUserMiddleware`` will detect the username in 50 ``request.META['REMOTE_USER']`` and will authenticate and auto-login that user 51 using the ``RemoteUserBackend``. 52 53 .. note:: 54 Since the ``RemoteUserBackend`` inherits from ``ModelBackend``, you will 55 still have all of the same permissions checking that is implemented in 56 ``ModelBackend``. 57 58 If your authentication mechanism uses a custom HTTP header and not 59 ``REMOTE_USER``, you can subclass ``RemoteUserMiddleware`` and set the 60 ``header`` attribute to the desired ``request.META`` key. For example:: 61 62 from django.contrib.auth.middleware import RemoteUserMiddleware 63 64 class CustomHeaderMiddleware(RemoteUserMiddleware): 65 header = 'HTTP_AUTHUSER' 66 67 68 ``RemoteUserBackend`` 69 ===================== 70 71 .. class:: django.contrib.backends.RemoteUserBackend 72 73 If you need more control, you can create your own authentication backend 74 that inherits from ``RemoteUserBackend`` and overrides certain parts: 75 76 Attributes 77 ~~~~~~~~~~ 78 79 .. attribute:: RemoteUserBackend.create_unknown_user 80 81 ``True`` or ``False``. Determines whether or not a 82 :class:`~django.contrib.auth.models.User` object is created if not already 83 in the database. Defaults to ``True``. 84 85 Methods 86 ~~~~~~~ 87 88 .. method:: RemoteUserBackend.clean_username(username) 89 90 Clean unwanted text from the passed username. This is useful if the 91 username includes unwanted text, such extra LDAP DN information. This 92 method needs to return the cleaned username. 93 94 .. method:: RemoteUserBackend.configure_user(user) 95 96 Configure a newly created user. This method is called immediately after a 97 new user is created, and can be used to perform custom setup actions for the 98 user. This methods needs to return the user object. -
docs/topics/auth.txt
=== modified file 'docs/topics/auth.txt'
1263 1263 and the Django-based applications. 1264 1264 1265 1265 So, to handle situations like this, the Django authentication system lets you 1266 plug in another authentication sources. You can override Django's default1266 plug in other authentication sources. You can override Django's default 1267 1267 database-based scheme, or you can use the default system in tandem with other 1268 1268 systems. 1269 1269 1270 See the :ref:`authentication backend reference <ref-authentication-backends>` 1271 for information on the authentication backends included with Django. 1272 1270 1273 Specifying authentication backends 1271 1274 ---------------------------------- 1272 1275 -
docs/topics/index.txt
=== modified file 'docs/topics/index.txt'
17 17 files 18 18 testing 19 19 auth 20 auth-remote-user 20 21 cache 21 22 email 22 23 i18n