3 from functools
import wraps
5 from django
.http
import HttpResponse
, HttpResponseBadRequest
6 from django
.contrib
.auth
import authenticate
, login
10 logger
= logging
.getLogger(__name__
)
13 #############################################################################
15 def view_or_basicauth(view
, request
, test_func
, realm
="", *args
, **kwargs
):
17 This is a helper function used by both 'require_valid_user' and
18 'has_perm_or_basicauth' that does the nitty of determining if they
19 are already logged in or if they have provided proper http-authorization
20 and returning the view if all goes well, otherwise responding with a 401.
22 if test_func(request
.user
):
23 # Already logged in, just return the view.
24 return view(request
, *args
, **kwargs
)
26 # They are not logged in. See if they provided login credentials
28 # the AUTHORIZATION header is used when passing auth-headers
29 # from Aapache to fcgi
31 for h
in ("AUTHORIZATION", "HTTP_AUTHORIZATION"):
32 auth
= request
.META
.get(h
, auth
)
37 auth
= auth
.split(None, 1)
40 auth_type
, credentials
= auth
42 # NOTE: We are only support basic authentication for now.
43 if auth_type
.lower() == "basic":
46 base64
.b64decode(credentials
).decode("utf-8").split(":", 1)
49 except (UnicodeDecodeError, binascii
.Error
) as e
:
50 return HttpResponseBadRequest(
51 "Could not decode credentials: {msg}".format(msg
=str(e
))
54 if len(credentials
) == 2:
55 uname
, passwd
= credentials
56 user
= authenticate(username
=uname
, password
=passwd
)
57 if user
is not None and user
.is_active
:
61 return view(request
, *args
, **kwargs
)
66 def auth_request(realm
=""):
67 # Either they did not provide an authorization header or
68 # something in the authorization attempt failed. Send a 401
69 # back to them to ask them to authenticate.
70 response
= HttpResponse()
71 response
.status_code
= 401
72 response
["WWW-Authenticate"] = 'Basic realm="%s"' % realm
76 #############################################################################
78 def require_valid_user(protected_view
):
80 A simple decorator that requires a user to be logged in. If they are not
81 logged in the request is examined for a 'authorization' header.
83 If the header is present it is tested for basic authentication and
84 the user is logged in with the provided credentials.
86 If the header is not present a http 401 is sent back to the
87 requestor to provide credentials.
89 The purpose of this is that in several django projects I have needed
90 several specific views that need to support basic authentication, yet the
91 web site as a whole used django's provided authentication.
93 The uses for this are for urls that are access programmatically such as
94 by rss feed readers, yet the view requires a user to be logged in. Many rss
95 readers support supplying the authentication credentials via http basic
96 auth (and they do NOT support a redirect to a form where they post a
99 XXX: Fix usage descriptions, ideally provide an example as doctest.
102 @wraps(protected_view
)
103 def wrapper(request
, *args
, **kwargs
):
104 def check_valid_user(user
):
105 return user
.is_authenticated
107 return view_or_basicauth(
108 protected_view
, request
, check_valid_user
, "", *args
, **kwargs
114 def check_username(protected_view
):
116 decorator to check whether the username passed to the view (from the URL)
117 matches the username with which the user is authenticated.
120 @wraps(protected_view
)
121 def wrapper(request
, username
, *args
, **kwargs
):
123 if request
.user
.username
.lower() == username
.lower():
124 return protected_view(request
, *args
, username
=username
, **kwargs
)
127 # TODO: raise SuspiciousOperation here?
129 "username in authentication (%s) and in requested resource (%s) don't match"
130 % (request
.user
.username
, username
)
132 return HttpResponseBadRequest(
133 "username in authentication (%s) and in requested resource (%s) don't match"
134 % (request
.user
.username
, username
)
140 #############################################################################
142 def has_perm_or_basicauth(perm
, realm
=""):
144 This is similar to the above decorator 'logged_in_or_basicauth'
145 except that it requires the logged in user to have a specific
150 @logged_in_or_basicauth('asforums.view_forumcollection')
156 def view_decorator(func
):
158 def wrapper(request
, *args
, **kwargs
):
159 return view_or_basicauth(
160 func
, request
, lambda u
: u
.has_perm(perm
), realm
, *args
, **kwargs
165 return view_decorator