| 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)
|
|---|