diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index b2f392262a519..b34da60299d15 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -76,14 +76,7 @@ def process_request(self, request): # authenticated remote-user, or return (leaving request.user set to # AnonymousUser by the AuthenticationMiddleware). if request.user.is_authenticated(): - try: - stored_backend = load_backend(request.session.get( - auth.BACKEND_SESSION_KEY, '')) - if isinstance(stored_backend, RemoteUserBackend): - auth.logout(request) - except ImportError: - # backend failed to load - auth.logout(request) + self._remove_invalid_user(request) return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already @@ -91,6 +84,11 @@ def process_request(self, request): if request.user.is_authenticated(): if request.user.get_username() == self.clean_username(username, request): return + else: + # An authenticated user is associated with the request, but + # it does not match the authorized user in the header. + self._remove_invalid_user(request) + # We are seeing this user for the first time in this session, attempt # to authenticate the user. user = auth.authenticate(remote_user=username) @@ -112,3 +110,17 @@ def clean_username(self, username, request): except AttributeError: # Backend has no clean_username method. pass return username + + def _remove_invalid_user(self, request): + """ + Removes the current authenticated user in the request which is invalid + but only if the user is authenticated via the RemoteUserBackend. + """ + try: + stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, '')) + except ImportError: + # backend failed to load + auth.logout(request) + else: + if isinstance(stored_backend, RemoteUserBackend): + auth.logout(request) diff --git a/django/contrib/auth/tests/test_remote_user.py b/django/contrib/auth/tests/test_remote_user.py index 5b743ac8c1952..45e96133ce6c6 100644 --- a/django/contrib/auth/tests/test_remote_user.py +++ b/django/contrib/auth/tests/test_remote_user.py @@ -125,6 +125,24 @@ def test_header_disappears(self): response = self.client.get('/remote_user/') self.assertEqual(response.context['user'].username, 'modeluser') + def test_user_switch_forces_new_login(self): + """ + Tests that if the username in the header changes between requests + that the original user is logged out + """ + User.objects.create(username='knownuser') + # Known user authenticates + response = self.client.get('/remote_user/', + **{self.header: self.known_user}) + self.assertEqual(response.context['user'].username, 'knownuser') + # During the session, the REMOTE_USER changes to a different user. + response = self.client.get('/remote_user/', + **{self.header: "newnewuser"}) + # Ensure that the current user is not the prior remote_user + # In backends that create a new user, username is "newnewuser" + # In backends that do not create new users, it is '' (anonymous user) + self.assertNotEqual(response.context['user'].username, 'knownuser') + def tearDown(self): """Restores settings to avoid breaking other tests.""" settings.MIDDLEWARE_CLASSES = self.curr_middleware diff --git a/docs/releases/1.4.14.txt b/docs/releases/1.4.14.txt index 6c140ee6dc134..811c3f67eaa93 100644 --- a/docs/releases/1.4.14.txt +++ b/docs/releases/1.4.14.txt @@ -38,3 +38,12 @@ if a file with the uploaded name already exists. underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``), rather than iterating through an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, etc.). + +``RemoteUserMiddleware`` session hijacking +========================================== + +When using the :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` +and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between +requests without an intervening logout could result in the prior user's session +being co-opted by the subsequent user. The middleware now logs the user out on +a failed login attempt. diff --git a/docs/releases/1.5.9.txt b/docs/releases/1.5.9.txt index 232cbb0767987..5070f0eca1f6c 100644 --- a/docs/releases/1.5.9.txt +++ b/docs/releases/1.5.9.txt @@ -38,3 +38,12 @@ if a file with the uploaded name already exists. underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``), rather than iterating through an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, etc.). + +``RemoteUserMiddleware`` session hijacking +========================================== + +When using the :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` +and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between +requests without an intervening logout could result in the prior user's session +being co-opted by the subsequent user. The middleware now logs the user out on +a failed login attempt. diff --git a/docs/releases/1.6.6.txt b/docs/releases/1.6.6.txt index c2ebdb9efb7fa..e52ea6b23f1e5 100644 --- a/docs/releases/1.6.6.txt +++ b/docs/releases/1.6.6.txt @@ -39,6 +39,15 @@ underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``), rather than iterating through an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, etc.). +``RemoteUserMiddleware`` session hijacking +========================================== + +When using the :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` +and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between +requests without an intervening logout could result in the prior user's session +being co-opted by the subsequent user. The middleware now logs the user out on +a failed login attempt. + Bugfixes ========