Create FUNDING.yml
[wolnelektury.git] / src / basicauth.py
1 #############################################################################
2 # from http://djangosnippets.org/snippets/243/
3
4 from functools import wraps
5 import base64
6
7 from django.http import HttpResponse
8 from django.contrib.auth import authenticate, login
9
10
11 def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs):
12     """
13     This is a helper function used by 'logged_in_or_basicauth' and
14     'has_perm_or_basicauth' (deleted) that does the nitty of determining if they
15     are already logged in or if they have provided proper http-authorization
16     and returning the view if all goes well, otherwise responding with a 401.
17     """
18     if test_func(request.user):
19         # Already logged in, just return the view.
20         #
21         return view(request, *args, **kwargs)
22
23     # They are not logged in. See if they provided login credentials
24     #
25     if 'HTTP_AUTHORIZATION' in request.META:
26         auth = request.META['HTTP_AUTHORIZATION'].split()
27         if len(auth) == 2:
28             # NOTE: We are only support basic authentication for now.
29             #
30             if auth[0].lower() == "basic":
31                 uname, passwd = base64.b64decode(auth[1].encode('utf-8')).decode('utf-8').split(':')
32                 user = authenticate(username=uname, password=passwd)
33                 if user is not None:
34                     if user.is_active:
35                         login(request, user)
36                         request.user = user
37                         return view(request, *args, **kwargs)
38
39     # Either they did not provide an authorization header or
40     # something in the authorization attempt failed. Send a 401
41     # back to them to ask them to authenticate.
42     #
43     response = HttpResponse()
44     response.status_code = 401
45     response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
46     return response
47
48
49 #
50 def logged_in_or_basicauth(realm=""):
51     """
52     A simple decorator that requires a user to be logged in. If they are not
53     logged in the request is examined for a 'authorization' header.
54
55     If the header is present it is tested for basic authentication and
56     the user is logged in with the provided credentials.
57
58     If the header is not present a http 401 is sent back to the
59     requestor to provide credentials.
60
61     The purpose of this is that in several django projects I have needed
62     several specific views that need to support basic authentication, yet the
63     web site as a whole used django's provided authentication.
64
65     The uses for this are for urls that are access programmatically such as
66     by rss feed readers, yet the view requires a user to be logged in. Many rss
67     readers support supplying the authentication credentials via http basic
68     auth (and they do NOT support a redirect to a form where they post a
69     username/password.)
70
71     Use is simple:
72
73     @logged_in_or_basicauth
74     def your_view:
75         ...
76
77     You can provide the name of the realm to ask for authentication within.
78     """
79     def view_decorator(func):
80         def wrapper(request, *args, **kwargs):
81             return view_or_basicauth(func, request,
82                                      lambda u: u.is_authenticated,
83                                      realm, *args, **kwargs)
84         return wrapper
85     return view_decorator
86
87
88 #############################################################################
89
90
91 def factory_decorator(decorator):
92     """ generates a decorator for a function factory class
93     if A(*) == f, factory_decorator(D)(A)(*) == D(f)
94     """
95     def fac_dec(func):
96         @wraps(func)
97         def wrapper(*args, **kwargs):
98             return decorator(func(*args, **kwargs))
99         return wrapper
100     return fac_dec