Skip to content

Commit e80b78d

Browse files
alexduttontomchristie
authored andcommitted
RemoteUserAuthentication, docs, and tests (#5306)
RemoteUserAuthentication, docs, and tests
1 parent 9b5a6be commit e80b78d

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

docs/api-guide/authentication.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,28 @@ If you're using an AJAX style API with SessionAuthentication, you'll need to mak
239239

240240
CSRF validation in REST framework works slightly differently to standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied.
241241

242+
243+
## RemoteUserAuthentication
244+
245+
This authentication scheme allows you to delegate authentication to your web server, which sets the `REMOTE_USER`
246+
environment variable.
247+
248+
To use it, you must have `django.contrib.auth.backends.RemoteUserBackend` (or a subclass) in your
249+
`AUTHENTICATION_BACKENDS` setting. By default, `RemoteUserBackend` creates `User` objects for usernames that don't
250+
already exist. To change this and other behaviour, consult the
251+
[Django documentation](https://docs.djangoproject.com/en/stable/howto/auth-remote-user/).
252+
253+
If successfully authenticated, `RemoteUserAuthentication` provides the following credentials:
254+
255+
* `request.user` will be a Django `User` instance.
256+
* `request.auth` will be `None`.
257+
258+
Consult your web server's documentation for information about configuring an authentication method, e.g.:
259+
260+
* [Apache Authentication How-To](https://httpd.apache.org/docs/2.4/howto/auth.html)
261+
* [NGINX (Restricting Access)](https://www.nginx.com/resources/admin-guide/#restricting_access)
262+
263+
242264
# Custom authentication
243265

244266
To implement a custom authentication scheme, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise.

rest_framework/authentication.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,24 @@ def authenticate_credentials(self, key):
201201

202202
def authenticate_header(self, request):
203203
return self.keyword
204+
205+
206+
class RemoteUserAuthentication(BaseAuthentication):
207+
"""
208+
REMOTE_USER authentication.
209+
210+
To use this, set up your web server to perform authentication, which will
211+
set the REMOTE_USER environment variable. You will need to have
212+
'django.contrib.auth.backends.RemoteUserBackend in your
213+
AUTHENTICATION_BACKENDS setting
214+
"""
215+
216+
# Name of request header to grab username from. This will be the key as
217+
# used in the request.META dictionary, i.e. the normalization of headers to
218+
# all uppercase and the addition of "HTTP_" prefix apply.
219+
header = "REMOTE_USER"
220+
221+
def authenticate(self, request):
222+
user = authenticate(remote_user=request.META.get(self.header))
223+
if user and user.is_active:
224+
return (user, None)

tests/test_authentication.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
HTTP_HEADER_ENCODING, exceptions, permissions, renderers, status
1717
)
1818
from rest_framework.authentication import (
19-
BaseAuthentication, BasicAuthentication, SessionAuthentication,
20-
TokenAuthentication
21-
)
19+
BaseAuthentication, BasicAuthentication, RemoteUserAuthentication, SessionAuthentication,
20+
TokenAuthentication)
2221
from rest_framework.authtoken.models import Token
2322
from rest_framework.authtoken.views import obtain_auth_token
2423
from rest_framework.compat import is_authenticated
@@ -64,6 +63,10 @@ def put(self, request):
6463
r'^basic/$',
6564
MockView.as_view(authentication_classes=[BasicAuthentication])
6665
),
66+
url(
67+
r'^remote-user/$',
68+
MockView.as_view(authentication_classes=[RemoteUserAuthentication])
69+
),
6770
url(
6871
r'^token/$',
6972
MockView.as_view(authentication_classes=[TokenAuthentication])
@@ -523,3 +526,20 @@ class MockUser(object):
523526
auth.authenticate_credentials('foo', 'bar')
524527
assert 'User inactive or deleted.' in str(error)
525528
authentication.authenticate = old_authenticate
529+
530+
531+
@override_settings(ROOT_URLCONF='tests.test_authentication',
532+
AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.RemoteUserBackend',))
533+
class RemoteUserAuthenticationUnitTests(TestCase):
534+
def setUp(self):
535+
self.username = 'john'
536+
self.email = 'lennon@thebeatles.com'
537+
self.password = 'password'
538+
self.user = User.objects.create_user(
539+
self.username, self.email, self.password
540+
)
541+
542+
def test_remote_user_works(self):
543+
response = self.client.post('/remote-user/',
544+
REMOTE_USER=self.username)
545+
self.assertEqual(response.status_code, status.HTTP_200_OK)

0 commit comments

Comments
 (0)