2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 from functools
import wraps
20 from django
.http
import HttpResponse
, HttpResponseBadRequest
21 from django
.contrib
.auth
import authenticate
23 from mygpo
.log
import log
24 from mygpo
.decorators
import repeat_on_conflict
27 @repeat_on_conflict(['user'])
28 def login(request
, user
):
29 from django
.contrib
.auth
import login
34 #############################################################################
36 def view_or_basicauth(view
, request
, test_func
, realm
= "", *args
, **kwargs
):
38 This is a helper function used by both 'require_valid_user' and
39 'has_perm_or_basicauth' that does the nitty of determining if they
40 are already logged in or if they have provided proper http-authorization
41 and returning the view if all goes well, otherwise responding with a 401.
43 if test_func(request
.user
):
44 # Already logged in, just return the view.
45 return view(request
, *args
, **kwargs
)
47 # They are not logged in. See if they provided login credentials
49 # the AUTHORIZATION header is used when passing auth-headers
50 # from Aapache to fcgi
52 for h
in ('AUTHORIZATION', 'HTTP_AUTHORIZATION'):
53 auth
= request
.META
.get(h
, auth
)
59 auth
= auth
.split(None, 1)
62 auth_type
, credentials
= auth
64 # NOTE: We are only support basic authentication for now.
65 if auth_type
.lower() == 'basic':
67 credentials
= credentials
.decode('base64').split(':', 1)
69 except UnicodeDecodeError as e
:
70 return HttpResponseBadRequest(
71 'Could not decode credentials: {msg}'.format(msg
=str(e
)))
73 if len(credentials
) == 2:
74 uname
, passwd
= credentials
75 user
= authenticate(username
=uname
, password
=passwd
)
76 if user
is not None and user
.is_active
:
77 login(request
, user
=user
)
80 return view(request
, *args
, **kwargs
)
85 def auth_request(realm
=''):
86 # Either they did not provide an authorization header or
87 # something in the authorization attempt failed. Send a 401
88 # back to them to ask them to authenticate.
89 response
= HttpResponse()
90 response
.status_code
= 401
91 response
['WWW-Authenticate'] = 'Basic realm="%s"' % realm
95 #############################################################################
97 def require_valid_user(protected_view
):
99 A simple decorator that requires a user to be logged in. If they are not
100 logged in the request is examined for a 'authorization' header.
102 If the header is present it is tested for basic authentication and
103 the user is logged in with the provided credentials.
105 If the header is not present a http 401 is sent back to the
106 requestor to provide credentials.
108 The purpose of this is that in several django projects I have needed
109 several specific views that need to support basic authentication, yet the
110 web site as a whole used django's provided authentication.
112 The uses for this are for urls that are access programmatically such as
113 by rss feed readers, yet the view requires a user to be logged in. Many rss
114 readers support supplying the authentication credentials via http basic
115 auth (and they do NOT support a redirect to a form where they post a
118 XXX: Fix usage descriptions, ideally provide an example as doctest.
120 @wraps(protected_view
)
121 def wrapper(request
, *args
, **kwargs
):
122 def check_valid_user(user
):
123 return user
.is_authenticated()
125 return view_or_basicauth(protected_view
, \
134 def check_username(protected_view
):
136 decorator to check whether the username passed to the view (from the URL)
137 matches the username with which the user is authenticated.
139 @wraps(protected_view
)
140 def wrapper(request
, username
, *args
, **kwargs
):
142 if request
.user
.username
.lower() == username
.lower():
143 return protected_view(request
, *args
, username
=username
, **kwargs
)
146 log('username in authentication (%s) and in requested resource (%s) don\'t match' % (request
.user
.username
, username
))
147 return HttpResponseBadRequest('username in authentication (%s) and in requested resource (%s) don\'t match' % (request
.user
.username
, username
))
152 #############################################################################
154 def has_perm_or_basicauth(perm
, realm
= ""):
156 This is similar to the above decorator 'logged_in_or_basicauth'
157 except that it requires the logged in user to have a specific
162 @logged_in_or_basicauth('asforums.view_forumcollection')
167 def view_decorator(func
):
169 def wrapper(request
, *args
, **kwargs
):
170 return view_or_basicauth(func
, request
,
171 lambda u
: u
.has_perm(perm
),
172 realm
, *args
, **kwargs
)
174 return view_decorator