A simple decorator that requires a user to be logged in. If they are not logged in the request is examined for a 'authorization' header.
If the header is present it is tested for basic authentication and the user is logged in with the provided credentials.
If the header is not present a http 401 is sent back to the requestor to provide credentials.
The purpose of this is that in several django projects I have needed several specific views that need to support basic authentication, yet the web site as a whole used django's provided authentication.
The uses for this are for urls that are access programmatically such as by rss feed readers, yet the view requires a user to be logged in. Many rss readers support supplying the authentication credentials via http basic auth (and they do NOT support a redirect to a form where they post a username/password.)
Use is simple:
@logged_in_or_basicauth
def your_view:
...
You can provide the name of the realm to ask for authentication within.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import base64
from django.http import HttpResponse
from django.contrib.auth import authenticate, login
#############################################################################
#
def view_or_basicauth(view, request, test_func, realm = "", *args, **kwargs):
"""
This is a helper function used by both 'logged_in_or_basicauth' and
'has_perm_or_basicauth' that does the nitty of determining if they
are already logged in or if they have provided proper http-authorization
and returning the view if all goes well, otherwise responding with a 401.
"""
if test_func(request.user):
# Already logged in, just return the view.
#
return view(request, *args, **kwargs)
# They are not logged in. See if they provided login credentials
#
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
# NOTE: We are only support basic authentication for now.
#
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
user = authenticate(username=uname, password=passwd)
if user is not None:
if user.is_active:
login(request, user)
request.user = user
return view(request, *args, **kwargs)
# Either they did not provide an authorization header or
# something in the authorization attempt failed. Send a 401
# back to them to ask them to authenticate.
#
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
#############################################################################
#
def logged_in_or_basicauth(realm = ""):
"""
A simple decorator that requires a user to be logged in. If they are not
logged in the request is examined for a 'authorization' header.
If the header is present it is tested for basic authentication and
the user is logged in with the provided credentials.
If the header is not present a http 401 is sent back to the
requestor to provide credentials.
The purpose of this is that in several django projects I have needed
several specific views that need to support basic authentication, yet the
web site as a whole used django's provided authentication.
The uses for this are for urls that are access programmatically such as
by rss feed readers, yet the view requires a user to be logged in. Many rss
readers support supplying the authentication credentials via http basic
auth (and they do NOT support a redirect to a form where they post a
username/password.)
Use is simple:
@logged_in_or_basicauth
def your_view:
...
You can provide the name of the realm to ask for authentication within.
"""
def view_decorator(func):
def wrapper(request, *args, **kwargs):
return view_or_basicauth(func, request,
lambda u: u.is_authenticated(),
realm, *args, **kwargs)
return wrapper
return view_decorator
#############################################################################
#
def has_perm_or_basicauth(perm, realm = ""):
"""
This is similar to the above decorator 'logged_in_or_basicauth'
except that it requires the logged in user to have a specific
permission.
Use:
@logged_in_or_basicauth('asforums.view_forumcollection')
def your_view:
...
"""
def view_decorator(func):
def wrapper(request, *args, **kwargs):
return view_or_basicauth(func, request,
lambda u: u.has_perm(perm),
realm, *args, **kwargs)
return wrapper
return view_decorator
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
This is exactly what I needed. In my initial testing it works well but the usage note should be:
Note the parentheses. That is because
logged_in_or_basicauth
is not a decorator but a function which returns a decorator. To specify the HTTP AUTH realm, do this:#
has_perm_or_basicauth only checks the permission the first time. After that it just blindly allows access to all authenticated users. You probably want to call test_func again right before you call the view.
#
I love it. Thanks.
#
how can i use this on apache using virtualenv? it runs on test server but on apache it asks authentication again and again :)
#
To huseyinalb and others : dont forget to put the option
on your apache conf
#
Yeah, on line 34, you need to check the test_func again.
Otherwise, has_perm_or_basicauth will succeed if the user is already authenticated but doesn't have the perm.
#
Line 28 was crashing out for me since the return from b64decode is a byte[], not a string. I had to add a
decode("ascii")
like the following:uname, passwd = base64.b64decode(auth[1]).decode("ascii").split(':')
#
Nice snippet. Just two questions...
1) I can't see any logout statement... what about the sessions cleanup?
2) the snippet is many years old (2007, wow!), in the meantime ... doesn't exist anything similar in django-core?
#
I just created a git repository for this to collect all the fixes from the comments and have a working version of this available:
https://github.com/m7v8/django-basic-authentication-decorator
#
Please login first before commenting.