1 | from mod_python import apache
|
---|
2 | import os
|
---|
3 | from urllib import quote
|
---|
4 | from django.core import signals
|
---|
5 | from django.dispatch import dispatcher
|
---|
6 | from django.core.handlers.base import BaseHandler
|
---|
7 | from django.core.handlers.modpython import ModPythonRequest
|
---|
8 | from django.conf import settings
|
---|
9 | from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME
|
---|
10 | from django.utils.encoding import iri_to_uri
|
---|
11 |
|
---|
12 | _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
|
---|
13 |
|
---|
14 | class ModPythonAuthOptions:
|
---|
15 | def __init__(self, req):
|
---|
16 | options = req.get_options()
|
---|
17 | self.permission_name = options.get('DjangoPermissionName', None)
|
---|
18 | self.staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
|
---|
19 | self.superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
|
---|
20 | self.raise_forbidden = _str_to_bool(options.get('DjangoRaiseForbidden', "off"))
|
---|
21 | self.settings_module = options.get('DJANGO_SETTINGS_MODULE', None)
|
---|
22 |
|
---|
23 | def setup_environment(req, options):
|
---|
24 | """
|
---|
25 | mod_python fakes the environ, and thus doesn't process SetEnv. This ensures
|
---|
26 | any future imports relying on settings will work.
|
---|
27 | """
|
---|
28 | os.environ.update(req.subprocess_env)
|
---|
29 | if options.settings_module:
|
---|
30 | os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module
|
---|
31 |
|
---|
32 | def authenticate_user(user):
|
---|
33 | if user is None:
|
---|
34 | return False
|
---|
35 | if hasattr(user, 'is_authenticated') and not user.is_authenticated():
|
---|
36 | return False
|
---|
37 | return True
|
---|
38 |
|
---|
39 | def validate_user(user, options):
|
---|
40 | if hasattr(user, 'is_active') and not user.is_active:
|
---|
41 | return False
|
---|
42 | if options.staff_only and not getattr(user, 'is_staff', None):
|
---|
43 | return False
|
---|
44 | if options.superuser_only and not getattr(user, 'is_superuser', None):
|
---|
45 | return False
|
---|
46 | # If a permission is required then user must have a has_perm function to
|
---|
47 | # validate.
|
---|
48 | if options.permission_name and (not hasattr(user, 'has_perm') or
|
---|
49 | not user.has_perm(options.permission_name)):
|
---|
50 | return False
|
---|
51 | return True
|
---|
52 |
|
---|
53 | def redirect_to_login(req):
|
---|
54 | path = quote(req.uri)
|
---|
55 | if req.args:
|
---|
56 | path = '%s?%s' % (path, req.args)
|
---|
57 | path = quote(path)
|
---|
58 | iri = '%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path)
|
---|
59 | uri = iri_to_uri(iri)
|
---|
60 | req.err_headers_out.add('Location', uri)
|
---|
61 | if req.proto_num >= 1001:
|
---|
62 | # Use HTTP Error 303 (see other) for HTTP/1.1 browsers.
|
---|
63 | raise apache.SERVER_RETURN, apache.HTTP_SEE_OTHER
|
---|
64 | else:
|
---|
65 | # Otherwise use HTTP Error 302 (moved temporarily).
|
---|
66 | raise apache.SERVER_RETURN, apache.HTTP_MOVED_TEMPORARILY
|
---|
67 |
|
---|
68 | def authenhandler(req, **kwargs):
|
---|
69 | """
|
---|
70 | mod_python authentication handler that checks against Django's auth
|
---|
71 | database.
|
---|
72 | """
|
---|
73 | options = ModPythonAuthOptions(req)
|
---|
74 | setup_environment(req, options)
|
---|
75 |
|
---|
76 | dispatcher.send(signal=signals.request_started)
|
---|
77 | try:
|
---|
78 | # This populates req.user too, so it's important to do first.
|
---|
79 | password = req.get_basic_auth_pw()
|
---|
80 |
|
---|
81 | # Get the user from any of the installed backends.
|
---|
82 | user = authenticate(username=req.user, password=password)
|
---|
83 |
|
---|
84 | if not authenticate_user(user):
|
---|
85 | # Raise unauthorized if the user doesn't authenticate to bring up a
|
---|
86 | # password dialog box to allow the user to authenticate.
|
---|
87 | return apache.HTTP_UNAUTHORIZED
|
---|
88 |
|
---|
89 | # Validate the user
|
---|
90 | if validate_user(user, options):
|
---|
91 | return apache.OK
|
---|
92 |
|
---|
93 | # mod_python docs say that HTTP_FORBIDDEN should be raised if the user
|
---|
94 | # authenticates but doesn't validate but Django provides it as an
|
---|
95 | # option, alternately raising HTTP_UNAUTHORIZED again to provide the
|
---|
96 | # option of logging in as an alternate user.
|
---|
97 | if options.raise_forbidden:
|
---|
98 | return apache.HTTP_FORBIDDEN
|
---|
99 | else:
|
---|
100 | return apache.HTTP_UNAUTHORIZED
|
---|
101 | finally:
|
---|
102 | dispatcher.send(signal=signals.request_finished)
|
---|
103 |
|
---|
104 | def accesshandler(req):
|
---|
105 | """
|
---|
106 | mod_python access handler that uses the contrib.auth framework (with
|
---|
107 | sessions, therefore requiring a session cookie).
|
---|
108 | """
|
---|
109 | options = ModPythonAuthOptions(req)
|
---|
110 | setup_environment(req, options)
|
---|
111 |
|
---|
112 | # Set up middleware, now that settings works we can do it now.
|
---|
113 | base_handler = BaseHandler()
|
---|
114 | base_handler.load_middleware()
|
---|
115 |
|
---|
116 | dispatcher.send(signal=signals.request_started)
|
---|
117 | try:
|
---|
118 | request = ModPythonRequest(req)
|
---|
119 |
|
---|
120 | # Apply request middleware
|
---|
121 | for middleware_method in base_handler._request_middleware:
|
---|
122 | response = middleware_method(request)
|
---|
123 | if response:
|
---|
124 | # If we get a response then there's no need to keep processing
|
---|
125 | # any remaining request middleware.
|
---|
126 | break
|
---|
127 |
|
---|
128 | user = getattr(request, 'user', None)
|
---|
129 | if not authenticate_user(user):
|
---|
130 | # Rather than raising HTTP_UNAUTHORIZED (which the browser won't be
|
---|
131 | # able to handle since this isn't basic HTTP authentication), write
|
---|
132 | # a response which redirects to settings.LOGIN_URL
|
---|
133 | redirect_to_login(req)
|
---|
134 |
|
---|
135 | if validate_user(user, options):
|
---|
136 | return apache.OK
|
---|
137 |
|
---|
138 | # mod_python docs say that HTTP_FORBIDDEN should be raised if the user
|
---|
139 | # authenticates but doesn't validate but Django provides it as an
|
---|
140 | # option, alternately redirecting to login to provide the option of
|
---|
141 | # logging in as an alternate user.
|
---|
142 | if options.raise_forbidden:
|
---|
143 | return apache.HTTP_FORBIDDEN
|
---|
144 | else:
|
---|
145 | redirect_to_login(req)
|
---|
146 |
|
---|
147 | finally:
|
---|
148 | dispatcher.send(signal=signals.request_finished)
|
---|