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