Merge pull request #793 from gpodder/remove-advertise
[mygpo.git] / mygpo / api / basic_auth.py
blobe24da68648b520b5397ae9a7cf112184ce4aa8be
1 import base64
2 import binascii
3 from functools import wraps
5 from django.http import HttpResponse, HttpResponseBadRequest
6 from django.contrib.auth import authenticate, login
8 import logging
10 logger = logging.getLogger(__name__)
13 #############################################################################
15 def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs):
16 """
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.
21 """
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
30 auth = None
31 for h in ("AUTHORIZATION", "HTTP_AUTHORIZATION"):
32 auth = request.META.get(h, auth)
34 if not auth:
35 return auth_request()
37 auth = auth.split(None, 1)
39 if len(auth) == 2:
40 auth_type, credentials = auth
42 # NOTE: We are only support basic authentication for now.
43 if auth_type.lower() == "basic":
44 try:
45 credentials = (
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:
58 login(request, user)
59 request.user = user
61 return view(request, *args, **kwargs)
63 return auth_request()
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
73 return response
76 #############################################################################
78 def require_valid_user(protected_view):
79 """
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
97 username/password.)
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
111 return wrapper
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)
126 else:
127 # TODO: raise SuspiciousOperation here?
128 logger.warning(
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)
137 return wrapper
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
146 permission.
148 Use:
150 @logged_in_or_basicauth('asforums.view_forumcollection')
151 def your_view:
156 def view_decorator(func):
157 @wraps(func)
158 def wrapper(request, *args, **kwargs):
159 return view_or_basicauth(
160 func, request, lambda u: u.has_perm(perm), realm, *args, **kwargs
163 return wrapper
165 return view_decorator