Ticket #3583: modpython.3.py

File modpython.3.py, 5.5 KB (added by Rick van Hattem <Rick.van.Hattem@…>, 7 years ago)

django/contrib/auth/handlers/modpython.py with patches to enable both PythonAuthenHandler and PythonAccessHandler

Line 
1from mod_python import apache
2import os
3from urllib import quote
4from django.core import signals
5from django.dispatch import dispatcher
6from django.core.handlers.base import BaseHandler
7from django.core.handlers.modpython import ModPythonRequest
8from django.conf import settings
9from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME
10from django.utils.encoding import iri_to_uri
11
12_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
13
14class 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
23def 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
32def 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
39def 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
53def 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
68def 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
104def 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)
Back to Top